getDevModeType() {
return devModeType;
}
+ /**
+ * Whether the development mode type is not local.
+ *
+ * @return true if {@link #getDevModeType()} is not {@link DevModeType#LOCAL}
+ */
+ public boolean isNotLocalDevModeType() {
+ return devModeType.orElse(null) != DevModeType.LOCAL;
+ }
+
/**
* An Auxiliary Application is a second application running in the same JVM as a primary application.
*
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java
index 111b6171fdba6..c032fce642b7b 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java
@@ -173,8 +173,6 @@ public final class RunTimeConfigurationGenerator {
static final MethodDescriptor CU_ADD_SOURCE_FACTORY_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class,
"addSourceFactoryProvider",
void.class, SmallRyeConfigBuilder.class, ConfigSourceFactoryProvider.class);
- static final MethodDescriptor CU_WITH_MAPPING = MethodDescriptor.ofMethod(ConfigUtils.class, "addMapping",
- void.class, SmallRyeConfigBuilder.class, String.class, String.class);
static final MethodDescriptor RCS_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSource.class, String.class);
static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class);
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java
index 49d64e6e45251..7e99084f77538 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java
@@ -280,9 +280,13 @@ private Path createAppCDSFromExit(JarBuildItem jarResult,
Path workingDirectory = appCDSPathsContainer.workingDirectory;
Path appCDSPath = appCDSPathsContainer.resultingFile;
- List javaArgs = new ArrayList<>(3);
+ boolean debug = log.isDebugEnabled();
+ List javaArgs = new ArrayList<>(debug ? 4 : 3);
javaArgs.add("-XX:ArchiveClassesAtExit=" + appCDSPath.getFileName().toString());
javaArgs.add(String.format("-D%s=true", MainClassBuildStep.GENERATE_APP_CDS_SYSTEM_PROPERTY));
+ if (debug) {
+ javaArgs.add("-Xlog:cds=debug");
+ }
javaArgs.add("-jar");
List command;
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java
index 29d490c2cc658..48bd549b6edd6 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java
@@ -75,6 +75,7 @@
import io.quarkus.runtime.configuration.ConfigRecorder;
import io.quarkus.runtime.configuration.DefaultsConfigSource;
import io.quarkus.runtime.configuration.DisableableConfigSource;
+import io.quarkus.runtime.configuration.MappingsConfigBuilder;
import io.quarkus.runtime.configuration.QuarkusConfigValue;
import io.quarkus.runtime.configuration.RuntimeOverrideConfigSource;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;
@@ -91,10 +92,6 @@ public class ConfigGenerationBuildStep {
SmallRyeConfigBuilder.class, "withSources",
SmallRyeConfigBuilder.class, ConfigSource[].class);
- private static final MethodDescriptor WITH_MAPPING = MethodDescriptor.ofMethod(
- SmallRyeConfigBuilder.class, "withMapping",
- SmallRyeConfigBuilder.class, Class.class, String.class);
-
@BuildStep
void staticInitSources(
BuildProducer staticInitConfigSourceProviderBuildItem,
@@ -553,23 +550,25 @@ private static void generateMappingsConfigBuilder(
.classOutput(new GeneratedClassGizmoAdaptor(generatedClass, true))
.className(className)
.interfaces(ConfigBuilder.class)
+ .superClass(MappingsConfigBuilder.class)
.setFinal(true)
.build()) {
MethodCreator method = classCreator.getMethodCreator(CONFIG_BUILDER);
ResultHandle configBuilder = method.getMethodParam(0);
+ MethodDescriptor addMapping = MethodDescriptor.ofMethod(MappingsConfigBuilder.class, "addMapping", void.class,
+ SmallRyeConfigBuilder.class, String.class, String.class);
+
for (ConfigClassWithPrefix mapping : mappings) {
- method.invokeVirtualMethod(WITH_MAPPING, configBuilder,
- method.loadClass(mapping.getKlass()),
+ method.invokeStaticMethod(addMapping, configBuilder, method.load(mapping.getKlass().getName()),
method.load(mapping.getPrefix()));
}
method.returnValue(configBuilder);
}
- reflectiveClass
- .produce(ReflectiveClassBuildItem.builder(className).build());
+ reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).build());
}
private static Set discoverService(
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java
index 26035efd27f36..0e5c0643f18ea 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemFinder.java
@@ -159,8 +159,6 @@ private List recursivelyFindConfigItems(Element element, String r
String name = null;
String defaultValue = NO_DEFAULT;
String defaultValueDoc = EMPTY;
- final TypeMirror typeMirror = unwrapTypeMirror(enclosedElement.asType());
- String type = typeMirror.toString();
List acceptedValues = null;
final TypeElement clazz = (TypeElement) element;
final String fieldName = enclosedElement.getSimpleName().toString();
@@ -250,6 +248,9 @@ private List recursivelyFindConfigItems(Element element, String r
defaultValue = EMPTY;
}
+ TypeMirror typeMirror = unwrapTypeMirror(enclosedElement.asType());
+ String type = getType(typeMirror);
+
if (isConfigGroup(type)) {
List groupConfigItems = readConfigGroupItems(configPhase, rootName, name, type,
configSection, withinAMap, generateSeparateConfigGroupDocsFiles);
@@ -387,6 +388,15 @@ private TypeMirror unwrapTypeMirror(TypeMirror typeMirror) {
return typeMirror;
}
+ private String getType(TypeMirror typeMirror) {
+ if (typeMirror instanceof DeclaredType) {
+ DeclaredType declaredType = (DeclaredType) typeMirror;
+ TypeElement typeElement = (TypeElement) declaredType.asElement();
+ return typeElement.getQualifiedName().toString();
+ }
+ return typeMirror.toString();
+ }
+
private boolean isConfigGroup(String type) {
if (type.startsWith("java.") || PRIMITIVE_TYPES.contains(type)) {
return false;
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java
index 7169fceea750e..649a612ed8433 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java
@@ -244,15 +244,6 @@ public static void addSourceFactoryProvider(SmallRyeConfigBuilder builder, Confi
builder.withSources(provider.getConfigSourceFactory(Thread.currentThread().getContextClassLoader()));
}
- public static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) {
- ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
-
public static List getProfiles() {
return ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getProfiles();
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java
new file mode 100644
index 0000000000000..24d1b607074be
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MappingsConfigBuilder.java
@@ -0,0 +1,18 @@
+package io.quarkus.runtime.configuration;
+
+import io.smallrye.config.SmallRyeConfigBuilder;
+
+/**
+ * To support mappings that are not public
+ */
+public abstract class MappingsConfigBuilder implements ConfigBuilder {
+ protected static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) {
+ // TODO - Ideally should use the classloader passed to Config, but the method is not public. Requires a change in SmallRye Config.
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java
index 5a095f3d15ddf..752efe3c9fcfa 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/init/InitializationTaskRecorder.java
@@ -1,10 +1,14 @@
package io.quarkus.runtime.init;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.stream.StreamSupport;
import org.eclipse.microprofile.config.ConfigProvider;
import io.quarkus.runtime.PreventFurtherStepsException;
+import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.Recorder;
/**
@@ -22,8 +26,27 @@ public void exitIfNeeded() {
.anyMatch(n -> QUARKUS_INIT_AND_EXIT.equals(n));
if (initAndExitConfigured) {
if (ConfigProvider.getConfig().getValue(QUARKUS_INIT_AND_EXIT, boolean.class)) {
- throw new PreventFurtherStepsException("Gracefully exiting after initalization.", 0);
+ preventFurtherRecorderSteps(5, "Error attempting to gracefully shutdown after initialization",
+ () -> new PreventFurtherStepsException("Gracefully exiting after initialization.", 0));
}
}
}
+
+ public static void preventFurtherRecorderSteps(int waitSeconds, String waitErrorMessage,
+ Supplier supplier) {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Quarkus.blockingExit();
+ latch.countDown();
+ }
+ }).start();
+ try {
+ latch.await(waitSeconds, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ System.err.println(waitErrorMessage);
+ }
+ throw supplier.get();
+ }
}
diff --git a/docs/src/main/asciidoc/config-extending-support.adoc b/docs/src/main/asciidoc/config-extending-support.adoc
index ae4f9035d3586..80a84b2fa5aec 100644
--- a/docs/src/main/asciidoc/config-extending-support.adoc
+++ b/docs/src/main/asciidoc/config-extending-support.adoc
@@ -368,6 +368,8 @@ The `ConfigSourceInterceptorFactory` may initialize an interceptor with access t
----
package org.acme.config;
+import static io.smallrye.config.SecretKeys.doLocked;
+
import jakarta.annotation.Priority;
import io.smallrye.config.ConfigSourceInterceptor;
diff --git a/docs/src/main/asciidoc/doc-reference.adoc b/docs/src/main/asciidoc/doc-reference.adoc
index 907359f0952c6..0c8774698d302 100644
--- a/docs/src/main/asciidoc/doc-reference.adoc
+++ b/docs/src/main/asciidoc/doc-reference.adoc
@@ -57,7 +57,7 @@ Your titles and headings must also follow the specific guidance for the Quarkus
.Title guidance for different Quarkus content types
[cols="15%,25%a,30%,30%"]
|===
-|Content type |Should ... |Good example|Bad example
+|Content type |Should ... |Good example |Bad example
|Concept
|* Start with a noun that names the concept or topic
diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
index 437e49d356470..f554c6e4fb5a7 100644
--- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
+++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc
@@ -622,7 +622,7 @@ For example, with Apicurio dev service if you set the image name to use version
[source,properties]
----
-quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.4.2.Final
+quarkus.apicurio-registry.devservices.image-name=quay.io/apicurio/apicurio-registry-mem:2.1.5.Final
----
You need to make sure that `apicurio-registry-serdes-avro-serde` dependency
@@ -645,6 +645,16 @@ and the REST client `apicurio-common-rest-client-vertx` dependency are set to co
+
+ io.apicurio
+ apicurio-registry-client
+ 2.1.5.Final
+
+
+ io.apicurio
+ apicurio-registry-common
+ 2.1.5.Final
+
io.apicurio
apicurio-registry-serdes-avro-serde
@@ -654,6 +664,14 @@ and the REST client `apicurio-common-rest-client-vertx` dependency are set to co
io.apicurio
apicurio-common-rest-client-jdk
+
+ io.apicurio
+ apicurio-registry-client
+
+
+ io.apicurio
+ apicurio-registry-common
+
@@ -674,6 +692,18 @@ dependencies {
implementation("io.quarkus:quarkus-apicurio-registry-avro")
implementation("io.apicurio:apicurio-registry-serdes-avro-serde") {
exclude group: "io.apicurio", module: "apicurio-common-rest-client-jdk"
+ exclude group: "io.apicurio", module: "apicurio-registry-client"
+ exclude group: "io.apicurio", module: "apicurio-registry-common"
+ version {
+ strictly "2.1.5.Final"
+ }
+ }
+ implementation("io.apicurio:apicurio-registry-client") {
+ version {
+ strictly "2.1.5.Final"
+ }
+ }
+ implementation("io.apicurio:apicurio-registry-common") {
version {
strictly "2.1.5.Final"
}
@@ -686,6 +716,11 @@ dependencies {
}
----
+Known previous compatible versions for `apicurio-registry-client` and `apicurio-common-rest-client-vertx` are the following
+
+- `apicurio-registry-client` 2.1.5.Final with `apicurio-common-rest-client-vertx` 0.1.5.Final
+- `apicurio-registry-client` 2.3.1.Final with `apicurio-common-rest-client-vertx` 0.1.13.Final
+
[[confluent]]
== Using the Confluent Schema Registry
diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc
index 2bf957cecf485..c9821898e0afb 100644
--- a/docs/src/main/asciidoc/mongodb-panache.adoc
+++ b/docs/src/main/asciidoc/mongodb-panache.adoc
@@ -622,7 +622,7 @@ For these operations, you can express the update document the same way you expre
- `firstname = ?1 and status = ?2` will be mapped to the update document `{'$set' : {'firstname': ?1, 'status': ?2}}`
- `firstname = :firstname and status = :status` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}`
- `{'firstname' : ?1 and 'status' : ?2}` will be mapped to the update document `{'$set' : {'firstname': ?1, 'status': ?2}}`
-- `{'firstname' : :firstname and 'status' : :status}` ` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}`
+- `{'firstname' : :firstname and 'status' : :status}` will be mapped to the update document `{'$set' : {'firstname': :firstname, 'status': :status}}`
- `{'$inc': {'cpt': ?1}}` will be used as-is
=== Query parameters
diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc
index 791aea44d13e4..93ad61a3840cd 100644
--- a/docs/src/main/asciidoc/qute-reference.adoc
+++ b/docs/src/main/asciidoc/qute-reference.adoc
@@ -1665,7 +1665,7 @@ On the other hand, if the value is set (e.g. via `TemplateInstance.data("foo", "
The type of a default value must be assignable to the type of the parameter declaration. For example, see the incorrect parameter declaration that results in a build failure: `{@org.acme.Foo foo=1}`.
TIP: The default value is actually an <>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`.
-However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}``.
+However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}`.
IMPORTANT: The type of a default value is not validated in <>.
diff --git a/docs/src/main/asciidoc/redis-reference.adoc b/docs/src/main/asciidoc/redis-reference.adoc
index a9e3d1e882229..55fba59026da1 100644
--- a/docs/src/main/asciidoc/redis-reference.adoc
+++ b/docs/src/main/asciidoc/redis-reference.adoc
@@ -730,7 +730,7 @@ This behavior is disabled in _prod_ mode, and if you want to import even in prod
%prod.quarkus.redis.load-script=import.redis
----
-Before importing in _prod_ mode, mae sure you configured `quarkus.redis.flush-before-load` accordingly.
+Before importing in _prod_ mode, make sure you configured `quarkus.redis.flush-before-load` accordingly.
IMPORTANT: In dev mode, to reload the content of the `.redis` load scripts, you need to add: `%dev.quarkus.vertx.caching=false`
diff --git a/docs/src/main/asciidoc/rest-client-multipart.adoc b/docs/src/main/asciidoc/rest-client-multipart.adoc
index f952569434078..e4d04f8784e7b 100644
--- a/docs/src/main/asciidoc/rest-client-multipart.adoc
+++ b/docs/src/main/asciidoc/rest-client-multipart.adoc
@@ -153,7 +153,7 @@ The name of the property needs to follow a certain convention which is best disp
quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8080/
----
-Having this configuration means that all requests performed using `org.acme.rest.client.multipart.MultipartService` will use `http://localhost:8080/ ` as the base URL.
+Having this configuration means that all requests performed using `org.acme.rest.client.multipart.MultipartService` will use `http://localhost:8080/` as the base URL.
Note that `org.acme.rest.client.multipart.MultipartService` _must_ match the fully qualified name of the `MultipartService` interface we created in the previous section.
diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc
index b0f6dded8e37e..531742043a161 100644
--- a/docs/src/main/asciidoc/rest-client-reactive.adoc
+++ b/docs/src/main/asciidoc/rest-client-reactive.adoc
@@ -985,6 +985,68 @@ Naturally this handling is per REST Client. `@ClientExceptionMapper` uses the de
NOTE: Methods annotated with `@ClientExceptionMapper` can also take a `java.lang.reflect.Method` parameter which is useful if the exception mapping code needs to know the REST Client method that was invoked and caused the exception mapping code to engage.
+=== Using @Blocking annotation in exception mappers
+
+In cases that warrant using `InputStream` as the return type of REST Client method (such as when large amounts of data need to be read):
+
+[source, java]
+----
+@Path("/echo")
+@RegisterRestClient
+public interface EchoClient {
+
+ @GET
+ InputStream get();
+}
+----
+
+This will work as expected, but if you try to read this InputStream object in a custom exception mapper, you will receive a `BlockingNotAllowedException` exception. This is because `ResponseExceptionMapper` classes are run on the Event Loop thread executor by default - which does not allow to perform IO operations.
+
+To make your exception mapper blocking, you can annotate the exception mapper with the `@Blocking` annotation:
+
+[source, java]
+----
+@Provider
+@Blocking <1>
+public class MyResponseExceptionMapper implements ResponseExceptionMapper {
+
+ @Override
+ public RuntimeException toThrowable(Response response) {
+ if (response.getStatus() == 500) {
+ response.readEntity(String.class); <2>
+ return new RuntimeException("The remote service responded with HTTP 500");
+ }
+ return null;
+ }
+}
+----
+
+<1> With the `@Blocking` annotation, the MyResponseExceptionMapper exception mapper will be executed in the worker thread pool.
+<2> Reading the entity is now allowed because we're executing the mapper on the worker thread pool.
+
+Note that you can also use the `@Blocking` annotation when using @ClientExceptionMapper:
+
+[source, java]
+----
+@Path("/echo")
+@RegisterRestClient
+public interface EchoClient {
+
+ @GET
+ InputStream get();
+
+ @ClientExceptionMapper
+ @Blocking
+ static RuntimeException toException(Response response) {
+ if (response.getStatus() == 500) {
+ response.readEntity(String.class);
+ return new RuntimeException("The remote service responded with HTTP 500");
+ }
+ return null;
+ }
+}
+----
+
[[multipart]]
== Multipart Form support
diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc
index c61f195a26d37..8b11df8722087 100644
--- a/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc
+++ b/docs/src/main/asciidoc/security-authentication-mechanisms-concept.adoc
@@ -195,11 +195,11 @@ For more information about OIDC authentication and authorization methods you can
[options="header"]
|====
|OIDC topic |Quarkus information resource
-|Bearer token authentication mechanism|xref:security-oidc-bearer-authentication-concept.adoc[OIDC Bearer authentication]
-|Authorization code flow authentication mechanism|xref:security-oidc-code-flow-authentication-concept.adoc[OpenID Connect (OIDC) authorization code flow mechanism]
-|Multiple tenants that can support bearer token or authorization code flow mechanisms|xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect (OIDC) multi-tenancy]
-|Using Keycloak to centralize authorization|xref:security-keycloak-authorization.adoc[Using OpenID Connect (OIDC) and Keycloak to centralize authorization]
-|Configuring Keycloak programmatically|xref:security-keycloak-admin-client.adoc[Using the Keycloak admin client]
+|Bearer token authentication mechanism |xref:security-oidc-bearer-authentication-concept.adoc[OIDC Bearer authentication]
+|Authorization code flow authentication mechanism |xref:security-oidc-code-flow-authentication-concept.adoc[OpenID Connect (OIDC) authorization code flow mechanism]
+|Multiple tenants that can support bearer token or authorization code flow mechanisms |xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect (OIDC) multi-tenancy]
+|Using Keycloak to centralize authorization |xref:security-keycloak-authorization.adoc[Using OpenID Connect (OIDC) and Keycloak to centralize authorization]
+|Configuring Keycloak programmatically |xref:security-keycloak-admin-client.adoc[Using the Keycloak admin client]
|====
[NOTE]
diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
index 626da1b83a4e4..06c36042845f8 100644
--- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
@@ -721,8 +721,8 @@ quarkus.oidc.client.tls.key-store-password=${key-store-password}
#quarkus.oidc.client.tls.key-store-alias-password=keyAliasPassword
# Truststore configuration
-quarkus.oidc.client.tls.trust-store-file=client-truststore.jks
-quarkus.oidc.client.tls.trust-store-password=${trust-store-password}
+quarkus.oidc-client.tls.trust-store-file=client-truststore.jks
+quarkus.oidc-client.tls.trust-store-password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.oidc.client.tls.trust-store-alias=certAlias
----
diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc
index e3f4fba9d2475..a2f21633539e0 100644
--- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc
@@ -211,7 +211,7 @@ quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated
----
-The first configuration is the default tenant configuration that should be used when the tenant can not be inferred from the request. Note that a `%prod` prodile prefix is used with `quarkus.oidc.auth-server-url` - it is done to support testing a multi-tenant application with `Dev Services For Keycloak`. This configuration is using a Keycloak instance to authenticate users.
+The first configuration is the default tenant configuration that should be used when the tenant can not be inferred from the request. Note that a `%prod` profile prefix is used with `quarkus.oidc.auth-server-url` - it is done to support testing a multi-tenant application with `Dev Services For Keycloak`. This configuration is using a Keycloak instance to authenticate users.
The second configuration is provided by `TenantConfigResolver`, it is the configuration that will be used when an incoming request is mapped to the tenant `tenant-a`.
diff --git a/docs/src/main/asciidoc/tests-with-coverage.adoc b/docs/src/main/asciidoc/tests-with-coverage.adoc
index 5c6e46bf6f38b..0cda4d5b29bf3 100644
--- a/docs/src/main/asciidoc/tests-with-coverage.adoc
+++ b/docs/src/main/asciidoc/tests-with-coverage.adoc
@@ -179,6 +179,11 @@ There are some config options that affect this:
include::{generated-dir}/config/quarkus-jacoco-jacoco-config.adoc[opts=optional, leveloffset=+1]
+[TIP]
+====
+When working with a multi-module project, then for code coverage to work properly, the upstream modules need to be properly xref:cdi-reference.adoc#bean_discovery[indexed].
+====
+
== Coverage for tests not using @QuarkusTest
The Quarkus automatic JaCoCo config will only work for tests that are annotated with `@QuarkusTest`. If you want to check
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java
index b9d0770a10f58..abfd5c917a9c6 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/InterceptorResolverBuildItem.java
@@ -1,9 +1,11 @@
package io.quarkus.arc.deployment;
+import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
+import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
@@ -17,13 +19,14 @@
public final class InterceptorResolverBuildItem extends SimpleBuildItem {
private final InterceptorResolver resolver;
-
private final Set interceptorBindings;
+ private final BeanDeployment beanDeployment;
InterceptorResolverBuildItem(BeanDeployment beanDeployment) {
this.resolver = beanDeployment.getInterceptorResolver();
this.interceptorBindings = Collections.unmodifiableSet(
- beanDeployment.getInterceptorBindings().stream().map(ClassInfo::name).collect(Collectors.toSet()));
+ beanDeployment.getInterceptorBindings().stream().map(ClassInfo::name).collect(Collectors.toUnmodifiableSet()));
+ this.beanDeployment = beanDeployment;
}
public InterceptorResolver get() {
@@ -38,4 +41,14 @@ public Set getInterceptorBindings() {
return interceptorBindings;
}
+ /**
+ *
+ * @param annotation
+ * @return the collection of interceptor bindings
+ * @see BeanDeployment#extractInterceptorBindings(AnnotationInstance)
+ */
+ public Collection extractInterceptorBindings(AnnotationInstance annotation) {
+ return beanDeployment.extractInterceptorBindings(annotation);
+ }
+
}
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java
index df0b3ce4ce5b9..53d1a2b115c78 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java
@@ -10,9 +10,6 @@
import java.util.Set;
import java.util.stream.Collectors;
-import org.jboss.jandex.ClassInfo;
-import org.jboss.jandex.DotName;
-
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.ArcConfig;
@@ -22,7 +19,6 @@
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.devconsole.DependencyGraph.Link;
import io.quarkus.arc.deployment.devui.ArcBeanInfoBuildItem;
-import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BeanDeploymentValidator;
import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext;
import io.quarkus.arc.processor.BeanInfo;
@@ -35,10 +31,7 @@
import io.quarkus.arc.runtime.ArcContainerSupplier;
import io.quarkus.arc.runtime.ArcRecorder;
import io.quarkus.arc.runtime.BeanLookupSupplier;
-import io.quarkus.arc.runtime.devconsole.InvocationInterceptor;
-import io.quarkus.arc.runtime.devconsole.InvocationTree;
import io.quarkus.arc.runtime.devconsole.InvocationsMonitor;
-import io.quarkus.arc.runtime.devconsole.Monitored;
import io.quarkus.arc.runtime.devmode.EventsMonitor;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -71,37 +64,9 @@ void monitor(ArcConfig config, BuildProducer skipNames = new HashSet<>();
- skipNames.add(DotName.createSimple(InvocationTree.class.getName()));
- skipNames.add(DotName.createSimple(InvocationsMonitor.class.getName()));
- skipNames.add(DotName.createSimple(EventsMonitor.class.getName()));
- annotationTransformers.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
- @Override
- public void transform(TransformationContext transformationContext) {
- if (transformationContext.isClass()) {
- ClassInfo beanClass = transformationContext.getTarget().asClass();
- if ((customScopes.isScopeDeclaredOn(beanClass)
- || isAdditionalBeanDefiningAnnotationOn(beanClass, beanDefiningAnnotations))
- && !skipNames.contains(beanClass.name())) {
- transformationContext.transform().add(Monitored.class).done();
- }
- }
- }
- }));
runtimeInfos.produce(new DevConsoleRuntimeTemplateInfoBuildItem("invocationsMonitor",
new BeanLookupSupplier(InvocationsMonitor.class), this.getClass(), curateOutcomeBuildItem));
}
@@ -226,16 +191,6 @@ public void handle(Void ignore) {
static final int DEFAULT_MAX_DEPENDENCY_LEVEL = 10;
- private boolean isAdditionalBeanDefiningAnnotationOn(ClassInfo beanClass,
- List beanDefiningAnnotations) {
- for (BeanDefiningAnnotationBuildItem beanDefiningAnnotation : beanDefiningAnnotations) {
- if (beanClass.classAnnotation(beanDefiningAnnotation.getName()) != null) {
- return true;
- }
- }
- return false;
- }
-
DependencyGraph buildDependencyGraph(BeanInfo bean, ValidationContext validationContext, BeanResolver resolver,
DevBeanInfos devBeanInfos, List allInjectionPoints,
Map> declaringToProducers,
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java
index c76dac830e220..0aeb3f4ab27e2 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devui/ArcDevUIProcessor.java
@@ -2,15 +2,30 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.ArcConfig;
+import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
+import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
import io.quarkus.arc.deployment.devconsole.DevBeanInfo;
import io.quarkus.arc.deployment.devconsole.DevBeanInfos;
import io.quarkus.arc.deployment.devconsole.DevDecoratorInfo;
import io.quarkus.arc.deployment.devconsole.DevInterceptorInfo;
import io.quarkus.arc.deployment.devconsole.DevObserverInfo;
+import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.arc.runtime.devconsole.InvocationInterceptor;
+import io.quarkus.arc.runtime.devconsole.InvocationTree;
+import io.quarkus.arc.runtime.devconsole.InvocationsMonitor;
+import io.quarkus.arc.runtime.devconsole.Monitored;
+import io.quarkus.arc.runtime.devmode.EventsMonitor;
import io.quarkus.arc.runtime.devui.ArcJsonRPCService;
import io.quarkus.deployment.IsDevelopment;
+import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
@@ -94,6 +109,46 @@ JsonRPCProvidersBuildItem createJsonRPCService() {
return new JsonRPCProvidersBuildItem(ArcJsonRPCService.class);
}
+ @BuildStep(onlyIf = IsDevelopment.class)
+ void registerMonitoringComponents(ArcConfig config, BuildProducer beans,
+ BuildProducer annotationTransformers,
+ CustomScopeAnnotationsBuildItem customScopes, List beanDefiningAnnotations) {
+ if (!config.devMode.monitoringEnabled) {
+ return;
+ }
+ if (!config.transformUnproxyableClasses) {
+ throw new IllegalStateException(
+ "Dev UI problem: monitoring of CDI business method invocations not possible\n\t- quarkus.arc.transform-unproxyable-classes was set to false and therefore it would not be possible to apply interceptors to unproxyable bean classes\n\t- please disable the monitoring feature via quarkus.arc.dev-mode.monitoring-enabled=false or enable unproxyable classes transformation");
+ }
+ // Register beans
+ beans.produce(AdditionalBeanBuildItem.builder().setUnremovable()
+ .addBeanClasses(EventsMonitor.class, InvocationTree.class, InvocationsMonitor.class,
+ InvocationInterceptor.class,
+ Monitored.class)
+ .build());
+
+ // Add @Monitored to all beans
+ Set skipNames = Set.of(DotName.createSimple(InvocationTree.class),
+ DotName.createSimple(InvocationsMonitor.class), DotName.createSimple(EventsMonitor.class));
+ annotationTransformers.produce(new AnnotationsTransformerBuildItem(AnnotationsTransformer
+ .appliedToClass()
+ .whenClass(c -> (customScopes.isScopeDeclaredOn(c)
+ || isAdditionalBeanDefiningAnnotationOn(c, beanDefiningAnnotations))
+ && !skipClass(c, skipNames))
+ .thenTransform(t -> t.add(Monitored.class))));
+ }
+
+ private boolean skipClass(ClassInfo beanClass, Set skipNames) {
+ if (skipNames.contains(beanClass.name())) {
+ return true;
+ }
+ if (beanClass.name().packagePrefix().startsWith("io.quarkus.devui.runtime")) {
+ // Skip monitoring for internal devui components
+ return true;
+ }
+ return false;
+ }
+
private List toDevBeanWithInterceptorInfo(List beans, DevBeanInfos devBeanInfos) {
List l = new ArrayList<>();
for (DevBeanInfo dbi : beans) {
@@ -102,6 +157,16 @@ private List toDevBeanWithInterceptorInfo(List beanDefiningAnnotations) {
+ for (BeanDefiningAnnotationBuildItem beanDefiningAnnotation : beanDefiningAnnotations) {
+ if (beanClass.hasDeclaredAnnotation(beanDefiningAnnotation.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static final String BEANS = "beans";
private static final String OBSERVERS = "observers";
private static final String INTERCEPTORS = "interceptors";
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
index 568036a05db26..6e5f32ac07e65 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java
@@ -87,9 +87,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
BuildProducer interceptedStaticMethods,
InterceptorResolverBuildItem interceptorResolver, TransformedAnnotationsBuildItem transformedAnnotations,
BuildProducer unremovableBeans) {
-
// In this step we collect all intercepted static methods, i.e. static methods annotated with interceptor bindings
- Set interceptorBindings = interceptorResolver.getInterceptorBindings();
for (ClassInfo clazz : beanArchiveIndex.getIndex().getKnownClasses()) {
for (MethodInfo method : clazz.methods()) {
@@ -107,12 +105,15 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
// Only method-level bindings are considered due to backwards compatibility
Set methodLevelBindings = null;
for (AnnotationInstance annotationInstance : annotations) {
- if (annotationInstance.target().kind() == Kind.METHOD
- && interceptorBindings.contains(annotationInstance.name())) {
- if (methodLevelBindings == null) {
- methodLevelBindings = new HashSet<>();
+ if (annotationInstance.target().kind() == Kind.METHOD) {
+ Collection bindings = interceptorResolver
+ .extractInterceptorBindings(annotationInstance);
+ if (!bindings.isEmpty()) {
+ if (methodLevelBindings == null) {
+ methodLevelBindings = new HashSet<>();
+ }
+ methodLevelBindings.addAll(bindings);
}
- methodLevelBindings.add(annotationInstance);
}
}
if (methodLevelBindings == null || methodLevelBindings.isEmpty()) {
diff --git a/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html b/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html
index 320bf44699109..69f7a8c6190c4 100644
--- a/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html
+++ b/extensions/arc/deployment/src/main/resources/dev-templates/invocations.html
@@ -79,7 +79,7 @@
- {#each info:invocationsMonitor.lastInvocations.orEmpty}
+ {#each info:invocationsMonitor.filteredLastInvocations.orEmpty}
{it.startFormatted}
diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js
index 8185976e9a23b..64fdf4ccd1328 100644
--- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js
+++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-fired-events.js
@@ -3,7 +3,6 @@ import { until } from 'lit/directives/until.js';
import { JsonRpc } from 'jsonrpc';
import '@vaadin/grid';
import { columnBodyRenderer } from '@vaadin/grid/lit.js';
-import '@vaadin/details';
import '@vaadin/vertical-layout';
import '@vaadin/button';
import '@vaadin/checkbox';
@@ -28,27 +27,29 @@ export class QwcArcFiredEvents extends LitElement {
.arctable {
height: 100%;
padding-bottom: 10px;
- }
- .payload {
- color: grey;
- font-size: small;
}`;
static properties = {
_firedEvents: {state: true},
- _observer: {state:false},
+ _skipLifecycleEvents: {state: true}
};
connectedCallback() {
super.connectedCallback();
this._refresh();
- this._observer = this.jsonRpc.streamEvents().onNext(jsonRpcResponse => {
+ this._eventsStream = this.jsonRpc.streamEvents().onNext(jsonRpcResponse => {
this._addToEvents(jsonRpcResponse.result);
});
+ // Context lifecycle events are skipped by default; updates are handled by the stream
+ this._skipLifecycleEvents = true;
+ this._skipLifecycleEventsStream = this.jsonRpc.streamSkipContextEvents().onNext(jsonRpcResponse => {
+ this._skipLifecycleEvents = jsonRpcResponse.result;
+ });
}
disconnectedCallback() {
- this._observer.cancel();
+ this._eventsStream.cancel();
+ this._skipLifecycleEventsStream.cancel();
super.disconnectedCallback();
}
@@ -59,13 +60,13 @@ export class QwcArcFiredEvents extends LitElement {
_renderFiredEvents(){
if(this._firedEvents){
return html`
@@ -90,40 +91,34 @@ export class QwcArcFiredEvents extends LitElement {
}
}
- _payloadRenderer(event) {
+ _eventTypeRenderer(event) {
return html`
-
- ${event.type}
-
-
- ${event.payload}
-
-
+ ${event.type}
`;
}
_refresh(){
- console.log("refresh");
this.jsonRpc.getLastEvents().then(events => {
this._firedEvents = events.result;
});
}
_clear(){
- console.log("clear");
this.jsonRpc.clearLastEvents().then(events => {
this._firedEvents = events.result;
});
}
_toggleContext(){
- console.log("context");
+ this.jsonRpc.toggleSkipContextEvents().then(events => {
+ this._firedEvents = events.result;
+ });
}
_addToEvents(event){
this._firedEvents = [
- ...this._firedEvents,
event,
+ ...this._firedEvents,
];
}
}
diff --git a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js
index 0e3e18d482aca..1a5dc198f04ea 100644
--- a/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js
+++ b/extensions/arc/deployment/src/main/resources/dev-ui/qwc-arc-invocation-trees.js
@@ -1,8 +1,8 @@
import { LitElement, html, css} from 'lit';
import { until } from 'lit/directives/until.js';
import { JsonRpc } from 'jsonrpc';
+import { columnBodyRenderer } from '@vaadin/grid/lit.js';
import '@vaadin/grid';
-import '@vaadin/grid/vaadin-grid-tree-column.js';
import '@vaadin/button';
import '@vaadin/checkbox';
@@ -26,14 +26,26 @@ export class QwcArcInvocationTrees extends LitElement {
.arctable {
height: 100%;
padding-bottom: 10px;
- }`;
+ }
+ ul li::before {
+ content: "└─ ";
+ }
+ ul {
+ list-style: none;
+ }
+ code {
+ font-size: 90%;
+ }
+ `;
static properties = {
- _invocations: {state: true}
+ _invocations: {state: true},
+ _filterOutQuarkusBeans: {state: true}
};
connectedCallback() {
super.connectedCallback();
+ this._filterOutQuarkusBeans = true;
this._refresh();
}
@@ -44,13 +56,13 @@ export class QwcArcInvocationTrees extends LitElement {
_renderInvocations(){
if(this._invocations){
return html`
-
+
+
`;
}
}
+ _invocationsRenderer(invocation) {
+ return html`
+
+ ${this._invocationRenderer(invocation)}
+
+ `;
+ }
+
+ _invocationRenderer(invocation) {
+ return html`
+
+ ${invocation.kind.toLowerCase()}
+ ${invocation.methodName}
+ ${invocation.duration == 0 ? '< 1' : invocation.duration} ms
+
+ ${invocation.children.map(child =>
+ html`${this._invocationRenderer(child)}`
+ )}
+
+
+ `;
+ }
+
_refresh(){
- console.log("refresh");
this.jsonRpc.getLastInvocations().then(invocations => {
- this._invocations = invocations.result;
+ if (this._filterOutQuarkusBeans) {
+ this._invocations = invocations.result.filter(i => !i.quarkusBean);
+ } else {
+ this._invocations = invocations.result;
+ }
});
}
_clear(){
- console.log("clear");
this.jsonRpc.clearLastInvocations().then(invocations => {
this._invocations = invocations.result;
});
}
_toggleFilter(){
- console.log("filter");
+ this._filterOutQuarkusBeans = !this._filterOutQuarkusBeans;
+ this._refresh();
}
}
customElements.define('qwc-arc-invocation-trees', QwcArcInvocationTrees);
\ No newline at end of file
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java
new file mode 100644
index 0000000000000..54219d0cf4a2e
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/RepeatingBindingStaticMethodTest.java
@@ -0,0 +1,85 @@
+package io.quarkus.arc.test.interceptor.staticmethods;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import jakarta.annotation.Priority;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InterceptorBinding;
+import jakarta.interceptor.InvocationContext;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class RepeatingBindingStaticMethodTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(root -> root
+ .addClasses(InterceptMe.class, SimpleBean.class, InterceptMeAlpha.class, InterceptMeBravo.class));
+
+ @Test
+ public void testInterceptor() {
+ assertEquals("a/b/PONG/b/a", SimpleBean.ping("pong"));
+ }
+
+ public static class SimpleBean {
+
+ @InterceptMe("alpha")
+ @InterceptMe("bravo")
+ public static String ping(String val) {
+ return val.toUpperCase();
+ }
+ }
+
+ @Priority(1)
+ @Interceptor
+ @InterceptMe("alpha")
+ static class InterceptMeAlpha {
+
+ @AroundInvoke
+ Object aroundInvoke(InvocationContext ctx) throws Exception {
+ return "a/" + ctx.proceed() + "/a";
+ }
+
+ }
+
+ @Priority(2)
+ @Interceptor
+ @InterceptMe("bravo")
+ static class InterceptMeBravo {
+
+ @AroundInvoke
+ Object aroundInvoke(InvocationContext ctx) throws Exception {
+ return "b/" + ctx.proceed() + "/b";
+ }
+
+ }
+
+ @Repeatable(InterceptMe.List.class)
+ @InterceptorBinding
+ @Target({ TYPE, METHOD, CONSTRUCTOR })
+ @Retention(RUNTIME)
+ @interface InterceptMe {
+
+ String value();
+
+ @Target({ TYPE, METHOD, CONSTRUCTOR })
+ @Retention(RUNTIME)
+ @interface List {
+ InterceptMe[] value();
+ }
+
+ }
+
+}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java
index ae93e5bed02f2..70e03db0d3545 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/appcds/AppCDSRecorder.java
@@ -1,31 +1,17 @@
package io.quarkus.arc.runtime.appcds;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
import io.quarkus.runtime.PreventFurtherStepsException;
-import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.Recorder;
+import io.quarkus.runtime.init.InitializationTaskRecorder;
@Recorder
public class AppCDSRecorder {
public void controlGenerationAndExit() {
if (Boolean.parseBoolean(System.getProperty("quarkus.appcds.generate", "false"))) {
- CountDownLatch latch = new CountDownLatch(1);
- new Thread(new Runnable() {
- @Override
- public void run() {
- Quarkus.blockingExit();
- latch.countDown();
- }
- }).start();
- try {
- latch.await(5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- System.err.println("Unable to properly shutdown Quarkus application when creating AppCDS");
- }
- throw new PreventFurtherStepsException();
+ InitializationTaskRecorder.preventFurtherRecorderSteps(5,
+ "Unable to properly shutdown Quarkus application when creating AppCDS",
+ PreventFurtherStepsException::new);
}
}
}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java
index 4ac219e710869..cd301510a341d 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/Invocation.java
@@ -94,6 +94,13 @@ public String getPackageName(String name) {
return "";
}
+ public boolean isQuarkusBean() {
+ if (interceptedBean == null) {
+ return false;
+ }
+ return interceptedBean.getBeanClass().getName().startsWith("io.quarkus");
+ }
+
public enum Kind {
BUSINESS,
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java
index 61f1189c55d8d..0f25f90f722f6 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devconsole/InvocationsMonitor.java
@@ -28,16 +28,22 @@ void addInvocation(Invocation invocation) {
invocations.add(invocation);
}
- public List getLastInvocations() {
- List result = new ArrayList<>(invocations);
+ // this method should be removed when the Dev UI 1 components are removed
+ public List getFilteredLastInvocations() {
+ List result = getLastInvocations();
if (filterOutQuarkusBeans) {
for (Iterator it = result.iterator(); it.hasNext();) {
Invocation invocation = it.next();
- if (invocation.getInterceptedBean().getBeanClass().getName().startsWith("io.quarkus")) {
+ if (invocation.isQuarkusBean()) {
it.remove();
}
}
}
+ return result;
+ }
+
+ public List getLastInvocations() {
+ List result = new ArrayList<>(invocations);
Collections.reverse(result);
return result;
}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java
index fde1f35d48f42..9ce8f7a265fc4 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventInfo.java
@@ -7,21 +7,15 @@ public class EventInfo {
private String type;
private List qualifiers;
private boolean isContextEvent;
- private Object payload;
public EventInfo() {
}
public EventInfo(String timestamp, String type, List qualifiers, boolean isContextEvent) {
- this(timestamp, type, qualifiers, isContextEvent, null);
- }
-
- public EventInfo(String timestamp, String type, List qualifiers, boolean isContextEvent, Object payload) {
this.timestamp = timestamp;
this.type = type;
this.qualifiers = qualifiers;
this.isContextEvent = isContextEvent;
- this.payload = payload;
}
public String getTimestamp() {
@@ -56,12 +50,4 @@ public void setIsContextEvent(boolean isContextEvent) {
this.isContextEvent = isContextEvent;
}
- public Object getPayload() {
- return payload;
- }
-
- public void setPayload(Object payload) {
- this.payload = payload;
- }
-
}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java
index 2d85c7db162fa..94a6ebb2d7819 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/EventsMonitor.java
@@ -28,6 +28,7 @@ public class EventsMonitor {
private volatile boolean skipContextEvents = true;
private final List events = Collections.synchronizedList(new ArrayList<>(DEFAULT_LIMIT));
private final BroadcastProcessor eventsStream = BroadcastProcessor.create();
+ private final BroadcastProcessor skipContextEventsStream = BroadcastProcessor.create();
void notify(@Observes Object payload, EventMetadata eventMetadata) {
if (skipContextEvents && isContextEvent(eventMetadata)) {
@@ -54,6 +55,10 @@ public Multi streamEvents() {
return eventsStream;
}
+ public Multi streamSkipContextEvents() {
+ return skipContextEventsStream;
+ }
+
public List getLastEvents() {
List result = new ArrayList<>(events);
Collections.reverse(result);
@@ -67,6 +72,7 @@ public boolean isSkipContextEvents() {
public void toggleSkipContextEvents() {
// This is not thread-safe but we don't expect concurrent actions from dev ui
skipContextEvents = !skipContextEvents;
+ skipContextEventsStream.onNext(skipContextEvents);
}
boolean isContextEvent(EventMetadata eventMetadata) {
@@ -87,14 +93,8 @@ boolean isContextEvent(EventMetadata eventMetadata) {
private EventInfo toEventInfo(Object payload, EventMetadata eventMetadata) {
EventInfo eventInfo = new EventInfo();
-
- // Timestamp
eventInfo.setTimestamp(now());
-
- // Type
eventInfo.setType(eventMetadata.getType().getTypeName());
-
- // Qualifiers
List q = new ArrayList<>();
if (eventMetadata.getQualifiers().size() > 1) {
for (Annotation qualifier : eventMetadata.getQualifiers()) {
@@ -105,13 +105,7 @@ private EventInfo toEventInfo(Object payload, EventMetadata eventMetadata) {
}
}
eventInfo.setQualifiers(q);
-
- // ContextEvent
eventInfo.setIsContextEvent(isContextEvent(eventMetadata));
-
- // Payload
- eventInfo.setPayload(payload.toString());
-
return eventInfo;
}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java
index e681fd05e0af3..acc98f37e7207 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devmode/InvocationInfo.java
@@ -1,9 +1,23 @@
package io.quarkus.arc.runtime.devmode;
+import java.util.List;
+
public class InvocationInfo {
private String startTime;
+ private String methodName;
+
+ // in milliseconds
+ private long duration;
+
+ // business method, producer, observer, etc.
+ private String kind;
+
+ private List children;
+
+ private boolean quarkusBean;
+
public String getStartTime() {
return startTime;
}
@@ -12,4 +26,44 @@ public void setStartTime(String startTime) {
this.startTime = startTime;
}
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public void setMethodName(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public long getDuration() {
+ return duration;
+ }
+
+ public void setDuration(long duration) {
+ this.duration = duration;
+ }
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public void setChildren(List children) {
+ this.children = children;
+ }
+
+ public boolean isQuarkusBean() {
+ return quarkusBean;
+ }
+
+ public void setQuarkusBean(boolean quarkusBean) {
+ this.quarkusBean = quarkusBean;
+ }
+
}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java
index 4b125b56a1872..996fc1f969dee 100644
--- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/devui/ArcJsonRPCService.java
@@ -7,7 +7,9 @@
import java.util.ArrayList;
import java.util.List;
-import io.quarkus.arc.Arc;
+import jakarta.enterprise.inject.Instance;
+import jakarta.inject.Inject;
+
import io.quarkus.arc.runtime.devconsole.Invocation;
import io.quarkus.arc.runtime.devconsole.InvocationsMonitor;
import io.quarkus.arc.runtime.devmode.EventInfo;
@@ -18,38 +20,50 @@
public class ArcJsonRPCService {
+ @Inject
+ Instance eventsMonitor;
+
+ @Inject
+ Instance invocationsMonitor;
+
public Multi streamEvents() {
- EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get();
- if (eventsMonitor != null) {
- return eventsMonitor.streamEvents();
- }
- return Multi.createFrom().empty();
+ return eventsMonitor.isResolvable() ? eventsMonitor.get().streamEvents() : Multi.createFrom().empty();
+ }
+
+ public Multi streamSkipContextEvents() {
+ return eventsMonitor.isResolvable() ? eventsMonitor.get().streamSkipContextEvents() : Multi.createFrom().empty();
}
@NonBlocking
public List getLastEvents() {
- EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get();
- if (eventsMonitor != null) {
- return eventsMonitor.getLastEvents();
+ if (eventsMonitor.isResolvable()) {
+ return eventsMonitor.get().getLastEvents();
}
return List.of();
}
@NonBlocking
public List clearLastEvents() {
- EventsMonitor eventsMonitor = Arc.container().instance(EventsMonitor.class).get();
- if (eventsMonitor != null) {
- eventsMonitor.clear();
- return eventsMonitor.getLastEvents();
+ if (eventsMonitor.isResolvable()) {
+ eventsMonitor.get().clear();
+ return getLastEvents();
+ }
+ return List.of();
+ }
+
+ @NonBlocking
+ public List toggleSkipContextEvents() {
+ if (eventsMonitor.isResolvable()) {
+ eventsMonitor.get().toggleSkipContextEvents();
+ return getLastEvents();
}
return List.of();
}
@NonBlocking
public List getLastInvocations() {
- InvocationsMonitor invocationsMonitor = Arc.container().instance(InvocationsMonitor.class).get();
- if (invocationsMonitor != null) {
- List lastInvocations = invocationsMonitor.getLastInvocations();
+ if (invocationsMonitor.isResolvable()) {
+ List lastInvocations = invocationsMonitor.get().getLastInvocations();
return toInvocationInfos(lastInvocations);
}
return List.of();
@@ -57,9 +71,8 @@ public List getLastInvocations() {
@NonBlocking
public List clearLastInvocations() {
- InvocationsMonitor invocationsMonitor = Arc.container().instance(InvocationsMonitor.class).get();
- if (invocationsMonitor != null) {
- invocationsMonitor.clear();
+ if (invocationsMonitor.isResolvable()) {
+ invocationsMonitor.get().clear();
return getLastInvocations();
}
return List.of();
@@ -75,8 +88,13 @@ private List toInvocationInfos(List invocations) {
private InvocationInfo toInvocationInfo(Invocation invocation) {
InvocationInfo info = new InvocationInfo();
- LocalDateTime starttime = LocalDateTime.ofInstant(Instant.ofEpochMilli(invocation.getStart()), ZoneId.systemDefault());
- info.setStartTime(timeString(starttime));
+ info.setStartTime(
+ timeString(LocalDateTime.ofInstant(Instant.ofEpochMilli(invocation.getStart()), ZoneId.systemDefault())));
+ info.setMethodName(invocation.getDeclaringClassName() + "#" + invocation.getMethod().getName());
+ info.setDuration(invocation.getDurationMillis());
+ info.setKind(invocation.getKind().toString());
+ info.setChildren(toInvocationInfos(invocation.getChildren()));
+ info.setQuarkusBean(invocation.isQuarkusBean());
return info;
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
index 489beb17221cb..b7b1f61120dfd 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
+++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java
@@ -16,7 +16,7 @@ void testImageWithJava11() {
Path path = getPath("openjdk-11-runtime");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14");
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15");
assertThat(v.getJavaVersion()).isEqualTo(11);
});
}
@@ -26,7 +26,7 @@ void testImageWithJava17() {
Path path = getPath("openjdk-17-runtime");
var result = sut.determine(path);
assertThat(result).hasValueSatisfying(v -> {
- assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14");
+ assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15");
assertThat(v.getJavaVersion()).isEqualTo(17);
});
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime
index 0cbac0dfb5547..242b681bff897 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime
@@ -1,4 +1,4 @@
-FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14
+FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
index 1238ab8318d4a..ae53314ed64b5 100644
--- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
+++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime
@@ -1,5 +1,5 @@
# Use Java 17 base image
-FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14
+FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
index c5a8fea44e41d..9b1ac89915b6c 100644
--- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
+++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
@@ -16,9 +16,9 @@ public class JibConfig {
/**
* The base image to be used when a container image is being produced for the jar build.
*
- * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14}
+ * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
index 6b14aa638525a..3bca893339175 100644
--- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
+++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
@@ -89,8 +89,8 @@ public class JibProcessor {
private static final IsClassPredicate IS_CLASS_PREDICATE = new IsClassPredicate();
private static final String BINARY_NAME_IN_CONTAINER = "application";
- private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14";
- private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.14";
+ private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15";
+ private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.15";
private static final String DEFAULT_BASE_IMAGE_USER = "185";
private static final String OPENTELEMETRY_CONTEXT_CONTEXT_STORAGE_PROVIDER_SYS_PROP = "io.opentelemetry.context.contextStorageProvider";
diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java
index 245c6bb941019..5739ded667a7c 100644
--- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java
+++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/OpenshiftConfig.java
@@ -15,8 +15,8 @@
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class OpenshiftConfig {
- public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.14";
- public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.14";
+ public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.15";
+ public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.15";
public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0";
public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application";
@@ -46,9 +46,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion
/**
* The base image to be used when a container image is being produced for the jar build.
*
- * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.14}
+ * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.15}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.14} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.15} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
index b01459912145c..098725815e833 100644
--- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
+++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java
@@ -12,8 +12,8 @@
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class S2iConfig {
- public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.14";
- public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.14";
+ public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.15";
+ public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.15";
public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0";
public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application";
@@ -41,9 +41,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion
/**
* The base image to be used when a container image is being produced for the jar build.
*
- * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.14}
+ * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.15}
* is used as the default.
- * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.14} is used as the default.
+ * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.15} is used as the default.
*/
@ConfigItem
public Optional baseJvmImage;
diff --git a/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java
new file mode 100644
index 0000000000000..38855b7bd4554
--- /dev/null
+++ b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_1__Update.java
@@ -0,0 +1,18 @@
+package db.migration;
+
+import java.sql.Statement;
+
+import org.flywaydb.core.api.migration.BaseJavaMigration;
+import org.flywaydb.core.api.migration.Context;
+
+/**
+ * Migration class for some testcases.
+ */
+public class V1_0_1__Update extends BaseJavaMigration {
+ @Override
+ public void migrate(Context context) throws Exception {
+ try (Statement statement = context.getConnection().createStatement()) {
+ statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')");
+ }
+ }
+}
diff --git a/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java
new file mode 100644
index 0000000000000..3181a849404d5
--- /dev/null
+++ b/extensions/flyway/deployment/src/test/java/db/migration/V1_0_2__Update.java
@@ -0,0 +1,39 @@
+package db.migration;
+
+import java.sql.Statement;
+
+import org.flywaydb.core.api.MigrationVersion;
+import org.flywaydb.core.api.migration.Context;
+import org.flywaydb.core.api.migration.JavaMigration;
+
+/**
+ * Migration class for some testcases.
+ */
+public class V1_0_2__Update implements JavaMigration {
+ @Override
+ public MigrationVersion getVersion() {
+ return MigrationVersion.fromVersion("1.0.2");
+ }
+
+ @Override
+ public String getDescription() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public Integer getChecksum() {
+ return null;
+ }
+
+ @Override
+ public boolean canExecuteInTransaction() {
+ return true;
+ }
+
+ @Override
+ public void migrate(Context context) throws Exception {
+ try (Statement statement = context.getConnection().createStatement()) {
+ statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')");
+ }
+ }
+}
diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java
index 8b1f25d96663c..6c4acbfcf162c 100644
--- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java
+++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest.java
@@ -12,13 +12,14 @@
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationVersion;
-import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.flywaydb.core.api.migration.JavaMigration;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import db.migration.V1_0_1__Update;
+import db.migration.V1_0_2__Update;
import io.agroal.api.AgroalDataSource;
import io.quarkus.test.QuarkusUnitTest;
@@ -33,7 +34,7 @@ public class FlywayExtensionCleanAndMigrateAtStartWithJavaMigrationTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addClasses(V1_0_1__Update.class, V1_0_2__Update.class)
+ .addClasses(V1_0_1__Update.class, V1_0_2__Update.class, V9_9_9__Update.class)
.addAsResource("db/migration/V1.0.0__Quarkus.sql")
.addAsResource("clean-and-migrate-at-start-config.properties", "application.properties"));
@@ -53,19 +54,10 @@ public void testFlywayConfigInjection() throws SQLException {
assertEquals("1.0.2", currentVersion, "Expected to be 1.0.2 as there is a SQL and two Java migration scripts");
}
- public static class V1_0_1__Update extends BaseJavaMigration {
- @Override
- public void migrate(Context context) throws Exception {
- try (Statement statement = context.getConnection().createStatement()) {
- statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')");
- }
- }
- }
-
- public static class V1_0_2__Update implements JavaMigration {
+ public static class V9_9_9__Update implements JavaMigration {
@Override
public MigrationVersion getVersion() {
- return MigrationVersion.fromVersion("1.0.2");
+ return MigrationVersion.fromVersion("9.9.9");
}
@Override
@@ -86,7 +78,7 @@ public boolean canExecuteInTransaction() {
@Override
public void migrate(Context context) throws Exception {
try (Statement statement = context.getConnection().createStatement()) {
- statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')");
+ statement.executeUpdate("INSERT INTO quarked_flyway VALUES (9999, 'should-not-be-added')");
}
}
}
diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java
index 7d33b3491df1f..b0ba671cf0e98 100644
--- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java
+++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFilesystemResourceTest.java
@@ -12,15 +12,13 @@
import jakarta.inject.Inject;
import org.flywaydb.core.Flyway;
-import org.flywaydb.core.api.MigrationVersion;
-import org.flywaydb.core.api.migration.BaseJavaMigration;
-import org.flywaydb.core.api.migration.Context;
-import org.flywaydb.core.api.migration.JavaMigration;
import org.h2.jdbc.JdbcSQLSyntaxErrorException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import db.migration.V1_0_1__Update;
+import db.migration.V1_0_2__Update;
import io.agroal.api.AgroalDataSource;
import io.quarkus.test.QuarkusUnitTest;
@@ -59,42 +57,4 @@ public void testFlywayConfigInjection() throws SQLException {
assertEquals("1.0.3", currentVersion, "Expected to be 1.0.3 as there is a SQL and two Java migration scripts");
}
- public static class V1_0_1__Update extends BaseJavaMigration {
- @Override
- public void migrate(Context context) throws Exception {
- try (Statement statement = context.getConnection().createStatement()) {
- statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1001, 'test')");
- }
- }
- }
-
- public static class V1_0_2__Update implements JavaMigration {
- @Override
- public MigrationVersion getVersion() {
- return MigrationVersion.fromVersion("1.0.2");
- }
-
- @Override
- public String getDescription() {
- return getClass().getSimpleName();
- }
-
- @Override
- public Integer getChecksum() {
- return null;
- }
-
- @Override
- public boolean canExecuteInTransaction() {
- return true;
- }
-
- @Override
- public void migrate(Context context) throws Exception {
- try (Statement statement = context.getConnection().createStatement()) {
- statement.executeUpdate("INSERT INTO quarked_flyway VALUES (1002, 'test')");
- }
- }
- }
-
}
diff --git a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties
index ef6fab20a9c65..26c07c53ca008 100644
--- a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties
+++ b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties
@@ -10,3 +10,4 @@ quarkus.flyway.table=test_flyway_history
quarkus.flyway.baseline-on-migrate=false
quarkus.flyway.baseline-version=0.0.1
quarkus.flyway.baseline-description=Initial description for test
+quarkus.flyway.users.locations=db/migration
\ No newline at end of file
diff --git a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties
index 352ca17e521d9..65afb9365aa52 100644
--- a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties
+++ b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-with-fs-resource-config.properties
@@ -10,4 +10,4 @@ quarkus.flyway.table=test_flyway_history
quarkus.flyway.baseline-on-migrate=false
quarkus.flyway.baseline-version=0.0.1
quarkus.flyway.baseline-description=Initial description for test
-quarkus.flyway.locations=filesystem:src/test/resources/db/migration
+quarkus.flyway.locations=filesystem:src/test/resources/db/migration,classpath:db/migration
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java
index 509145744666b..e3df6ac28286f 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/QuarkusPathLocationScanner.java
@@ -28,17 +28,20 @@ public final class QuarkusPathLocationScanner implements ResourceAndClassScanner
private static Map> applicationCallbackClasses = Collections.emptyMap(); // the set default to aid unit tests
private final Collection scannedResources;
+ private final Collection> scannedMigrationClasses;
public QuarkusPathLocationScanner(Collection locations) {
LOGGER.debugv("Locations: {0}", locations);
this.scannedResources = new ArrayList<>();
+ this.scannedMigrationClasses = new ArrayList<>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
FileSystemScanner fileSystemScanner = null;
for (String migrationFile : applicationMigrationFiles) {
if (isClassPathResource(locations, migrationFile)) {
LOGGER.debugf("Loading %s", migrationFile);
+
scannedResources.add(new ClassPathResource(null, migrationFile, classLoader, StandardCharsets.UTF_8));
} else if (migrationFile.startsWith(Location.FILESYSTEM_PREFIX)) {
if (fileSystemScanner == null) {
@@ -51,6 +54,13 @@ public QuarkusPathLocationScanner(Collection locations) {
}
}
+ // Filter the provided migration classes to match the provided locations.
+ for (Class extends JavaMigration> migrationClass : applicationMigrationClasses) {
+ if (isClassPathResource(locations, migrationClass.getCanonicalName().replace('.', '/'))) {
+ LOGGER.debugf("Loading migration class %s", migrationClass.getCanonicalName());
+ scannedMigrationClasses.add(migrationClass);
+ }
+ }
}
public static void setApplicationCallbackClasses(Map> callbackClasses) {
@@ -96,7 +106,7 @@ private boolean isClassPathResource(Collection locations, String migra
*/
@Override
public Collection> scanForClasses() {
- return applicationMigrationClasses;
+ return scannedMigrationClasses;
}
public static void setApplicationMigrationFiles(Collection applicationMigrationFiles) {
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java
index eef1d7cc1f433..0cd850422385d 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java
@@ -17,6 +17,7 @@
import org.hibernate.engine.jdbc.cursor.internal.RefCursorSupportInitiator;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
+import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator;
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
@@ -234,6 +235,9 @@ private static List> buildQuarkusServiceInitiatorLis
// Default implementation
serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE);
+ // Default implementation
+ serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE);
+
serviceInitiators.trimToSize();
return serviceInitiators;
}
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java
index bb499897a92cb..7d92e318ad25e 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/StandardHibernateORMInitiatorListProvider.java
@@ -12,6 +12,7 @@
import org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
+import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator;
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
import org.hibernate.persister.internal.PersisterFactoryInitiator;
@@ -34,7 +35,8 @@
* Hibernate ORM when running on Quarkus.
* WARNING: this is a customized list: we started from a copy of ORM's standard
* list, then changes have evolved.
- * Also: Hibernate Reactive uses a different list.
+ * Also: Hibernate Reactive uses a different list, and there's an additional definition of
+ * services in PreconfiguredServiceRegistryBuilder.
*/
public final class StandardHibernateORMInitiatorListProvider implements InitialInitiatorListProvider {
@@ -104,6 +106,9 @@ public List> initialInitiatorList() {
// Default implementation
serviceInitiators.add(ParameterMarkerStrategyInitiator.INSTANCE);
+ // Default implementation
+ serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE);
+
serviceInitiators.trimToSize();
return serviceInitiators;
diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java
index f1d1f4fe70293..920269e71b9f5 100644
--- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java
+++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/SettingsSpyingIdentifierGenerator.java
@@ -23,7 +23,6 @@ public class SettingsSpyingIdentifierGenerator implements IdentifierGenerator {
public static final List> collectedSettings = new ArrayList<>();
@Override
- @SuppressWarnings({ "unchecked", "rawtypes" })
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
collectedSettings.add(new HashMap<>(serviceRegistry.getService(ConfigurationService.class).getSettings()));
}
diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java
index 5de1999a0e3b6..a5be0f364df0d 100644
--- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java
+++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/PreconfiguredReactiveServiceRegistryBuilder.java
@@ -16,6 +16,7 @@
import org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator;
import org.hibernate.engine.jdbc.cursor.internal.RefCursorSupportInitiator;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
+import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator;
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.persister.internal.PersisterFactoryInitiator;
@@ -241,6 +242,9 @@ private static List> buildQuarkusServiceInitiatorLis
// Custom for Hibernate Reactive: ParameterMarkerStrategy
serviceInitiators.add(NativeParametersHandling.INSTANCE);
+ // Default implementation
+ serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE);
+
serviceInitiators.trimToSize();
return serviceInitiators;
}
diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java
index 7334d7f96b905..fa57b9e8b3bde 100644
--- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java
+++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/registry/ReactiveHibernateInitiatorListProvider.java
@@ -12,6 +12,7 @@
import org.hibernate.engine.jdbc.dialect.internal.DialectResolverInitiator;
import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator;
import org.hibernate.engine.jdbc.internal.JdbcServicesInitiator;
+import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator;
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
import org.hibernate.persister.internal.PersisterFactoryInitiator;
import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator;
@@ -123,6 +124,9 @@ public List> initialInitiatorList() {
// Custom for Hibernate Reactive: ParameterMarkerStrategy
serviceInitiators.add(NativeParametersHandling.INSTANCE);
+ // Default implementation
+ serviceInitiators.add(SqlStatementLoggerInitiator.INSTANCE);
+
serviceInitiators.trimToSize();
return serviceInitiators;
}
diff --git a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java
index 0aa11409ddabb..e1611e2bcbeb6 100644
--- a/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java
+++ b/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/customized/QuarkusReactiveConnectionPoolInitiator.java
@@ -3,13 +3,9 @@
import java.util.Map;
import org.hibernate.boot.registry.StandardServiceInitiator;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
-import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.reactive.pool.ReactiveConnectionPool;
import org.hibernate.reactive.pool.impl.ExternalSqlClientPool;
-import org.hibernate.reactive.pool.impl.Parameters;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
@@ -37,10 +33,8 @@ public ReactiveConnectionPool initiateService(Map configurationValues, ServiceRe
// nothing to do, but given the separate hierarchies have to handle this here.
return null;
}
- SqlStatementLogger sqlStatementLogger = registry.getService(JdbcServices.class).getSqlStatementLogger();
- final Dialect dialect = registry.getService(JdbcEnvironment.class).getDialect();
- Parameters parameters = Parameters.instance(dialect);
- return new ExternalSqlClientPool(pool, sqlStatementLogger, parameters);
+ final JdbcServices jdbcService = registry.getService(JdbcServices.class);
+ return new ExternalSqlClientPool(pool, jdbcService.getSqlStatementLogger(), jdbcService.getSqlExceptionHelper());
}
}
diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java
similarity index 62%
rename from extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java
rename to extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java
index a8c4e6362386f..8c1e87051ea77 100644
--- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUiProcessor.java
+++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoDevUIProcessor.java
@@ -2,34 +2,36 @@
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.annotations.ExecutionTime;
-import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
-import io.quarkus.devui.spi.page.CardPageBuildItem;
+import io.quarkus.devui.spi.page.MenuPageBuildItem;
import io.quarkus.devui.spi.page.Page;
-import io.quarkus.info.runtime.InfoRecorder;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
/**
* This processor is responsible for the dev ui widget.
*/
-public class InfoDevUiProcessor {
+public class InfoDevUIProcessor {
@BuildStep(onlyIf = IsDevelopment.class)
- @Record(ExecutionTime.STATIC_INIT)
- CardPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
+ MenuPageBuildItem create(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
InfoBuildTimeConfig config,
ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig,
- LaunchModeBuildItem launchModeBuildItem,
- InfoRecorder unused) {
- CardPageBuildItem pageBuildItem = new CardPageBuildItem();
+ LaunchModeBuildItem launchModeBuildItem) {
+ MenuPageBuildItem pageBuildItem = new MenuPageBuildItem();
var path = nonApplicationRootPathBuildItem.resolveManagementPath(config.path(),
managementInterfaceBuildTimeConfig, launchModeBuildItem);
- pageBuildItem.addPage(Page.externalPageBuilder("App Information")
+
+ pageBuildItem.addBuildTimeData("infoUrl", path);
+
+ pageBuildItem.addPage(Page.webComponentPageBuilder()
+ .title("Information")
.icon("font-awesome-solid:circle-info")
+ .componentLink("qwc-info.js"));
+ pageBuildItem.addPage(Page.externalPageBuilder("Raw")
.url(path)
+ .icon("font-awesome-solid:circle-info")
.isJsonContent());
return pageBuildItem;
diff --git a/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js
new file mode 100644
index 0000000000000..3cd1f9e32c2d2
--- /dev/null
+++ b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js
@@ -0,0 +1,149 @@
+import { LitElement, html, css} from 'lit';
+import { columnBodyRenderer } from '@vaadin/grid/lit.js';
+import { infoUrl } from 'build-time-data';
+import '@vaadin/progress-bar';
+import 'qui-card';
+import '@vaadin/icon';
+
+/**
+ * This component shows the Info Screen
+ */
+export class QwcInfo extends LitElement {
+
+ static styles = css`
+ :host {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
+ gap: 1em;
+ padding: 10px;
+ }
+ qui-card {
+ display: flex;
+ }
+ .cardContent {
+ display: flex;
+ align-items: center;
+ padding: 10px;
+ gap: 10px;
+ }
+ vaadin-icon {
+ font-size: xx-large;
+ }
+ `;
+
+ static properties = {
+ _infoUrl: {state: false},
+ _info: {state: true},
+ };
+
+ constructor() {
+ super();
+ this._infoUrl = infoUrl;
+ this._info = null;
+ }
+
+ async connectedCallback() {
+ super.connectedCallback();
+ await this.load();
+ }
+
+ async load() {
+ const response = await fetch(this._infoUrl)
+ const data = await response.json();
+ this._info = data;
+ }
+
+ render() {
+ if (this._info) {
+ return html`
+ ${this._renderOsInfo(this._info)}
+ ${this._renderJavaInfo(this._info)}
+ ${this._renderGitInfo(this._info)}
+ ${this._renderBuildInfo(this._info)}
+ `;
+ }else{
+ return html`
+
+
Fetching infomation...
+
+
+ `;
+ }
+ }
+
+ _renderOsInfo(info){
+ if(info.os){
+ let os = info.os;
+ return html`
+
+ ${this._renderOsIcon(os.name)}
+
+ Name ${os.name}
+ Version ${os.version}
+ Arch ${os.arch}
+
+
+ `;
+ }
+ }
+
+ _renderJavaInfo(info){
+ if(info.java){
+ let java = info.java;
+ return html`
+
+
+
+ Version ${java.version}
+
+
+ `;
+ }
+ }
+
+ _renderOsIcon(osname){
+
+ if(osname){
+ if(osname.toLowerCase().startsWith("linux")){
+ return html` `;
+ }else if(osname.toLowerCase().startsWith("mac") || osname.toLowerCase().startsWith("darwin")){
+ return html` `;
+ }else if(osname.toLowerCase().startsWith("win")){
+ return html` `;
+ }
+ }
+ }
+
+ _renderGitInfo(info){
+ if(info.git){
+ let git = info.git;
+ return html`
+
+
+
+ Branch ${git.branch}
+ Commit ${git.commit.id}
+ Time ${git.commit.time}
+
+
+ `;
+ }
+ }
+
+ _renderBuildInfo(info){
+ if(info.build){
+ let build = info.build;
+ return html`
+
+
+ Group ${build.group}
+ Artifact ${build.artifact}
+ Version ${build.version}
+ Time ${build.time}
+
+
+ `;
+ }
+ }
+}
+customElements.define('qwc-info', QwcInfo);
\ No newline at end of file
diff --git a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java
index c12857249237c..c4ac6cf7c98e3 100644
--- a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java
+++ b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java
@@ -81,7 +81,7 @@ protected String formatUrl(String urlFormat, String type, String host, String da
private boolean configureUrlPropertyUsingKey(Map properties, String key) {
String value = serviceBinding.getProperties().get(key);
- if (value == null) {
+ if (value == null || prefix == null) {
return false;
} else if (value.startsWith(prefix)) {
properties.put(urlPropertyName, value);
@@ -109,7 +109,7 @@ public Reactive() {
}
public Reactive(String urlFormat) {
- super("reactive", "quarkus.datasource.reactive.url", "", urlFormat);
+ super("reactive", "quarkus.datasource.reactive.url", null, urlFormat);
}
}
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java
index d13091514834b..2074e4fd122f1 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddClusterRoleResourceDecorator.java
@@ -7,7 +7,6 @@
import java.util.List;
import java.util.Map;
-import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
@@ -46,9 +45,4 @@ public void visit(KubernetesListBuilder list) {
.endMetadata()
.withRules(rules));
}
-
- @Override
- public Class extends Decorator>[] before() {
- return new Class[] { AddRoleBindingResourceDecorator.class };
- }
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java
index 632b3bca7bcef..752efe7fd2b03 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddRoleResourceDecorator.java
@@ -7,7 +7,6 @@
import java.util.List;
import java.util.Map;
-import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
@@ -49,9 +48,4 @@ public void visit(KubernetesListBuilder list) {
.endMetadata()
.withRules(rules));
}
-
- @Override
- public Class extends Decorator>[] before() {
- return new Class[] { AddRoleBindingResourceDecorator.class };
- }
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java
index f6f90801b3608..b8fb1f0eb8dc4 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddServiceAccountResourceDecorator.java
@@ -5,7 +5,6 @@
import java.util.HashMap;
import java.util.Map;
-import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta;
@@ -44,9 +43,4 @@ public void visit(KubernetesListBuilder list) {
.endMetadata()
.endServiceAccountItem();
}
-
- @Override
- public Class extends Decorator>[] before() {
- return new Class[] { AddRoleBindingResourceDecorator.class };
- }
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
index cce484f5534c9..fa4d58dad9825 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java
@@ -30,7 +30,6 @@
import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.logger.NoopLogger;
import io.dekorate.processor.SimpleFileReader;
-import io.dekorate.processor.SimpleFileWriter;
import io.dekorate.project.Project;
import io.dekorate.utils.Maps;
import io.dekorate.utils.Strings;
@@ -146,7 +145,7 @@ public void build(ApplicationInfoBuildItem applicationInfo,
.map(DeploymentTargetEntry::getName)
.collect(Collectors.toSet()));
final Map generatedResourcesMap;
- final SessionWriter sessionWriter = new SimpleFileWriter(project, false);
+ final SessionWriter sessionWriter = new QuarkusFileWriter(project);
final SessionReader sessionReader = new SimpleFileReader(
project.getRoot().resolve("src").resolve("main").resolve("kubernetes"), targets);
sessionWriter.setProject(project);
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java
new file mode 100644
index 0000000000000..a837f359522dc
--- /dev/null
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/QuarkusFileWriter.java
@@ -0,0 +1,105 @@
+package io.quarkus.kubernetes.deployment;
+
+import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE;
+import static io.quarkus.kubernetes.deployment.Constants.CLUSTER_ROLE_BINDING;
+import static io.quarkus.kubernetes.deployment.Constants.CRONJOB;
+import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT;
+import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG;
+import static io.quarkus.kubernetes.deployment.Constants.INGRESS;
+import static io.quarkus.kubernetes.deployment.Constants.JOB;
+import static io.quarkus.kubernetes.deployment.Constants.ROLE;
+import static io.quarkus.kubernetes.deployment.Constants.ROLE_BINDING;
+import static io.quarkus.kubernetes.deployment.Constants.ROUTE;
+import static io.quarkus.kubernetes.deployment.Constants.SERVICE_ACCOUNT;
+import static io.quarkus.kubernetes.deployment.Constants.STATEFULSET;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import io.dekorate.processor.SimpleFileWriter;
+import io.dekorate.project.Project;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.KubernetesList;
+import io.fabric8.kubernetes.api.model.KubernetesListBuilder;
+
+public class QuarkusFileWriter extends SimpleFileWriter {
+
+ private static final List RESOURCE_KIND_ORDER = List.of("Namespace",
+ "NetworkPolicy",
+ "ResourceQuota",
+ "LimitRange",
+ "PodSecurityPolicy",
+ "PodDisruptionBudget",
+ SERVICE_ACCOUNT,
+ "Secret",
+ "SecretList",
+ "ConfigMap",
+ "StorageClass",
+ "PersistentVolume",
+ "PersistentVolumeClaim",
+ "CustomResourceDefinition",
+ CLUSTER_ROLE,
+ "ClusterRoleList",
+ CLUSTER_ROLE_BINDING,
+ "ClusterRoleBindingList",
+ ROLE,
+ "RoleList",
+ ROLE_BINDING,
+ "RoleBindingList",
+ "Service",
+ "ImageStream",
+ "BuildConfig",
+ "DaemonSet",
+ "Pod",
+ "ReplicationController",
+ "ReplicaSet",
+ DEPLOYMENT,
+ "HorizontalPodAutoscaler",
+ STATEFULSET,
+ DEPLOYMENT_CONFIG,
+ JOB,
+ CRONJOB,
+ INGRESS,
+ ROUTE,
+ "APIService");
+
+ public QuarkusFileWriter(Project project) {
+ super(project, false);
+ }
+
+ @Override
+ public Map write(String group, KubernetesList list) {
+ // sort resources in list by: ServiceAccount, Role, ClusterRole, the rest...
+ return super.write(group, new KubernetesListBuilder().addAllToItems(sort(list.getItems())).build());
+ }
+
+ private List sort(List items) {
+ // Resources that we need the order.
+ Map> groups = new HashMap<>();
+ // List of resources with unknown order: we preserve the order of creation in this case
+ List rest = new LinkedList<>();
+ for (HasMetadata item : items) {
+ String kind = item.getKind();
+ if (RESOURCE_KIND_ORDER.contains(kind)) {
+ groups.computeIfAbsent(kind, k -> new LinkedList<>())
+ .add(item);
+ } else {
+ rest.add(item);
+ }
+ }
+
+ List sorted = new LinkedList<>();
+ // we first add the resources with order
+ for (String kind : RESOURCE_KIND_ORDER) {
+ List group = groups.get(kind);
+ if (group != null) {
+ sorted.addAll(group);
+ }
+ }
+
+ sorted.addAll(rest);
+ return sorted;
+ }
+}
diff --git a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java
index 4d5dc582cf76b..26efb7775171a 100644
--- a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java
+++ b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java
@@ -27,6 +27,7 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
@@ -45,6 +46,7 @@
import io.quarkus.mailer.runtime.Mailers;
import io.quarkus.mailer.runtime.MailersBuildTimeConfig;
import io.quarkus.mailer.runtime.MailersRuntimeConfig;
+import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.deployment.CheckedTemplateAdapterBuildItem;
import io.quarkus.qute.deployment.QuteProcessor;
import io.quarkus.qute.deployment.TemplatePathBuildItem;
@@ -91,13 +93,15 @@ void registerBeans(BuildProducer beans) {
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
MailersBuildItem generateMailerSupportBean(MailerRecorder recorder,
+ CombinedIndexBuildItem index,
BeanDiscoveryFinishedBuildItem beans,
BuildProducer syntheticBeans) {
List mailerInjectionPoints = beans.getInjectionPoints().stream()
.filter(i -> SUPPORTED_INJECTION_TYPES.contains(i.getRequiredType().name()))
.collect(Collectors.toList());
- boolean hasDefaultMailer = mailerInjectionPoints.stream().anyMatch(i -> i.hasDefaultedQualifier());
+ boolean hasDefaultMailer = mailerInjectionPoints.stream().anyMatch(i -> i.hasDefaultedQualifier())
+ || !index.getIndex().getAnnotations(CheckedTemplate.class).isEmpty();
Set namedMailers = mailerInjectionPoints.stream()
.map(i -> i.getRequiredQualifier(MAILER_NAME))
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java
index 04fce11c4d0d3..39d66426f92d0 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java
@@ -68,4 +68,6 @@ public final class OidcConstants {
public static final String BACK_CHANNEL_LOGOUT_SID_CLAIM = "sid";
public static final String FRONT_CHANNEL_LOGOUT_SID_PARAM = "sid";
public static final String ID_TOKEN_SID_CLAIM = "sid";
+
+ public static final String OPENID_SCOPE = "openid";
}
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java
index 99568273ad37f..2e6f427080efb 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevConsoleProcessor.java
@@ -18,6 +18,7 @@ public abstract class AbstractDevConsoleProcessor {
protected static final String TOKEN_PATH_CONFIG_KEY = CONFIG_PREFIX + "token-path";
protected static final String END_SESSION_PATH_CONFIG_KEY = CONFIG_PREFIX + "end-session-path";
protected static final String POST_LOGOUT_URI_PARAM_CONFIG_KEY = CONFIG_PREFIX + "logout.post-logout-uri-param";
+ protected static final String SCOPES_KEY = CONFIG_PREFIX + "authentication.scopes";
protected void produceDevConsoleTemplateItems(Capabilities capabilities,
BuildProducer devConsoleTemplate,
@@ -66,6 +67,9 @@ protected void produceDevConsoleTemplateItems(Capabilities capabilities,
new DevConsoleRuntimeTemplateInfoBuildItem("postLogoutUriParam",
new OidcConfigPropertySupplier(POST_LOGOUT_URI_PARAM_CONFIG_KEY), this.getClass(),
curateOutcomeBuildItem));
+ devConsoleRuntimeInfo.produce(
+ new DevConsoleRuntimeTemplateInfoBuildItem("scopes",
+ new OidcConfigPropertySupplier(SCOPES_KEY), this.getClass(), curateOutcomeBuildItem));
}
diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java
index c0679b768a2ac..84f96c0465768 100644
--- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java
+++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevConsoleProcessor.java
@@ -101,7 +101,8 @@ public void run() {
metadata != null ? metadata.getString("authorization_endpoint") : null,
metadata != null ? metadata.getString("token_endpoint") : null,
metadata != null ? metadata.getString("end_session_endpoint") : null,
- metadata != null ? metadata.containsKey("introspection_endpoint") : false);
+ metadata != null ? metadata.containsKey("introspection_endpoint")
+ || metadata.containsKey("userinfo_endpoint") : false);
produceDevConsoleRouteItems(devConsoleRoute,
new OidcTestServiceHandler(vertxInstance, oidcConfig.devui.webClientTimeout),
diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html
index efd7382381617..0006e4572e603 100644
--- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html
+++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html
@@ -10,7 +10,7 @@
var port = {config:property('quarkus.http.port')};
-{#if info:oidcApplicationType is 'service'}
+{#if info:oidcApplicationType is 'service' || info:oidcApplicationType is 'hybrid'}
var devRoot = '{devRootAppend}';
var encodedDevRoot = devRoot.replaceAll("/", "%2F");
@@ -40,7 +40,7 @@
$('.loginError').hide();
$('.implicitLoggedIn').show();
var search = window.location.search;
- var code = search.match(/code=([^&]+)/)[1];
+ var code = decodeURIComponent(search.match(/code=([^&]+)/)[1]);
var state = search.match(/state=([^&]+)/)[1];
exchangeCodeForTokens(code, state);
}else if(errorInUrl()){
@@ -103,6 +103,7 @@
var address;
var state;
var clientId = getClientId();
+ var scopes = '{info:scopes??}';
{#if info:keycloakAdminUrl?? && info:keycloakRealms??}
address = '{info:keycloakAdminUrl??}' + "/realms/" + $('#keycloakRealm').val() + "/protocol/openid-connect/auth";
state = makeid() + "_" + $('#keycloakRealm').val() + "_" + clientId;
@@ -114,14 +115,14 @@
window.location.href = address
+ "?client_id=" + clientId
+ "&redirect_uri=" + "http%3A%2F%2Flocalhost%3A" + port + encodedDevRoot + "%2Fio.quarkus.quarkus-oidc%2Fprovider"
- + "&scope=openid&response_type=token id_token&response_mode=query&prompt=login"
+ + "&scope=" + scopes + "&response_type=token id_token&response_mode=query&prompt=login"
+ "&nonce=" + makeid()
+ "&state=" + state;
{#else}
window.location.href = address
+ "?client_id=" + clientId
+ "&redirect_uri=" + "http%3A%2F%2Flocalhost%3A" + port + encodedDevRoot + "%2Fio.quarkus.quarkus-oidc%2Fprovider"
- + "&scope=openid&response_type=code&response_mode=query&prompt=login"
+ + "&scope=" + scopes + "&response_type=code&response_mode=query&prompt=login"
+ "&nonce=" + makeid()
+ "&state=" + state;
{/if}
diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java
index b6eaff8715bec..1c53cc3965904 100644
--- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java
+++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OpaqueTokenVerificationWithUserInfoValidationTest.java
@@ -16,7 +16,9 @@ public class OpaqueTokenVerificationWithUserInfoValidationTest {
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addAsResource(new StringAsset("quarkus.oidc.token.verify-access-token-with-user-info=true\n"),
+ .addAsResource(new StringAsset(
+ "quarkus.oidc.token.verify-access-token-with-user-info=true\n"
+ + "quarkus.oidc.authentication.user-info-required=false\n"),
"application.properties"))
.assertException(t -> {
Throwable e = t;
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java
index d8e7361970eb7..6dc0e7d4a2ca5 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java
@@ -29,24 +29,24 @@ public AbstractJsonObjectResponse(JsonObject json) {
}
public String getString(String name) {
- return json.getString(name);
+ return contains(name) ? json.getString(name) : null;
}
public Boolean getBoolean(String name) {
- return json.getBoolean(name);
+ return contains(name) ? json.getBoolean(name) : null;
}
public Long getLong(String name) {
- JsonNumber number = json.getJsonNumber(name);
+ JsonNumber number = contains(name) ? json.getJsonNumber(name) : null;
return number != null ? number.longValue() : null;
}
public JsonArray getArray(String name) {
- return json.getJsonArray(name);
+ return contains(name) ? json.getJsonArray(name) : null;
}
public JsonObject getObject(String name) {
- return json.getJsonObject(name);
+ return contains(name) ? json.getJsonObject(name) : null;
}
public JsonObject getJsonObject() {
@@ -58,7 +58,7 @@ public Object get(String name) {
}
public boolean contains(String propertyName) {
- return json.containsKey(propertyName);
+ return json.containsKey(propertyName) && !json.isNull(propertyName);
}
public Set getPropertyNames() {
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
index 0dde01e5b97c2..e76abffd709e7 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
@@ -533,7 +533,7 @@ && isRedirectFromProvider(context, configContext)) {
: Collections.emptyList();
List scopes = new ArrayList<>(oidcConfigScopes.size() + 1);
if (configContext.oidcConfig.getAuthentication().addOpenidScope.orElse(true)) {
- scopes.add("openid");
+ scopes.add(OidcConstants.OPENID_SCOPE);
}
scopes.addAll(oidcConfigScopes);
codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ)
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java
index 9220acadc32a6..bc7384b392e10 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfigPropertySupplier.java
@@ -1,15 +1,18 @@
package io.quarkus.oidc.runtime;
+import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import org.eclipse.microprofile.config.ConfigProvider;
import io.quarkus.oidc.common.runtime.OidcCommonUtils;
+import io.quarkus.oidc.common.runtime.OidcConstants;
public class OidcConfigPropertySupplier implements Supplier {
private static final String AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc.auth-server-url";
private static final String END_SESSION_PATH_KEY = "quarkus.oidc.end-session-path";
+ private static final String SCOPES_KEY = "quarkus.oidc.authentication.scopes";
private String oidcConfigProperty;
private String defaultValue;
private boolean urlProperty;
@@ -40,6 +43,13 @@ public String get() {
return checkUrlProperty(value);
}
return defaultValue;
+ } else if (SCOPES_KEY.equals(oidcConfigProperty)) {
+ Optional> scopes = ConfigProvider.getConfig().getOptionalValues(oidcConfigProperty, String.class);
+ if (scopes.isPresent()) {
+ return OidcCommonUtils.urlEncode(String.join(" ", scopes.get()));
+ } else {
+ return OidcConstants.OPENID_SCOPE;
+ }
} else {
return checkUrlProperty(ConfigProvider.getConfig().getOptionalValue(oidcConfigProperty, String.class));
}
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
index 64187c10e2cef..f5fd7d6985710 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
@@ -172,6 +172,19 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf
return Uni.createFrom().failure(t);
}
+ if (oidcConfig.roles.source.orElse(null) == Source.userinfo && !enableUserInfo(oidcConfig)) {
+ throw new ConfigurationException(
+ "UserInfo is not required but UserInfo is expected to be the source of authorization roles");
+ }
+ if (oidcConfig.token.verifyAccessTokenWithUserInfo && !enableUserInfo(oidcConfig)) {
+ throw new ConfigurationException(
+ "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled");
+ }
+ if (!oidcConfig.authentication.isIdTokenRequired().orElse(true) && !enableUserInfo(oidcConfig)) {
+ throw new ConfigurationException(
+ "UserInfo is not required but it will be needed to verify a code flow access token");
+ }
+
if (!oidcConfig.discoveryEnabled.orElse(true)) {
if (!isServiceApp(oidcConfig)) {
if (!oidcConfig.authorizationPath.isPresent() || !oidcConfig.tokenPath.isPresent()) {
@@ -226,10 +239,6 @@ private Uni createTenantContext(Vertx vertx, OidcTenantConf
}
if (oidcConfig.token.verifyAccessTokenWithUserInfo) {
- if (!oidcConfig.authentication.isUserInfoRequired().orElse(false)) {
- throw new ConfigurationException(
- "UserInfo is not required but 'verifyAccessTokenWithUserInfo' is enabled");
- }
if (!oidcConfig.isDiscoveryEnabled().orElse(true)) {
if (oidcConfig.userInfoPath.isEmpty()) {
throw new ConfigurationException(
@@ -251,6 +260,18 @@ public TenantConfigContext apply(OidcProvider p) {
});
}
+ private static boolean enableUserInfo(OidcTenantConfig oidcConfig) {
+ Optional userInfoRequired = oidcConfig.authentication.isUserInfoRequired();
+ if (userInfoRequired.isPresent()) {
+ if (!userInfoRequired.get()) {
+ return false;
+ }
+ } else {
+ oidcConfig.authentication.setUserInfoRequired(true);
+ }
+ return true;
+ }
+
private static TenantConfigContext createTenantContextFromPublicKey(OidcTenantConfig oidcConfig) {
if (!isServiceApp(oidcConfig)) {
throw new ConfigurationException("'public-key' property can only be used with the 'service' applications");
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
index a9b245bf4e1da..88714eea23bdc 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java
@@ -429,6 +429,9 @@ static OidcTenantConfig mergeTenantConfig(OidcTenantConfig tenant, OidcTenantCon
if (tenant.token.issuer.isEmpty()) {
tenant.token.issuer = provider.token.issuer;
}
+ if (tenant.token.principalClaim.isEmpty()) {
+ tenant.token.principalClaim = provider.token.principalClaim;
+ }
return tenant;
}
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java
index 6c94a35a37153..38909dfce8841 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/KnownOidcProviders.java
@@ -63,6 +63,7 @@ private static OidcTenantConfig google() {
ret.setAuthServerUrl("https://accounts.google.com");
ret.setApplicationType(OidcTenantConfig.ApplicationType.WEB_APP);
ret.getAuthentication().setScopes(List.of("openid", "email", "profile"));
+ ret.getToken().setPrincipalClaim("name");
return ret;
}
diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java
index 37cc57d5294e9..e82bea9dd1368 100644
--- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java
+++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java
@@ -195,6 +195,7 @@ public void testAcceptGoogleProperties() throws Exception {
assertEquals(OidcUtils.DEFAULT_TENANT_ID, config.getTenantId().get());
assertEquals(ApplicationType.WEB_APP, config.getApplicationType().get());
assertEquals("https://accounts.google.com", config.getAuthServerUrl().get());
+ assertEquals("name", config.getToken().getPrincipalClaim().get());
assertEquals(List.of("openid", "email", "profile"), config.authentication.scopes.get());
}
@@ -206,12 +207,14 @@ public void testOverrideGoogleProperties() throws Exception {
tenant.setApplicationType(ApplicationType.HYBRID);
tenant.setAuthServerUrl("http://localhost/wiremock");
tenant.authentication.setScopes(List.of("write"));
+ tenant.token.setPrincipalClaim("firstname");
OidcTenantConfig config = OidcUtils.mergeTenantConfig(tenant, KnownOidcProviders.provider(Provider.GOOGLE));
assertEquals(OidcUtils.DEFAULT_TENANT_ID, config.getTenantId().get());
assertEquals(ApplicationType.HYBRID, config.getApplicationType().get());
assertEquals("http://localhost/wiremock", config.getAuthServerUrl().get());
+ assertEquals("firstname", config.getToken().getPrincipalClaim().get());
assertEquals(List.of("write"), config.authentication.scopes.get());
}
diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java
new file mode 100644
index 0000000000000..dd28befc9a69f
--- /dev/null
+++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/UserInfoTest.java
@@ -0,0 +1,67 @@
+package io.quarkus.oidc.runtime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.oidc.UserInfo;
+
+public class UserInfoTest {
+ UserInfo userInfo = new UserInfo(
+ "{"
+ + "\"name\": \"alice\","
+ + "\"admin\": true,"
+ + "\"email\": null,"
+ + "\"id\": 1234,"
+ + "\"permissions\": [\"read\", \"write\"],"
+ + "\"scopes\": {\"scope\": \"see\"}"
+ + "}");
+
+ @Test
+ public void testGetString() {
+ assertEquals("alice", userInfo.getString("name"));
+ assertNull(userInfo.getString("names"));
+ }
+
+ @Test
+ public void testGetBoolean() {
+ assertTrue(userInfo.getBoolean("admin"));
+ assertNull(userInfo.getBoolean("admins"));
+ }
+
+ @Test
+ public void testGetLong() {
+ assertEquals(1234, userInfo.getLong("id"));
+ assertNull(userInfo.getLong("ids"));
+ }
+
+ @Test
+ public void testGetArray() {
+ JsonArray array = userInfo.getArray("permissions");
+ assertNotNull(array);
+ assertEquals(2, array.size());
+ assertEquals("read", array.getString(0));
+ assertEquals("write", array.getString(1));
+ assertNull(userInfo.getArray("permit"));
+ }
+
+ @Test
+ public void testGetObject() {
+ JsonObject map = userInfo.getObject("scopes");
+ assertNotNull(map);
+ assertEquals(1, map.size());
+ assertEquals("see", map.getString("scope"));
+ assertNull(userInfo.getObject("scope"));
+ }
+
+ @Test
+ public void testGetNullProperty() {
+ assertNull(userInfo.getString("email"));
+ }
+}
diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java
index 79cd04d59c38b..6ae06be35007c 100644
--- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java
+++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java
@@ -5,7 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.util.Arrays;
+import java.util.List;
import jakarta.inject.Inject;
@@ -24,6 +24,7 @@ class OpenTelemetryLegacyConfigurationTest {
.overrideConfigKey("quarkus.opentelemetry.enabled", "false")
.overrideConfigKey("quarkus.opentelemetry.tracer.enabled", "false")
.overrideConfigKey("quarkus.opentelemetry.propagators", "tracecontext")
+ .overrideConfigKey("quarkus.opentelemetry.tracer.resource-attributes", "service.name=authservice")
.overrideConfigKey("quarkus.opentelemetry.tracer.suppress-non-application-uris", "false")
.overrideConfigKey("quarkus.opentelemetry.tracer.include-static-resources", "true")
.overrideConfigKey("quarkus.opentelemetry.tracer.sampler", "off")
@@ -46,7 +47,9 @@ void config() {
assertEquals(FALSE, oTelBuildConfig.enabled());
assertTrue(oTelBuildConfig.traces().enabled().isPresent());
assertEquals(FALSE, oTelBuildConfig.traces().enabled().get());
- assertEquals(Arrays.asList("tracecontext"), oTelBuildConfig.propagators()); // will not include the default baggagge
+ assertEquals(List.of("tracecontext"), oTelBuildConfig.propagators()); // will not include the default baggagge
+ assertTrue(oTelRuntimeConfig.resourceAttributes().isPresent());
+ assertEquals("service.name=authservice", oTelRuntimeConfig.resourceAttributes().get().get(0));
assertEquals(FALSE, oTelRuntimeConfig.traces().suppressNonApplicationUris());
assertEquals(TRUE, oTelRuntimeConfig.traces().includeStaticResources());
assertEquals("always_off", oTelBuildConfig.traces().sampler());
diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java
index ce1ea16693839..9d97991d4ca18 100644
--- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java
+++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java
@@ -1,5 +1,6 @@
package io.quarkus.opentelemetry.runtime.config;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
@@ -15,21 +16,27 @@
@Priority(Priorities.LIBRARY + 300 + 5)
public class OTelFallbackConfigSourceInterceptor extends FallbackConfigSourceInterceptor {
+ private final static Map FALLBACKS = new HashMap<>();
private final static LegacySamplerNameConverter LEGACY_SAMPLER_NAME_CONVERTER = new LegacySamplerNameConverter();
+ static {
+ FALLBACKS.put("quarkus.otel.enabled", "quarkus.opentelemetry.enabled");
+ FALLBACKS.put("quarkus.otel.traces.enabled", "quarkus.opentelemetry.tracer.enabled");
+ FALLBACKS.put("quarkus.otel.propagators", "quarkus.opentelemetry.propagators");
+ FALLBACKS.put("quarkus.otel.resource.attributes", "quarkus.opentelemetry.tracer.resource-attributes");
+ FALLBACKS.put("quarkus.otel.traces.suppress-non-application-uris",
+ "quarkus.opentelemetry.tracer.suppress-non-application-uris");
+ FALLBACKS.put("quarkus.otel.traces.include-static-resources", "quarkus.opentelemetry.tracer.include-static-resources");
+ FALLBACKS.put("quarkus.otel.traces.sampler", "quarkus.opentelemetry.tracer.sampler");
+ FALLBACKS.put("quarkus.otel.traces.sampler.arg", "quarkus.opentelemetry.tracer.sampler.ratio");
+ FALLBACKS.put("quarkus.otel.exporter.otlp.enabled", "quarkus.opentelemetry.tracer.exporter.otlp.enabled");
+ FALLBACKS.put("quarkus.otel.exporter.otlp.traces.legacy-endpoint",
+ "quarkus.opentelemetry.tracer.exporter.otlp.endpoint");
+ FALLBACKS.put("quarkus.otel.exporter.otlp.traces.headers", "quarkus.opentelemetry.tracer.exporter.otlp.headers");
+ }
+
public OTelFallbackConfigSourceInterceptor() {
- super(Map.of(
- "quarkus.otel.enabled", "quarkus.opentelemetry.enabled",
- "quarkus.otel.traces.enabled", "quarkus.opentelemetry.tracer.enabled",
- "quarkus.otel.propagators", "quarkus.opentelemetry.propagators",
- "quarkus.otel.traces.suppress-non-application-uris",
- "quarkus.opentelemetry.tracer.suppress-non-application-uris",
- "quarkus.otel.traces.include-static-resources", "quarkus.opentelemetry.tracer.include-static-resources",
- "quarkus.otel.traces.sampler", "quarkus.opentelemetry.tracer.sampler",
- "quarkus.otel.traces.sampler.arg", "quarkus.opentelemetry.tracer.sampler.ratio",
- "quarkus.otel.exporter.otlp.enabled", "quarkus.opentelemetry.tracer.exporter.otlp.enabled",
- "quarkus.otel.exporter.otlp.traces.headers", "quarkus.opentelemetry.tracer.exporter.otlp.headers",
- "quarkus.otel.exporter.otlp.traces.legacy-endpoint", "quarkus.opentelemetry.tracer.exporter.otlp.endpoint"));
+ super(FALLBACKS);
}
@Override
@@ -44,10 +51,28 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final
@Override
public Iterator iterateNames(final ConfigSourceInterceptorContext context) {
Set names = new HashSet<>();
- Iterator namesIterator = super.iterateNames(context);
+ Iterator namesIterator = context.iterateNames();
while (namesIterator.hasNext()) {
- names.add(namesIterator.next());
+ String name = namesIterator.next();
+ String fallback = FALLBACKS.get(name);
+ // We only include the used property, so if it is a fallback (not mapped), it will be reported as unknown
+ if (fallback != null) {
+ ConfigValue nameValue = context.proceed(name);
+ ConfigValue fallbackValue = context.proceed(fallback);
+ if (nameValue == null) {
+ names.add(fallback);
+ } else if (fallbackValue == null) {
+ names.add(name);
+ } else if (nameValue.getConfigSourceOrdinal() >= fallbackValue.getConfigSourceOrdinal()) {
+ names.add(name);
+ } else {
+ names.add(fallback);
+ }
+ } else {
+ names.add(name);
+ }
}
+
// TODO - Required because the defaults ConfigSource for mappings does not provide configuration names.
names.add("quarkus.otel.enabled");
names.add("quarkus.otel.metrics.exporter");
diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OtelConfigRelocateConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OtelConfigRelocateConfigSourceInterceptor.java
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java
index 8cc1c50404282..e3f5b77ffcf00 100644
--- a/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java
+++ b/extensions/panache/hibernate-reactive-panache-common/runtime/src/main/java/io/quarkus/hibernate/reactive/panache/common/runtime/AbstractJpaOperations.java
@@ -360,6 +360,15 @@ public static Mutiny.Query> bindParameters(Mutiny.Query> query, Object[] par
return query;
}
+ public static Mutiny.SelectionQuery> bindParameters(Mutiny.SelectionQuery> query, Object[] params) {
+ if (params == null || params.length == 0)
+ return query;
+ for (int i = 0; i < params.length; i++) {
+ query.setParameter(i + 1, params[i]);
+ }
+ return query;
+ }
+
public static Mutiny.Query> bindParameters(Mutiny.Query> query, Map params) {
if (params == null || params.size() == 0)
return query;
@@ -369,6 +378,15 @@ public static Mutiny.Query> bindParameters(Mutiny.Query> query, Map bindParameters(Mutiny.SelectionQuery> query, Map params) {
+ if (params == null || params.size() == 0)
+ return query;
+ for (Entry entry : params.entrySet()) {
+ query.setParameter(entry.getKey(), entry.getValue());
+ }
+ return query;
+ }
+
public static Uni executeUpdate(String query, Object... params) {
return getSession().chain(session -> {
Mutiny.Query> jpaQuery = session.createQuery(query);
diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java
index 2dd4221f96c86..9a7db850fdd0b 100644
--- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java
+++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/DevModeResource.java
@@ -11,6 +11,7 @@
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
+import io.vertx.oracleclient.OracleException;
import io.vertx.oracleclient.OraclePool;
@Path("/dev")
@@ -25,7 +26,7 @@ public class DevModeResource {
public CompletionStage checkConnectionFailure() throws SQLException {
CompletableFuture future = new CompletableFuture<>();
client.query("SELECT 1 FROM DUAL").execute(ar -> {
- Class> expectedExceptionClass = SQLException.class;
+ Class> expectedExceptionClass = OracleException.class;
if (ar.succeeded()) {
future.complete(Response.serverError().entity("Expected SQL query to fail").build());
} else if (!expectedExceptionClass.isAssignableFrom(ar.cause().getClass())) {
diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java
index b84018f654060..a953a81500034 100644
--- a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java
+++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/ReactiveOracleReloadTest.java
@@ -1,12 +1,14 @@
package io.quarkus.reactive.oracle.client;
import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;
+@Disabled("Failing on CI but working locally - must be investigated")
public class ReactiveOracleReloadTest {
@RegisterExtension
diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java
index 1f2762d529d9f..b7d1f63436dde 100644
--- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java
+++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java
@@ -19,6 +19,7 @@
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.jaxrs.client.reactive.runtime.ParameterAnnotationsSupplier;
+import io.quarkus.jaxrs.client.reactive.runtime.ParameterDescriptorFromClassSupplier;
import io.quarkus.jaxrs.client.reactive.runtime.ParameterGenericTypesSupplier;
class ClassRestClientContext implements AutoCloseable {
@@ -30,6 +31,9 @@ class ClassRestClientContext implements AutoCloseable {
public final Map methodStaticFields = new HashMap<>();
public final Map methodParamAnnotationsStaticFields = new HashMap<>();
public final Map methodGenericParametersStaticFields = new HashMap<>();
+ public final Map beanTypesParameterDescriptorsStaticFields = new HashMap<>();
+ public final Map classesMap = new HashMap<>();
+ private int beanParamIndex = 0;
public ClassRestClientContext(String name, BuildProducer generatedClasses,
String... interfaces) {
@@ -53,12 +57,12 @@ public void close() {
}
protected FieldDescriptor createJavaMethodField(ClassInfo interfaceClass, MethodInfo method, int methodIndex) {
- ResultHandle interfaceClassHandle = clinit.loadClassFromTCCL(interfaceClass.toString());
+ ResultHandle interfaceClassHandle = loadClass(interfaceClass.toString());
ResultHandle parameterArray = clinit.newArray(Class.class, method.parametersCount());
for (int i = 0; i < method.parametersCount(); i++) {
String parameterClass = method.parameterType(i).name().toString();
- clinit.writeArrayValue(parameterArray, i, clinit.loadClassFromTCCL(parameterClass));
+ clinit.writeArrayValue(parameterArray, i, loadClass(parameterClass));
}
ResultHandle javaMethodHandle = clinit.invokeVirtualMethod(
@@ -125,4 +129,43 @@ protected Supplier getLazyJavaMethodGenericParametersField(int
return javaMethodGenericParametersField;
};
}
+
+ /**
+ * Generates "Class.forName(beanClass)" to generate the parameter descriptors. This method will only be created if and only
+ * if the supplier is used in order to not have a penalty performance.
+ */
+ protected Supplier getLazyBeanParameterDescriptors(String beanClass) {
+ return () -> {
+ FieldDescriptor field = beanTypesParameterDescriptorsStaticFields.get(beanClass);
+ if (field != null) {
+ return field;
+ }
+
+ ResultHandle clazz = loadClass(beanClass);
+
+ ResultHandle mapWithAnnotationsHandle = clinit.newInstance(MethodDescriptor.ofConstructor(
+ ParameterDescriptorFromClassSupplier.class, Class.class),
+ clazz);
+ field = FieldDescriptor.of(classCreator.getClassName(), "beanParamDescriptors" + beanParamIndex, Supplier.class);
+ classCreator.getFieldCreator(field).setModifiers(Modifier.FINAL | Modifier.STATIC); // needs to be package-private because it's used by subresources
+ clinit.writeStaticField(field, mapWithAnnotationsHandle);
+
+ beanTypesParameterDescriptorsStaticFields.put(beanClass, field);
+
+ beanParamIndex++;
+
+ return field;
+ };
+ }
+
+ private ResultHandle loadClass(String className) {
+ ResultHandle classType = classesMap.get(className);
+ if (classType != null) {
+ return classType;
+ }
+
+ ResultHandle classFromTCCL = clinit.loadClassFromTCCL(className);
+ classesMap.put(className, classFromTCCL);
+ return classFromTCCL;
+ }
}
diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
index 42fb8d05ef807..3113b9af635e7 100644
--- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
+++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java
@@ -19,6 +19,7 @@
import java.io.Closeable;
import java.io.File;
import java.io.InputStream;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.AbstractMap;
@@ -152,6 +153,7 @@
import io.quarkus.gizmo.TryBlock;
import io.quarkus.jaxrs.client.reactive.runtime.ClientResponseBuilderFactory;
import io.quarkus.jaxrs.client.reactive.runtime.JaxrsClientReactiveRecorder;
+import io.quarkus.jaxrs.client.reactive.runtime.ParameterDescriptorFromClassSupplier;
import io.quarkus.jaxrs.client.reactive.runtime.RestClientBase;
import io.quarkus.jaxrs.client.reactive.runtime.ToObjectArray;
import io.quarkus.jaxrs.client.reactive.runtime.impl.MultipartResponseDataBase;
@@ -313,7 +315,7 @@ public boolean test(Map anns) {
})
.setResourceMethodCallback(new Consumer<>() {
@Override
- public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
+ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) {
MethodInfo method = entry.getMethodInfo();
String source = JaxrsClientReactiveProcessor.class.getSimpleName() + " > " + method.declaringClass()
+ "[" + method + "]";
@@ -909,9 +911,8 @@ A more full example of generated client (with sub-resource) can is at the bottom
addQueryParam(jandexMethod, methodCreator, methodTarget, param.name,
methodCreator.getMethodParam(paramIdx),
jandexMethod.parameterType(paramIdx), index, methodCreator.getThis(),
- methodCreator.readStaticField(methodGenericParametersField.get()),
- methodCreator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx));
+ getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx)));
} else if (param.parameterType == ParameterType.BEAN
|| param.parameterType == ParameterType.MULTI_PART_FORM) {
// bean params require both, web-target and Invocation.Builder, modifications
@@ -930,13 +931,17 @@ A more full example of generated client (with sub-resource) can is at the bottom
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
+
+ Supplier beanParamDescriptorsField = classContext
+ .getLazyBeanParameterDescriptors(beanParam.type);
+
formParams = addBeanParamData(jandexMethod, methodCreator, handleBeanParamMethod,
- invocationBuilderRef, beanParam.getItems(),
+ invocationBuilderRef, classContext, beanParam.getItems(),
methodCreator.getMethodParam(paramIdx), methodTarget, index,
restClientInterface.getClassName(),
methodCreator.getThis(),
handleBeanParamMethod.getThis(),
- formParams, methodGenericParametersField, methodParamAnnotationsField, paramIdx, multipart,
+ formParams, beanParamDescriptorsField, multipart,
beanParam.type);
handleBeanParamMethod.returnValue(invocationBuilderRef);
@@ -945,9 +950,8 @@ A more full example of generated client (with sub-resource) can is at the bottom
// methodTarget = methodTarget.resolveTemplate(paramname, paramvalue);
addPathParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx),
param.type, methodCreator.getThis(),
- methodCreator.readStaticField(methodGenericParametersField.get()),
- methodCreator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx);
+ getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx));
} else if (param.parameterType == ParameterType.BODY) {
// just store the index of parameter used to create the body, we'll use it later
bodyParameterIdx = paramIdx;
@@ -965,8 +969,9 @@ A more full example of generated client (with sub-resource) can is at the bottom
handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0));
addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
handleHeaderMethod.getMethodParam(1), param.type,
- handleHeaderMethod.getThis(), methodGenericParametersField.get(),
- methodParamAnnotationsField.get(), paramIdx);
+ handleHeaderMethod.getThis(),
+ getGenericTypeFromArray(handleHeaderMethod, methodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(handleHeaderMethod, methodParamAnnotationsField, paramIdx));
handleHeaderMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx));
} else if (param.parameterType == ParameterType.COOKIE) {
@@ -984,7 +989,8 @@ A more full example of generated client (with sub-resource) can is at the bottom
addCookieParam(handleCookieMethod, invocationBuilderRef, param.name,
handleCookieMethod.getMethodParam(1), param.type,
handleCookieMethod.getThis(),
- methodGenericParametersField.get(), methodParamAnnotationsField.get(), paramIdx);
+ getGenericTypeFromArray(handleCookieMethod, methodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(handleCookieMethod, methodParamAnnotationsField, paramIdx));
handleCookieMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx));
} else if (param.parameterType == ParameterType.FORM) {
@@ -993,9 +999,9 @@ A more full example of generated client (with sub-resource) can is at the bottom
addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.declaredType,
param.signature,
restClientInterface.getClassName(), methodCreator.getThis(), formParams,
- methodCreator.readStaticField(methodGenericParametersField.get()),
- methodCreator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx, multipart,
+ getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx),
+ multipart,
param.mimeType, param.partFileName,
jandexMethod.declaringClass().name() + "." + jandexMethod.name());
}
@@ -1215,9 +1221,8 @@ private void handleSubResourceMethod(List
addPathParam(ownerMethod, webTarget, param.name, paramValue,
param.type,
client,
- ownerMethod.readStaticField(methodGenericParametersField.get()),
- ownerMethod.readStaticField(methodParamAnnotationsField.get()),
- i);
+ getGenericTypeFromArray(ownerMethod, methodGenericParametersField, i),
+ getAnnotationsFromArray(ownerMethod, methodParamAnnotationsField, i));
}
}
@@ -1322,9 +1327,10 @@ private void handleSubResourceMethod(List
addQueryParam(jandexMethod, subMethodCreator, methodTarget, param.name,
paramValue, subParamField.type, index,
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
- subMethodCreator.readStaticField(subParamField.genericsParametersField.get()),
- subMethodCreator.readStaticField(subParamField.paramAnnotationsField.get()),
- subParamField.paramIndex));
+ getGenericTypeFromArray(subMethodCreator, subParamField.genericsParametersField,
+ subParamField.paramIndex),
+ getAnnotationsFromArray(subMethodCreator, subParamField.paramAnnotationsField,
+ subParamField.paramIndex)));
} else if (param.parameterType == ParameterType.BEAN
|| param.parameterType == ParameterType.MULTI_PART_FORM) {
// bean params require both, web-target and Invocation.Builder, modifications
@@ -1341,17 +1347,20 @@ private void handleSubResourceMethod(List
MethodCreator handleBeanParamMethod = subContext.classCreator.getMethodCreator(
handleBeanParamDescriptor).setModifiers(Modifier.PRIVATE);
+ Supplier beanParamDescriptors = subContext
+ .getLazyBeanParameterDescriptors(beanParam.type);
+
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod,
- invocationBuilderRef, beanParam.getItems(),
+ invocationBuilderRef, subContext, beanParam.getItems(),
paramValue, methodTarget, index,
interfaceClass.name().toString(),
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()),
formParams,
- methodGenericParametersField, methodParamAnnotationsField, subParamField.paramIndex,
+ beanParamDescriptors,
multipart, beanParam.type);
handleBeanParamMethod.returnValue(invocationBuilderRef);
@@ -1361,9 +1370,10 @@ private void handleSubResourceMethod(List
addPathParam(subMethodCreator, methodTarget, param.name, paramValue,
param.type,
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
- subMethodCreator.readStaticField(subParamField.genericsParametersField.get()),
- subMethodCreator.readStaticField(subParamField.paramAnnotationsField.get()),
- subParamField.paramIndex);
+ getGenericTypeFromArray(subMethodCreator, subParamField.genericsParametersField,
+ subParamField.paramIndex),
+ getAnnotationsFromArray(subMethodCreator, subParamField.paramAnnotationsField,
+ subParamField.paramIndex));
} else if (param.parameterType == ParameterType.BODY) {
// just store the index of parameter used to create the body, we'll use it later
bodyParameterValue = paramValue;
@@ -1384,9 +1394,10 @@ private void handleSubResourceMethod(List
handleHeaderMethod.getMethodParam(1),
param.type,
handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()),
- subParamField.genericsParametersField.get(),
- subParamField.paramAnnotationsField.get(),
- subParamField.paramIndex);
+ getGenericTypeFromArray(handleHeaderMethod, subParamField.genericsParametersField,
+ subParamField.paramIndex),
+ getAnnotationsFromArray(handleHeaderMethod, subParamField.paramAnnotationsField,
+ subParamField.paramIndex));
handleHeaderMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleHeaderDescriptor, paramValue);
} else if (param.parameterType == ParameterType.COOKIE) {
@@ -1406,9 +1417,10 @@ private void handleSubResourceMethod(List
handleCookieMethod.getMethodParam(1),
param.type,
handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()),
- subParamField.genericsParametersField.get(),
- subParamField.paramAnnotationsField.get(),
- subParamField.paramIndex);
+ getGenericTypeFromArray(handleCookieMethod, subParamField.genericsParametersField,
+ subParamField.paramIndex),
+ getAnnotationsFromArray(handleCookieMethod, subParamField.paramAnnotationsField,
+ subParamField.paramIndex));
handleCookieMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleCookieDescriptor, paramValue);
} else if (param.parameterType == ParameterType.FORM) {
@@ -1430,9 +1442,10 @@ private void handleSubResourceMethod(List
subMethodCreator.getMethodParam(paramIdx),
jandexSubMethod.parameterType(paramIdx), index,
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
- subMethodCreator.readStaticField(subMethodGenericParametersField.get()),
- subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()),
- paramIdx));
+ getGenericTypeFromArray(subMethodCreator, subMethodGenericParametersField,
+ paramIdx),
+ getAnnotationsFromArray(subMethodCreator, subMethodParamAnnotationsField,
+ paramIdx)));
} else if (param.parameterType == ParameterType.BEAN
|| param.parameterType == ParameterType.MULTI_PART_FORM) {
// bean params require both, web-target and Invocation.Builder, modifications
@@ -1445,20 +1458,23 @@ private void handleSubResourceMethod(List
subMethod.getName() + "$$" + subMethodIndex + "$$handleBeanParam$$" + paramIdx,
Invocation.Builder.class,
Invocation.Builder.class, param.type);
- MethodCreator handleBeanParamMethod = ownerContext.classCreator.getMethodCreator(
+ MethodCreator handleBeanParamMethod = subContext.classCreator.getMethodCreator(
handleBeanParamDescriptor).setModifiers(Modifier.PRIVATE);
+ Supplier beanParamDescriptors = subContext
+ .getLazyBeanParameterDescriptors(beanParam.type);
+
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
formParams = addBeanParamData(jandexMethod, subMethodCreator, handleBeanParamMethod,
- invocationBuilderRef, beanParam.getItems(),
+ invocationBuilderRef, subContext, beanParam.getItems(),
subMethodCreator.getMethodParam(paramIdx), methodTarget, index,
interfaceClass.name().toString(),
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()),
formParams,
- subMethodGenericParametersField, subMethodParamAnnotationsField, paramIdx, multipart,
+ beanParamDescriptors, multipart,
beanParam.type);
handleBeanParamMethod.returnValue(invocationBuilderRef);
@@ -1468,9 +1484,8 @@ private void handleSubResourceMethod(List
addPathParam(subMethodCreator, methodTarget, param.name,
subMethodCreator.getMethodParam(paramIdx), param.type,
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
- subMethodCreator.readStaticField(subMethodGenericParametersField.get()),
- subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()),
- paramIdx);
+ getGenericTypeFromArray(subMethodCreator, subMethodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(subMethodCreator, subMethodParamAnnotationsField, paramIdx));
} else if (param.parameterType == ParameterType.BODY) {
// just store the index of parameter used to create the body, we'll use it later
bodyParameterValue = subMethodCreator.getMethodParam(paramIdx);
@@ -1489,7 +1504,8 @@ private void handleSubResourceMethod(List
addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
handleHeaderMethod.getMethodParam(1), param.type,
handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()),
- subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx);
+ getGenericTypeFromArray(handleHeaderMethod, subMethodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(handleHeaderMethod, subMethodParamAnnotationsField, paramIdx));
handleHeaderMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleHeaderDescriptor, subMethodCreator.getMethodParam(paramIdx));
} else if (param.parameterType == ParameterType.COOKIE) {
@@ -1508,7 +1524,8 @@ private void handleSubResourceMethod(List
handleCookieMethod.getMethodParam(1),
param.type,
handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()),
- subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx);
+ getGenericTypeFromArray(handleCookieMethod, subMethodGenericParametersField, paramIdx),
+ getAnnotationsFromArray(handleCookieMethod, subMethodParamAnnotationsField, paramIdx));
handleCookieMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleCookieDescriptor, subMethodCreator.getMethodParam(paramIdx));
} else if (param.parameterType == ParameterType.FORM) {
@@ -1635,7 +1652,7 @@ private void handleMultipartField(String formParamName, String partType, String
String type,
String parameterGenericType, ResultHandle fieldValue, AssignableResultHandle multipartForm,
BytecodeCreator methodCreator,
- ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations, int methodIndex,
+ ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations,
ResultHandle genericType, String errorLocation) {
BytecodeCreator ifValueNotNull = methodCreator.ifNotNull(fieldValue).trueBranch();
@@ -1679,7 +1696,7 @@ private void handleMultipartField(String formParamName, String partType, String
} else {
// go via converter
ResultHandle convertedFormParam = convertParamToString(ifValueNotNull, client, fieldValue, type, genericType,
- parameterAnnotations, methodIndex);
+ parameterAnnotations);
BytecodeCreator parameterIsStringBranch = checkStringParam(ifValueNotNull, convertedFormParam,
restClientInterfaceClassName, errorLocation);
addString(parameterIsStringBranch, multipartForm, formParamName, null, partFilename, convertedFormParam);
@@ -2270,6 +2287,7 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod,
// Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder)
BytecodeCreator invocationBuilderEnricher,
AssignableResultHandle invocationBuilder,
+ ClassRestClientContext classContext,
List- beanParamItems,
ResultHandle param,
// can only be used in the current method, not in `invocationBuilderEnricher`
@@ -2280,18 +2298,17 @@ private AssignableResultHandle addBeanParamData(MethodInfo jandexMethod,
ResultHandle invocationEnricherClient,
// this client or containing client if this is a sub-client
AssignableResultHandle formParams,
- Supplier
methodGenericTypeField,
- Supplier methodParamAnnotationsField,
- int paramIdx, boolean multipart, String beanParamClass) {
+ Supplier descriptorsField,
+ boolean multipart, String beanParamClass) {
// Form params collector must be initialized at method root level before any inner blocks that may use it
if (areFormParamsDefinedIn(beanParamItems)) {
formParams = createFormDataIfAbsent(methodCreator, formParams, multipart);
}
- addSubBeanParamData(jandexMethod, methodCreator, invocationBuilderEnricher, invocationBuilder, beanParamItems, param,
- target,
+ addSubBeanParamData(jandexMethod, methodCreator, invocationBuilderEnricher, invocationBuilder, classContext,
+ beanParamItems, param, target,
index, restClientInterfaceClassName, client, invocationEnricherClient, formParams,
- methodGenericTypeField, methodParamAnnotationsField, paramIdx, multipart, beanParamClass);
+ descriptorsField, multipart, beanParamClass);
return formParams;
}
@@ -2300,6 +2317,7 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
// Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder)
BytecodeCreator invocationBuilderEnricher,
AssignableResultHandle invocationBuilder,
+ ClassRestClientContext classContext,
List- beanParamItems,
ResultHandle param,
// can only be used in the current method, not in `invocationBuilderEnricher`
@@ -2310,9 +2328,8 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
// this client or containing client if this is a sub-client
ResultHandle invocationEnricherClient,
AssignableResultHandle formParams,
- Supplier
methodGenericTypeField,
- Supplier methodParamAnnotationsField,
- int paramIdx, boolean multipart, String beanParamClass) {
+ Supplier beanParamDescriptorField,
+ boolean multipart, String beanParamClass) {
BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch();
BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1))
.trueBranch();
@@ -2322,10 +2339,12 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
case BEAN_PARAM:
BeanParamItem beanParamItem = (BeanParamItem) item;
ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param);
- addSubBeanParamData(jandexMethod, creator, invoEnricher, invocationBuilder, beanParamItem.items(),
- beanParamElementHandle, target, index, restClientInterfaceClassName, client,
+ Supplier newBeanParamDescriptorField = classContext
+ .getLazyBeanParameterDescriptors(beanParamItem.className());
+ addSubBeanParamData(jandexMethod, creator, invoEnricher, invocationBuilder, classContext,
+ beanParamItem.items(), beanParamElementHandle, target, index, restClientInterfaceClassName, client,
invocationEnricherClient, formParams,
- methodGenericTypeField, methodParamAnnotationsField, paramIdx, multipart,
+ newBeanParamDescriptorField, multipart,
beanParamItem.className());
break;
case QUERY_PARAM:
@@ -2335,9 +2354,8 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
queryParam.extract(creator, param),
queryParam.getValueType(),
index, client,
- creator.readStaticField(methodGenericTypeField.get()),
- creator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx));
+ getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()),
+ getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName())));
break;
case COOKIE:
CookieParamItem cookieParam = (CookieParamItem) item;
@@ -2345,33 +2363,34 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
cookieParam.getCookieName(),
cookieParam.extract(invoEnricher, invoEnricher.getMethodParam(1)),
cookieParam.getParamType(), invocationEnricherClient,
- methodGenericTypeField.get(), methodParamAnnotationsField.get(), paramIdx);
+ getGenericTypeFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()),
+ getAnnotationsFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()));
break;
case HEADER_PARAM:
HeaderParamItem headerParam = (HeaderParamItem) item;
addHeaderParam(invoEnricher, invocationBuilder,
headerParam.getHeaderName(),
headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1)),
- headerParam.getParamType(), invocationEnricherClient, methodGenericTypeField.get(),
- methodParamAnnotationsField.get(), paramIdx);
+ headerParam.getParamType(), invocationEnricherClient,
+ getGenericTypeFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()),
+ getAnnotationsFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()));
break;
case PATH_PARAM:
PathParamItem pathParam = (PathParamItem) item;
addPathParam(creator, target,
pathParam.getPathParamName(),
pathParam.extract(creator, param), pathParam.getParamType(), client,
- creator.readStaticField(methodGenericTypeField.get()),
- creator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx);
+ getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()),
+ getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName()));
break;
case FORM_PARAM:
FormParamItem formParam = (FormParamItem) item;
addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param),
formParam.getParamType(), formParam.getParamSignature(), restClientInterfaceClassName, client,
formParams,
- creator.readStaticField(methodGenericTypeField.get()),
- creator.readStaticField(methodParamAnnotationsField.get()),
- paramIdx, multipart, formParam.getMimeType(), formParam.getFileName(),
+ getGenericTypeFromParameter(creator, beanParamDescriptorField, item.fieldName()),
+ getAnnotationsFromParameter(creator, beanParamDescriptorField, item.fieldName()),
+ multipart, formParam.getMimeType(), formParam.getFileName(),
beanParamClass + "." + formParam.getSourceName());
break;
default:
@@ -2380,6 +2399,64 @@ private void addSubBeanParamData(MethodInfo jandexMethod, BytecodeCreator method
}
}
+ private ResultHandle getGenericTypeFromParameter(BytecodeCreator creator, Supplier supplier,
+ String name) {
+ // Will return Map
+ ResultHandle map = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class),
+ creator.readStaticField(supplier.get()));
+ // Will return ParameterDescriptorFromClassSupplier.ParameterDescriptor;
+ ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class),
+ map, creator.load(name));
+ // if (value != null) return value.genericType;
+ AssignableResultHandle genericType = creator.createVariable(java.lang.reflect.Type.class);
+ BranchResult ifBranch = creator.ifNotNull(value);
+ BytecodeCreator ifNotNull = ifBranch.trueBranch();
+ ifNotNull.assign(genericType, ifNotNull.readInstanceField(
+ FieldDescriptor.of(ParameterDescriptorFromClassSupplier.ParameterDescriptor.class, "genericType",
+ java.lang.reflect.Type.class),
+ value));
+ // if (value == null) return null;
+ BytecodeCreator ifNull = ifBranch.falseBranch();
+ ifNull.assign(genericType, ifNull.loadNull());
+ return genericType;
+ }
+
+ private ResultHandle getGenericTypeFromArray(BytecodeCreator creator, Supplier supplier,
+ int paramIdx) {
+ ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class),
+ creator.readStaticField(supplier.get()));
+ return creator.readArrayValue(creator.checkCast(value, java.lang.reflect.Type[].class), paramIdx);
+ }
+
+ private ResultHandle getAnnotationsFromParameter(BytecodeCreator creator, Supplier supplier,
+ String name) {
+ // Will return Map
+ ResultHandle map = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class),
+ creator.readStaticField(supplier.get()));
+ // Will return ParameterDescriptorFromClassSupplier.ParameterDescriptor;
+ ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class),
+ map, creator.load(name));
+ // if (value != null) return value.genericType;
+ AssignableResultHandle annotations = creator.createVariable(Annotation[].class);
+ BranchResult ifBranch = creator.ifNotNull(value);
+ BytecodeCreator ifNotNull = ifBranch.trueBranch();
+ ifNotNull.assign(annotations, ifNotNull.readInstanceField(
+ FieldDescriptor.of(ParameterDescriptorFromClassSupplier.ParameterDescriptor.class, "annotations",
+ Annotation[].class),
+ value));
+ // if (value == null) return null;
+ BytecodeCreator ifNull = ifBranch.falseBranch();
+ ifNull.assign(annotations, ifNull.loadNull());
+ return annotations;
+ }
+
+ private ResultHandle getAnnotationsFromArray(BytecodeCreator creator, Supplier supplier,
+ int paramIdx) {
+ ResultHandle value = creator.invokeInterfaceMethod(ofMethod(Supplier.class, "get", Object.class),
+ creator.readStaticField(supplier.get()));
+ return creator.readArrayValue(creator.checkCast(value, Annotation[][].class), paramIdx);
+ }
+
private boolean areFormParamsDefinedIn(List- beanParamItems) {
for (Item item : beanParamItems) {
switch (item.type()) {
@@ -2406,7 +2483,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth
// this client or containing client if we're in a subresource
ResultHandle client,
ResultHandle genericType,
- ResultHandle paramAnnotations, int paramIndex) {
+ ResultHandle paramAnnotations) {
AssignableResultHandle result = methodCreator.createVariable(WebTarget.class);
BranchResult isParamNull = methodCreator.ifNull(queryParamHandle);
@@ -2453,7 +2530,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth
}
// get the new WebTarget
addQueryParamToWebTarget(loopCreator, key, result, client, genericType, paramAnnotations,
- paramIndex, paramArray, componentType, result);
+ paramArray, componentType, result);
} else {
ResultHandle paramArray;
String componentType = null;
@@ -2481,8 +2558,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth
}
addQueryParamToWebTarget(notNullParam, notNullParam.load(paramName), webTarget, client, genericType,
- paramAnnotations, paramIndex,
- paramArray, componentType, result);
+ paramAnnotations, paramArray, componentType, result);
}
isParamNull.trueBranch().assign(result, webTarget);
@@ -2521,14 +2597,13 @@ private BranchResult iteratorHasNext(BytecodeCreator creator, ResultHandle itera
private void addQueryParamToWebTarget(BytecodeCreator creator, ResultHandle paramName,
ResultHandle webTarget,
ResultHandle client, ResultHandle genericType,
- ResultHandle paramAnnotations, int paramIndex, ResultHandle paramArray,
+ ResultHandle paramAnnotations, ResultHandle paramArray,
String componentType,
AssignableResultHandle resultVariable) {
ResultHandle convertedParamArray = creator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class,
- Class.class, Supplier.class, Supplier.class, int.class),
- client, paramArray, creator.loadClassFromTCCL(componentType), genericType, paramAnnotations,
- creator.load(paramIndex));
+ Class.class, java.lang.reflect.Type.class, Annotation[].class),
+ client, paramArray, creator.loadClassFromTCCL(componentType), genericType, paramAnnotations);
creator.assign(resultVariable, creator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(WebTarget.class, "queryParam", WebTarget.class,
@@ -2563,19 +2638,15 @@ private boolean isMap(Type type, IndexView index) {
private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder,
String paramName, ResultHandle headerParamHandle, String paramType, ResultHandle client,
- FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField,
- int paramIdx) {
+ ResultHandle genericType, ResultHandle annotations) {
BytecodeCreator notNullValue = invoBuilderEnricher.ifNull(headerParamHandle).falseBranch();
- ResultHandle genericType = notNullValue.readStaticField(methodGenericTypeField);
-
- ResultHandle parameterAnnotations = notNullValue.readStaticField(methodParamAnnotationsField);
headerParamHandle = notNullValue.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
- Object.class, Class.class, Supplier.class, Supplier.class, int.class),
+ Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, headerParamHandle,
- notNullValue.loadClassFromTCCL(paramType), genericType, parameterAnnotations, notNullValue.load(paramIdx));
+ notNullValue.loadClassFromTCCL(paramType), genericType, annotations);
notNullValue.assign(invocationBuilder,
notNullValue.invokeInterfaceMethod(
@@ -2586,13 +2657,12 @@ private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResul
private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodTarget,
String paramName, ResultHandle pathParamHandle, String parameterType, ResultHandle client,
- ResultHandle genericType, ResultHandle parameterAnnotations, int paramIndex) {
+ ResultHandle genericType, ResultHandle parameterAnnotations) {
ResultHandle handle = methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
- Object.class, Class.class, Supplier.class, Supplier.class, int.class),
+ Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, pathParamHandle,
- methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations,
- methodCreator.load(paramIndex));
+ methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations);
methodCreator.assign(methodTarget,
methodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD,
methodTarget,
@@ -2603,17 +2673,17 @@ private void addFormParam(BytecodeCreator methodCreator, String paramName, Resul
String parameterType, String parameterGenericType,
String restClientInterfaceClassName, ResultHandle client, AssignableResultHandle formParams,
ResultHandle genericType,
- ResultHandle parameterAnnotations, int methodIndex, boolean multipart,
+ ResultHandle parameterAnnotations, boolean multipart,
String partType, String partFilename, String errorLocation) {
if (multipart) {
handleMultipartField(paramName, partType, partFilename, parameterType, parameterGenericType, formParamHandle,
formParams, methodCreator,
- client, restClientInterfaceClassName, parameterAnnotations, methodIndex, genericType,
+ client, restClientInterfaceClassName, parameterAnnotations, genericType,
errorLocation);
} else {
BytecodeCreator notNullValue = methodCreator.ifNull(formParamHandle).falseBranch();
ResultHandle convertedFormParam = convertParamToString(notNullValue, client, formParamHandle, parameterType,
- genericType, parameterAnnotations, methodIndex);
+ genericType, parameterAnnotations);
BytecodeCreator parameterIsStringBranch = checkStringParam(notNullValue, convertedFormParam,
restClientInterfaceClassName, errorLocation);
parameterIsStringBranch.invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams,
@@ -2637,30 +2707,25 @@ private BytecodeCreator checkStringParam(BytecodeCreator notNullValue, ResultHan
private ResultHandle convertParamToString(BytecodeCreator notNullValue, ResultHandle client,
ResultHandle formParamHandle, String parameterType,
- ResultHandle genericType, ResultHandle parameterAnnotations, int methodIndex) {
+ ResultHandle genericType, ResultHandle parameterAnnotations) {
return notNullValue.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
- Object.class, Class.class, Supplier.class, Supplier.class, int.class),
+ Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, formParamHandle,
- notNullValue.loadClassFromTCCL(parameterType), genericType, parameterAnnotations,
- notNullValue.load(methodIndex));
+ notNullValue.loadClassFromTCCL(parameterType), genericType, parameterAnnotations);
}
private void addCookieParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder,
String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client,
- FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField, int paramIdx) {
+ ResultHandle genericType, ResultHandle annotations) {
BytecodeCreator notNullValue = invoBuilderEnricher.ifNull(cookieParamHandle).falseBranch();
- ResultHandle genericType = notNullValue.readStaticField(methodGenericTypeField);
-
- ResultHandle parameterAnnotations = notNullValue.readStaticField(methodParamAnnotationsField);
-
cookieParamHandle = notNullValue.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
- Object.class, Class.class, Supplier.class, Supplier.class, int.class),
+ Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
client, cookieParamHandle,
- notNullValue.loadClassFromTCCL(paramType), genericType, parameterAnnotations, notNullValue.load(paramIdx));
+ notNullValue.loadClassFromTCCL(paramType), genericType, annotations);
notNullValue.assign(invocationBuilder,
notNullValue.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Invocation.Builder.class, "cookie", Invocation.Builder.class, String.class,
diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java
new file mode 100644
index 0000000000000..72cb181e8f295
--- /dev/null
+++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ParameterDescriptorFromClassSupplier.java
@@ -0,0 +1,52 @@
+package io.quarkus.jaxrs.client.reactive.runtime;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class ParameterDescriptorFromClassSupplier
+ implements Supplier
> {
+
+ private final Class clazz;
+ private volatile Map value;
+
+ public ParameterDescriptorFromClassSupplier(Class clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public Map get() {
+ if (value == null) {
+ value = new HashMap<>();
+ Class currentClass = clazz;
+ while (currentClass != null && currentClass != Object.class) {
+ for (Field field : currentClass.getDeclaredFields()) {
+ ParameterDescriptor descriptor = new ParameterDescriptor();
+ descriptor.annotations = field.getAnnotations();
+ descriptor.genericType = field.getGenericType();
+ value.put(field.getName(), descriptor);
+ }
+
+ for (Method method : currentClass.getDeclaredMethods()) {
+ ParameterDescriptor descriptor = new ParameterDescriptor();
+ descriptor.annotations = method.getAnnotations();
+ descriptor.genericType = method.getGenericReturnType();
+ value.put(method.getName(), descriptor);
+ }
+
+ currentClass = currentClass.getSuperclass();
+ }
+ }
+
+ return value;
+ }
+
+ public static class ParameterDescriptor {
+ public Annotation[] annotations;
+ public Type genericType;
+ }
+}
diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
index c7f70ad6d0854..45c8aef084b2c 100644
--- a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
+++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java
@@ -7,7 +7,6 @@
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;
@@ -122,9 +121,8 @@ public RestClientBase(List providers) {
}
@SuppressWarnings("unused") // used by generated code
- public Object[] convertParamArray(T[] value, Class type, Supplier genericType,
- Supplier methodAnnotations, int paramIndex) {
- ParamConverter converter = getConverter(type, genericType, methodAnnotations, paramIndex);
+ public Object[] convertParamArray(T[] value, Class type, Type genericType, Annotation[] annotations) {
+ ParamConverter converter = getConverter(type, genericType, annotations);
if (converter == null) {
return value;
@@ -139,10 +137,8 @@ public Object[] convertParamArray(T[] value, Class type, Supplier
}
@SuppressWarnings("unused") // used by generated code
- public Object convertParam(T value, Class type, Supplier genericType,
- Supplier methodAnnotations,
- int paramIndex) {
- ParamConverter converter = getConverter(type, genericType, methodAnnotations, paramIndex);
+ public Object convertParam(T value, Class type, Type genericType, Annotation[] annotations) {
+ ParamConverter converter = getConverter(type, genericType, annotations);
if (converter != null) {
return converter.toString(value);
} else {
@@ -154,30 +150,26 @@ public Object convertParam(T value, Class type, Supplier genericT
}
}
- private ParamConverter getConverter(Class type, Supplier genericType,
- Supplier methodAnnotations,
- int paramIndex) {
+ private ParamConverter getConverter(Class type, Type genericType, Annotation[] annotations) {
ParamConverterProvider converterProvider = providerForClass.get(type);
if (converterProvider == null) {
for (ParamConverterProvider provider : paramConverterProviders) {
- ParamConverter converter = provider.getConverter(type, genericType.get()[paramIndex],
- methodAnnotations.get()[paramIndex]);
+ ParamConverter converter = provider.getConverter(type, genericType, annotations);
if (converter != null) {
providerForClass.put(type, provider);
return converter;
}
}
// FIXME: this should go in favour of generating them, so we can generate them only if used for dead-code elimination
- ParamConverter converter = DEFAULT_PROVIDER.getConverter(type, genericType.get()[paramIndex],
- methodAnnotations.get()[paramIndex]);
+ ParamConverter converter = DEFAULT_PROVIDER.getConverter(type, genericType, annotations);
if (converter != null) {
providerForClass.put(type, DEFAULT_PROVIDER);
return converter;
}
providerForClass.put(type, NO_PROVIDER);
} else if (converterProvider != NO_PROVIDER) {
- return converterProvider.getConverter(type, genericType.get()[paramIndex], methodAnnotations.get()[paramIndex]);
+ return converterProvider.getConverter(type, genericType, annotations);
}
return null;
}
diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java
index 62181bcb5d575..b7cfc7052729f 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java
@@ -1,5 +1,8 @@
package io.quarkus.resteasy.reactive.server.deployment;
+import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SERVER_MESSAGE_BODY_READER;
+
+import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
@@ -14,6 +17,8 @@
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig;
+import org.jboss.resteasy.reactive.common.model.MethodParameter;
+import org.jboss.resteasy.reactive.common.model.ParameterType;
import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler;
import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner;
import org.jboss.resteasy.reactive.common.processor.scanning.ScannedSerializer;
@@ -24,9 +29,11 @@
import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter;
import org.jboss.resteasy.reactive.server.spi.EndpointInvokerFactory;
+import io.quarkus.builder.BuildException;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
+import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.resteasy.reactive.common.deployment.JsonDefaultProducersHandler;
import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder;
@@ -147,16 +154,73 @@ protected void handleAdditionalMethodProcessing(ServerResourceMethod method, Cla
warnAboutMissingJsonProviderIfNeeded(method, info);
}
+ @Override
+ public boolean additionalRegisterClassForReflectionCheck(ResourceMethodCallbackEntry entry) {
+ return checkBodyParameterMessageBodyReader(entry);
+ }
+
+ /**
+ * Check whether the Resource Method has a body parameter for which there exists a matching
+ * {@link jakarta.ws.rs.ext.MessageBodyReader}
+ * that is not a {@link org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader}.
+ * In this case the Resource Class needs to be registered for reflection because the
+ * {@link jakarta.ws.rs.ext.MessageBodyReader#isReadable(Class, java.lang.reflect.Type, Annotation[], MediaType)}
+ * method expects to be passed the method annotations.
+ */
+ private boolean checkBodyParameterMessageBodyReader(ResourceMethodCallbackEntry entry) {
+ MethodParameter[] parameters = entry.getResourceMethod().getParameters();
+ if (parameters.length == 0) {
+ return false;
+ }
+ MethodParameter bodyParameter = null;
+ for (MethodParameter parameter : parameters) {
+ if (parameter.parameterType == ParameterType.BODY) {
+ bodyParameter = parameter;
+ break;
+ }
+ }
+ if (bodyParameter == null) {
+ return false;
+ }
+ String parameterClassName = bodyParameter.getDeclaredType();
+ List readers = getSerializerScanningResult().getReaders();
+
+ for (ScannedSerializer reader : readers) {
+ if (isSubclassOf(parameterClassName, reader.getHandledClassName()) && !isServerMessageBodyReader(
+ reader.getClassInfo())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSubclassOf(String className, String parentName) {
+ if (className.equals(parentName)) {
+ return true;
+ }
+ ClassInfo classByName = index.getClassByName(className);
+ if (classByName == null) {
+ return false;
+ }
+ try {
+ return JandexUtil.isSubclassOf(index, classByName,
+ DotName.createSimple(parentName));
+ } catch (BuildException e) {
+ return false;
+ }
+ }
+
+ private boolean isServerMessageBodyReader(ClassInfo readerClassInfo) {
+ return index.getAllKnownImplementors(SERVER_MESSAGE_BODY_READER).contains(readerClassInfo);
+ }
+
private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, MethodInfo info) {
if (!capabilities.isCapabilityWithPrefixMissing("io.quarkus.resteasy.reactive.json")) {
return;
}
if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson())) {
- if (serializerScanningResult == null) {
- serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult);
- }
- boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(serializerScanningResult.getReaders());
- boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(serializerScanningResult.getWriters());
+ boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders());
+ boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters());
if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) {
LOGGER.warnf("Quarkus detected the use of JSON in JAX-RS method '" + info.declaringClass().name() + "#"
+ info.name()
@@ -165,6 +229,13 @@ private void warnAboutMissingJsonProviderIfNeeded(ServerResourceMethod method, M
}
}
+ private SerializerScanningResult getSerializerScanningResult() {
+ if (serializerScanningResult == null) {
+ serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, applicationScanningResult);
+ }
+ return serializerScanningResult;
+ }
+
private boolean appProvidedJsonProviderExists(List providers) {
boolean appProvidedJsonReaderExists = false;
for (ScannedSerializer provider : providers) {
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 8f582abc8e2bd..7d4574abb0d99 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
@@ -498,7 +498,7 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem,
: Collections.emptyMap())
.setResourceMethodCallback(new Consumer<>() {
@Override
- public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
+ public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) {
MethodInfo method = entry.getMethodInfo();
resourceMethodEntries.add(new ResteasyReactiveResourceMethodEntriesBuildItem.Entry(
@@ -545,18 +545,27 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) {
.build());
}
if (parameterType.name().equals(FILE)) {
- reflectiveClassBuildItemBuildProducer
- .produce(ReflectiveClassBuildItem
- .builder(entry.getActualEndpointInfo().name().toString())
- .constructors(false).methods().build());
+ minimallyRegisterResourceClassForReflection(entry,
+ reflectiveClassBuildItemBuildProducer);
}
}
if (filtersAccessResourceMethod) {
- reflectiveClassBuildItemBuildProducer.produce(
- ReflectiveClassBuildItem.builder(entry.getActualEndpointInfo().name().toString())
- .constructors(false).methods().build());
+ minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer);
}
+
+ if (entry.additionalRegisterClassForReflectionCheck()) {
+ minimallyRegisterResourceClassForReflection(entry, reflectiveClassBuildItemBuildProducer);
+ }
+ }
+
+ private void minimallyRegisterResourceClassForReflection(
+ EndpointIndexer.ResourceMethodCallbackEntry entry,
+ BuildProducer reflectiveClassBuildItemBuildProducer) {
+ reflectiveClassBuildItemBuildProducer
+ .produce(ReflectiveClassBuildItem
+ .builder(entry.getActualEndpointInfo().name().toString())
+ .constructors(false).methods().build());
}
private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName annotation) {
diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java
new file mode 100644
index 0000000000000..7e9ce98150490
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/InvalidJsonFromServerTest.java
@@ -0,0 +1,61 @@
+package io.quarkus.rest.client.reactive.jackson.test;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.ClientWebApplicationException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+/**
+ * Tests that when the server responds with data that is not valid JSON, we return an internal server error
+ */
+public class InvalidJsonFromServerTest {
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(JsonObject.class, JsonClient.class, InvalidJsonEndpoint.class));
+
+ @RestClient
+ JsonClient client;
+
+ @Test
+ public void test() {
+ assertThatThrownBy(() -> client.get())
+ .isInstanceOf(ClientWebApplicationException.class)
+ .hasMessageContaining("HTTP 200")
+ .cause()
+ .hasMessageContaining("was expecting double-quote to start field name");
+ }
+
+ @Path("/invalid-json")
+ @RegisterRestClient(baseUri = "http://localhost:8081")
+ public interface JsonClient {
+
+ @Produces(MediaType.APPLICATION_JSON)
+ @GET
+ JsonObject get();
+ }
+
+ static class JsonObject {
+ public String name;
+ }
+
+ @Path("/invalid-json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public static class InvalidJsonEndpoint {
+
+ @GET
+ public String get() {
+ return "{name: test}";
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java
index 19d3d79d72b64..5939422b47b3a 100644
--- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java
+++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java
@@ -14,11 +14,13 @@
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
+import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.ClientWebApplicationException;
import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;
+import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -26,6 +28,8 @@
public class ClientJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ClientRestHandler {
+ private static final Logger log = Logger.getLogger(ClientJacksonMessageBodyReader.class);
+
private final ConcurrentMap contextResolverMap = new ConcurrentHashMap<>();
private RestClientRequestContext context;
@@ -39,8 +43,11 @@ public Object readFrom(Class type, Type genericType, Annotation[] annota
MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
try {
return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
+ } catch (JsonParseException e) {
+ log.debug("Server returned invalid json data", e);
+ throw new ClientWebApplicationException(e, Response.Status.OK);
} catch (StreamReadException | DatabindException e) {
- throw new ClientWebApplicationException(e, Response.Status.BAD_REQUEST);
+ throw new ClientWebApplicationException(e, Response.Status.BAD_REQUEST); // TODO: we need to check if this actually makes sense...
}
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java
index 583aa2cfa6883..ae97af6d7c3b3 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java
@@ -90,12 +90,6 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance
throw new IllegalStateException(message);
}
- StringBuilder sigBuilder = new StringBuilder();
- sigBuilder.append(targetMethod.name()).append("_").append(targetMethod.returnType().name().toString());
- for (Type i : targetMethod.parameterTypes()) {
- sigBuilder.append(i.name().toString());
- }
-
int priority = Priorities.USER;
AnnotationValue priorityAnnotationValue = instance.value("priority");
if (priorityAnnotationValue != null) {
@@ -103,8 +97,7 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance
}
ClassInfo restClientInterfaceClassInfo = targetMethod.declaringClass();
- String generatedClassName = restClientInterfaceClassInfo.name().toString() + "_" + targetMethod.name() + "_"
- + "ResponseExceptionMapper" + "_" + HashUtil.sha1(sigBuilder.toString());
+ String generatedClassName = getGeneratedClassName(targetMethod);
try (ClassCreator cc = ClassCreator.builder().classOutput(classOutput).className(generatedClassName)
.interfaces(ResteasyReactiveResponseExceptionMapper.class).build()) {
MethodCreator toThrowable = cc.getMethodCreator("toThrowable", Throwable.class, Response.class,
@@ -143,6 +136,17 @@ GeneratedClassResult generateResponseExceptionMapper(AnnotationInstance instance
return new GeneratedClassResult(restClientInterfaceClassInfo.name().toString(), generatedClassName, priority);
}
+ public static String getGeneratedClassName(MethodInfo methodInfo) {
+ StringBuilder sigBuilder = new StringBuilder();
+ sigBuilder.append(methodInfo.name()).append("_").append(methodInfo.returnType().name().toString());
+ for (Type i : methodInfo.parameterTypes()) {
+ sigBuilder.append(i.name().toString());
+ }
+
+ return methodInfo.declaringClass().name().toString() + "_" + methodInfo.name() + "_"
+ + "ResponseExceptionMapper" + "_" + HashUtil.sha1(sigBuilder.toString());
+ }
+
private static boolean ignoreAnnotation(MethodInfo methodInfo) {
// ignore the annotation if it's placed on a Kotlin companion class
// this is not a problem since the Kotlin compiler will also place the annotation the static method interface method
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java
index 1d0b3f5d45d2c..df1a36f223dec 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java
@@ -10,6 +10,7 @@
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.annotation.RegisterProviders;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import org.jboss.jandex.DotName;
import io.quarkus.rest.client.reactive.ClientExceptionMapper;
@@ -32,6 +33,8 @@ public class DotNames {
public static final DotName CLIENT_EXCEPTION_MAPPER = DotName.createSimple(ClientExceptionMapper.class.getName());
public static final DotName CLIENT_REDIRECT_HANDLER = DotName.createSimple(ClientRedirectHandler.class.getName());
+ public static final DotName RESPONSE_EXCEPTION_MAPPER = DotName.createSimple(ResponseExceptionMapper.class.getName());
+
static final DotName METHOD = DotName.createSimple(Method.class.getName());
private DotNames() {
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
index 10beeb6e130d9..7bace127961f7 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
@@ -10,9 +10,12 @@
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS;
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER;
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS;
+import static io.quarkus.rest.client.reactive.deployment.DotNames.RESPONSE_EXCEPTION_MAPPER;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.*;
import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.CDI_WRAPPER_SUFFIX;
+import static org.jboss.resteasy.reactive.common.processor.JandexUtil.isImplementorOf;
+import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.BLOCKING;
import static org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner.BUILTIN_HTTP_ANNOTATIONS_TO_METHOD;
import java.lang.annotation.RetentionPolicy;
@@ -498,6 +501,24 @@ void addRestClientBeans(Capabilities capabilities,
}
}
}
+
+ Set blockingClassNames = new HashSet<>();
+ Set registerBlockingClasses = new HashSet<>(index.getAnnotations(BLOCKING));
+ for (AnnotationInstance registerBlockingClass : registerBlockingClasses) {
+ AnnotationTarget target = registerBlockingClass.target();
+ if (target.kind() == AnnotationTarget.Kind.CLASS
+ && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER)) {
+ // Watch for @Blocking annotations in classes that implements ResponseExceptionMapper:
+ blockingClassNames.add(target.asClass().toString());
+ } else if (target.kind() == AnnotationTarget.Kind.METHOD
+ && target.asMethod().annotation(CLIENT_EXCEPTION_MAPPER) != null) {
+ // Watch for @Blocking annotations in methods that are also annotated with @ClientExceptionMapper:
+ blockingClassNames.add(ClientExceptionMapperHandler.getGeneratedClassName(target.asMethod()));
+ }
+ }
+
+ recorder.setBlockingClassNames(blockingClassNames);
+
if (LaunchMode.current() == LaunchMode.DEVELOPMENT) {
recorder.setConfigKeys(configKeys);
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java
index 535ea3ebe2065..b56ac980cadb0 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java
@@ -2,10 +2,16 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.net.URI;
+import java.util.Arrays;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.CookieParam;
@@ -74,7 +80,7 @@ void shouldConvertHeaderParams() {
void shouldConvertCookieParams() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri)
.build(Client.class);
- assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1");
+ assertThat(client.getWithCookie(Param.FIRST)).isEqualTo("1");
assertThat(client.sub().getWithCookie(Param.SECOND)).isEqualTo("2");
Bean bean = new Bean();
@@ -93,7 +99,7 @@ interface Client {
@GET
@Path("/param/{param}")
- String get(@PathParam("param") Param param);
+ String get(@MyAnnotation("myValue") @PathParam("param") Param param);
@GET
@Path("/param/{param}")
@@ -101,7 +107,7 @@ interface Client {
@GET
@Path("/query")
- String getWithQuery(@QueryParam("param") Param param);
+ String getWithQuery(@MyAnnotation("myValue") @QueryParam("param") Param param);
@GET
@Path("/query")
@@ -109,7 +115,7 @@ interface Client {
@GET
@Path("/header")
- String getWithHeader(@HeaderParam("param") Param param);
+ String getWithHeader(@MyAnnotation("myValue") @HeaderParam("param") Param param);
@GET
@Path("/header")
@@ -117,7 +123,7 @@ interface Client {
@GET
@Path("/cookie")
- String getWithCookie(@HeaderParam("cookie-param") Param param);
+ String getWithCookie(@MyAnnotation("myValue") @CookieParam("cookie-param") Param param);
@GET
@Path("/cookie")
@@ -127,28 +133,32 @@ interface Client {
interface SubClient {
@GET
@Path("/param/{param}")
- String get(@PathParam("param") Param param);
+ String get(@MyAnnotation("myValue") @PathParam("param") Param param);
@GET
@Path("/query")
- String getWithQuery(@QueryParam("param") Param param);
+ String getWithQuery(@MyAnnotation("myValue") @QueryParam("param") Param param);
@GET
@Path("/header")
- String getWithHeader(@HeaderParam("param") Param param);
+ String getWithHeader(@MyAnnotation("myValue") @HeaderParam("param") Param param);
@GET
@Path("cookie")
- String getWithCookie(@CookieParam("cookie-param") Param param);
+ String getWithCookie(@MyAnnotation("myValue") @CookieParam("cookie-param") Param param);
}
public static class Bean {
+ @MyAnnotation("myValue")
@PathParam("param")
public Param param;
+ @MyAnnotation("myValue")
@QueryParam("param")
public Param queryParam;
+ @MyAnnotation("myValue")
@HeaderParam("param")
public Param headerParam;
+ @MyAnnotation("myValue")
@CookieParam("cookie-param")
public Param cookieParam;
}
@@ -158,6 +168,12 @@ enum Param {
SECOND
}
+ @Target({ ElementType.FIELD, ElementType.PARAMETER })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyAnnotation {
+ String value() default "";
+ }
+
public static class ParamConverter implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
@@ -171,6 +187,9 @@ public jakarta.ws.rs.ext.ParamConverter getConverter(Class rawType, Ty
fail("Annotations cannot be null!");
}
+ assertTrue(Arrays.stream(annotations)
+ .anyMatch(a -> a instanceof MyAnnotation && ((MyAnnotation) a).value().equals("myValue")));
+
if (rawType == Param.class) {
return (jakarta.ws.rs.ext.ParamConverter) new jakarta.ws.rs.ext.ParamConverter () {
@Override
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java
new file mode 100644
index 0000000000000..47b82d5993330
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/error/BlockingExceptionMapperTest.java
@@ -0,0 +1,253 @@
+package io.quarkus.rest.client.reactive.error;
+
+import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass;
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriBuilder;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.rest.client.reactive.ClientExceptionMapper;
+import io.quarkus.test.QuarkusUnitTest;
+import io.smallrye.common.annotation.Blocking;
+import io.vertx.core.Context;
+
+public class BlockingExceptionMapperTest {
+
+ private static final AtomicBoolean EVENT_LOOP_THREAD_USED_BY_MAPPER = new AtomicBoolean();
+ private static final int STATUS_FOR_BLOCKING_MAPPER = 501;
+ private static final int STATUS_FOR_NON_BLOCKING_MAPPER = 500;
+
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(Client.class,
+ ClientUsingNotBlockingExceptionMapper.class,
+ ClientUsingBlockingExceptionMapper.class,
+ ClientUsingBlockingExceptionMapperWithAnnotation.class,
+ ClientUsingBothExceptionMappers.class,
+ NotBlockingExceptionMapper.class,
+ BlockingExceptionMapper.class,
+ ClientResource.class,
+ Resource.class)
+ .addAsResource(
+ new StringAsset(setUrlForClass(ClientUsingNotBlockingExceptionMapper.class) + "\n"
+ + setUrlForClass(ClientUsingBlockingExceptionMapper.class) + "\n"
+ + setUrlForClass(ClientUsingBlockingExceptionMapperWithAnnotation.class) + "\n"
+ + setUrlForClass(ClientUsingBothExceptionMappers.class) + "\n"),
+ "application.properties"));
+
+ public static final String ERROR_MESSAGE = "The entity was not found";
+
+ @RestClient
+ ClientUsingNotBlockingExceptionMapper clientUsingNotBlockingExceptionMapper;
+
+ @RestClient
+ ClientUsingBlockingExceptionMapper clientUsingBlockingExceptionMapper;
+
+ @RestClient
+ ClientUsingBlockingExceptionMapperWithAnnotation clientUsingBlockingExceptionMapperWithAnnotation;
+
+ @RestClient
+ ClientUsingBothExceptionMappers clientUsingBothExceptionMappers;
+
+ @BeforeEach
+ public void setup() {
+ EVENT_LOOP_THREAD_USED_BY_MAPPER.set(false);
+ }
+
+ @Disabled("This test randomly fails because https://github.com/quarkusio/quarkus/issues/32839")
+ @Test
+ public void shouldUseEventLoopByDefault() {
+ assertThrows(BlockingNotAllowedException.class, clientUsingNotBlockingExceptionMapper::nonBlocking);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue();
+ }
+
+ @Test
+ public void shouldUseWorkerThreadIfExceptionMapperIsAnnotatedWithBlocking() {
+ RuntimeException exception = assertThrows(RuntimeException.class, clientUsingBlockingExceptionMapper::blocking);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse();
+ assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE);
+ }
+
+ @Test
+ public void shouldUseWorkerThreadOnlyIfExceptionMapperIsAnnotatedWithBlockingIsUsed() {
+ // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed:
+ // assertThrows(BlockingNotAllowedException.class, clientUsingBothExceptionMappers::nonBlocking);
+ // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue();
+
+ RuntimeException exception = assertThrows(RuntimeException.class, clientUsingBothExceptionMappers::blocking);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse();
+ assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE);
+ }
+
+ @Test
+ public void shouldUseWorkerThreadWhenClientIsInjected() {
+ // To be uncommented after https://github.com/quarkusio/quarkus/issues/32839 is fixed:
+ // given().get("/client/non-blocking").then().statusCode(500);
+ // assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isTrue();
+
+ given().get("/client/blocking").then().statusCode(500);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse();
+ }
+
+ @Test
+ public void shouldUseWorkerThreadIfExceptionMapperIsAnnotatedWithBlockingAndUsingClientExceptionMapper() {
+ RuntimeException exception = assertThrows(RuntimeException.class,
+ clientUsingBlockingExceptionMapperWithAnnotation::blocking);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse();
+ assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE);
+ }
+
+ @Test
+ public void shouldUseWorkerThreadUsingProgrammaticApproach() {
+ var client = RestClientBuilder.newBuilder()
+ .baseUri(UriBuilder.fromUri("http://localhost:8081").build())
+ .register(BlockingExceptionMapper.class)
+ .build(Client.class);
+
+ RuntimeException exception = assertThrows(RuntimeException.class, client::blocking);
+ assertThat(EVENT_LOOP_THREAD_USED_BY_MAPPER.get()).isFalse();
+ assertThat(exception.getMessage()).isEqualTo(ERROR_MESSAGE);
+ }
+
+ @Path("/error")
+ @RegisterRestClient
+ public interface Client {
+ @GET
+ @Path("/blocking")
+ InputStream blocking();
+ }
+
+ @Path("/error")
+ @RegisterRestClient
+ @RegisterProvider(NotBlockingExceptionMapper.class)
+ public interface ClientUsingNotBlockingExceptionMapper {
+
+ @GET
+ @Path("/non-blocking")
+ InputStream nonBlocking();
+ }
+
+ @Path("/error")
+ @RegisterRestClient
+ @RegisterProvider(BlockingExceptionMapper.class)
+ public interface ClientUsingBlockingExceptionMapper {
+ @GET
+ @Path("/blocking")
+ InputStream blocking();
+ }
+
+ @Path("/error")
+ @RegisterRestClient
+ @RegisterProvider(NotBlockingExceptionMapper.class)
+ @RegisterProvider(BlockingExceptionMapper.class)
+ public interface ClientUsingBothExceptionMappers {
+ @GET
+ @Path("/blocking")
+ InputStream blocking();
+
+ @GET
+ @Path("/non-blocking")
+ InputStream nonBlocking();
+ }
+
+ @Path("/error")
+ @RegisterRestClient
+ public interface ClientUsingBlockingExceptionMapperWithAnnotation {
+ @GET
+ @Path("/blocking")
+ InputStream blocking();
+
+ @Blocking
+ @ClientExceptionMapper
+ static RuntimeException map(Response response) {
+ EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread());
+ return new RuntimeException(response.readEntity(String.class));
+ }
+ }
+
+ public static class NotBlockingExceptionMapper implements ResponseExceptionMapper {
+
+ @Override
+ public boolean handles(int status, MultivaluedMap headers) {
+ return status == STATUS_FOR_NON_BLOCKING_MAPPER;
+ }
+
+ @Override
+ public Exception toThrowable(Response response) {
+ EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread());
+ // Reading InputStream in the Event Loop throws the BlockingNotAllowedException exception
+ response.readEntity(String.class);
+ return null;
+ }
+ }
+
+ @Blocking
+ public static class BlockingExceptionMapper implements ResponseExceptionMapper {
+ @Override
+ public boolean handles(int status, MultivaluedMap headers) {
+ return status == STATUS_FOR_BLOCKING_MAPPER;
+ }
+
+ @Override
+ public Exception toThrowable(Response response) {
+ EVENT_LOOP_THREAD_USED_BY_MAPPER.set(Context.isOnEventLoopThread());
+ return new RuntimeException(response.readEntity(String.class));
+ }
+ }
+
+ @Path("/error")
+ public static class Resource {
+
+ @GET
+ @Path("/blocking")
+ public Response blocking() {
+ return Response.status(STATUS_FOR_BLOCKING_MAPPER).entity(ERROR_MESSAGE).build();
+ }
+
+ @GET
+ @Path("/non-blocking")
+ public Response nonBlocking() {
+ return Response.status(STATUS_FOR_NON_BLOCKING_MAPPER).entity(ERROR_MESSAGE).build();
+ }
+ }
+
+ @Path("/client")
+ public static class ClientResource {
+
+ @RestClient
+ ClientUsingBothExceptionMappers clientUsingBothExceptionMappers;
+
+ @GET
+ @Path("/blocking")
+ public void callBlocking() {
+ clientUsingBothExceptionMappers.blocking();
+ }
+
+ @GET
+ @Path("/non-blocking")
+ public void callNonBlocking() {
+ clientUsingBothExceptionMappers.nonBlocking();
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java
new file mode 100644
index 0000000000000..d8cf3b8305253
--- /dev/null
+++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/ClientUseWorkerExecutorRestHandler.java
@@ -0,0 +1,41 @@
+package io.quarkus.rest.client.reactive.runtime;
+
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
+
+import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
+import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
+
+import io.quarkus.runtime.ExecutorRecorder;
+import io.vertx.core.Context;
+
+/**
+ * This is added by the Reactive Rest Client if the `@Blocking` annotation is used in some scenarios. For example, when users
+ * provide a custom ResponseExceptionMapper that is annotates with the `@Blocking` annotation.
+ *
+ * Then this handler is applied, the execution of the next handlers will use the worker thread pool.
+ */
+public class ClientUseWorkerExecutorRestHandler implements ClientRestHandler {
+
+ private volatile Executor executor;
+ private final Supplier supplier = new Supplier() {
+ @Override
+ public Executor get() {
+ return ExecutorRecorder.getCurrent();
+ }
+ };
+
+ @Override
+ public void handle(RestClientRequestContext requestContext) throws Exception {
+ if (!Context.isOnEventLoopThread()) {
+ return; //already dispatched
+ }
+
+ if (executor == null) {
+ executor = supplier.get();
+ }
+
+ requestContext.suspend();
+ requestContext.resume(executor);
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java
index 8e78ea877f6df..1d369c71e5a47 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientResponseFilter.java
@@ -1,6 +1,7 @@
package io.quarkus.rest.client.reactive.runtime;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import jakarta.ws.rs.client.ClientRequestContext;
@@ -11,10 +12,14 @@
import org.jboss.resteasy.reactive.client.handlers.ClientResponseCompleteRestHandler;
import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl;
import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
+import org.jboss.resteasy.reactive.client.spi.ClientRestHandler;
import org.jboss.resteasy.reactive.common.core.UnwrappableException;
import org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl;
+import io.vertx.core.Context;
+
public class MicroProfileRestClientResponseFilter implements ClientResponseFilter {
+ private static final ClientRestHandler[] EMPTY_CLIENT_REST_HANDLERS = new ClientRestHandler[0];
private final List> exceptionMappers;
public MicroProfileRestClientResponseFilter(List> exceptionMappers) {
@@ -29,21 +34,47 @@ public MicroProfileRestClientResponseFilter(List> exc
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
for (ResponseExceptionMapper exceptionMapper : exceptionMappers) {
if (exceptionMapper.handles(responseContext.getStatus(), responseContext.getHeaders())) {
- // we have an exception mapper, we don't need the response anymore, we can map it to response right away (I hope :D)
RestClientRequestContext restClientContext = ((ClientRequestContextImpl) requestContext)
.getRestClientRequestContext();
- ResponseImpl response = ClientResponseCompleteRestHandler.mapToResponse(restClientContext, false);
- Throwable throwable;
- if (exceptionMapper instanceof ResteasyReactiveResponseExceptionMapper) {
- throwable = ((ResteasyReactiveResponseExceptionMapper) exceptionMapper).toThrowable(response,
- restClientContext);
+
+ boolean requiresBlocking = RestClientRecorder.isClassBlocking(exceptionMapper.getClass());
+ if (Context.isOnEventLoopThread() && requiresBlocking) {
+ switchToWorkerThreadPoolAndRetry(restClientContext);
+ break;
} else {
- throwable = exceptionMapper.toThrowable(response);
- }
- if (throwable != null) {
- throw new UnwrappableException(throwable);
+ // we have an exception mapper, we don't need the response anymore, we can map it to response right away (I hope :D)
+ ResponseImpl response = ClientResponseCompleteRestHandler.mapToResponse(restClientContext, false);
+ Throwable throwable;
+ if (exceptionMapper instanceof ResteasyReactiveResponseExceptionMapper) {
+ throwable = ((ResteasyReactiveResponseExceptionMapper) exceptionMapper).toThrowable(response,
+ restClientContext);
+ } else {
+ throwable = exceptionMapper.toThrowable(response);
+ }
+ if (throwable != null) {
+ throw new UnwrappableException(throwable);
+ }
}
}
}
}
+
+ private void switchToWorkerThreadPoolAndRetry(RestClientRequestContext restClientContext) {
+ int position = restClientContext.getPosition();
+
+ List nextHandlers = new ArrayList<>(2 + restClientContext.getHandlers().length - position);
+ nextHandlers.add(new ClientUseWorkerExecutorRestHandler());
+ nextHandlers.add(currentHandler(restClientContext));
+
+ while (position < restClientContext.getHandlers().length) {
+ nextHandlers.add(restClientContext.getHandlers()[position]);
+ position++;
+ }
+
+ restClientContext.restart(nextHandlers.toArray(EMPTY_CLIENT_REST_HANDLERS), true);
+ }
+
+ private ClientRestHandler currentHandler(RestClientRequestContext restClientContext) {
+ return restClientContext.getHandlers()[restClientContext.getPosition() - 1];
+ }
}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java
index e310af1d53137..6049e880488f1 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientRecorder.java
@@ -1,6 +1,7 @@
package io.quarkus.rest.client.reactive.runtime;
import java.util.Map;
+import java.util.Set;
import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver;
@@ -9,15 +10,24 @@
@Recorder
public class RestClientRecorder {
private static volatile Map configKeys;
+ private static volatile Set blockingClassNames;
public void setConfigKeys(Map configKeys) {
RestClientRecorder.configKeys = configKeys;
}
+ public void setBlockingClassNames(Set blockingClassNames) {
+ RestClientRecorder.blockingClassNames = blockingClassNames;
+ }
+
public static Map getConfigKeys() {
return configKeys;
}
+ public static boolean isClassBlocking(Class> exceptionMapperClass) {
+ return blockingClassNames.contains(exceptionMapperClass.getName());
+ }
+
public void setRestClientBuilderResolver() {
RestClientBuilderResolver.setInstance(new BuilderResolver());
}
diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
index a240bf2cabc32..1b9a6f21932e7 100644
--- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
+++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java
@@ -214,6 +214,11 @@ private static void prepareBouncyCastleProvider(CurateOutcomeBuildItem curateOut
.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.jcajce.provider.drbg.DRBG$Default"));
runtimeReInitialized
.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV"));
+ // URLSeededEntropySourceProvider.seedStream may contain a reference to a 'FileInputStream' which includes
+ // references to FileDescriptors which aren't allowed in the image heap
+ runtimeReInitialized
+ .produce(new RuntimeReinitializedClassBuildItem(
+ "org.bouncycastle.jcajce.provider.drbg.DRBG$URLSeededEntropySourceProvider"));
} else {
reflection.produce(ReflectiveClassBuildItem.builder("org.bouncycastle.crypto.general.AES")
.methods().fields().build());
diff --git a/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java b/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java
index 39254abe27a81..a1e2f4ab5750a 100644
--- a/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java
+++ b/extensions/smallrye-stork/deployment/src/main/java/io/quarkus/stork/deployment/SmallRyeStorkProcessor.java
@@ -26,8 +26,10 @@
import io.quarkus.vertx.deployment.VertxBuildItem;
import io.smallrye.stork.spi.LoadBalancerProvider;
import io.smallrye.stork.spi.ServiceDiscoveryProvider;
+import io.smallrye.stork.spi.ServiceRegistrarProvider;
import io.smallrye.stork.spi.internal.LoadBalancerLoader;
import io.smallrye.stork.spi.internal.ServiceDiscoveryLoader;
+import io.smallrye.stork.spi.internal.ServiceRegistrarLoader;
public class SmallRyeStorkProcessor {
@@ -35,10 +37,11 @@ public class SmallRyeStorkProcessor {
private static final Logger LOGGER = Logger.getLogger(SmallRyeStorkProcessor.class.getName());
@BuildStep
- void registerServiceProviders(BuildProducer services, Capabilities capabilities) {
+ void registerServiceProviders(BuildProducer services) {
services.produce(new ServiceProviderBuildItem(io.smallrye.stork.spi.config.ConfigProvider.class.getName(),
StorkConfigProvider.class.getName()));
- for (Class> providerClass : asList(LoadBalancerLoader.class, ServiceDiscoveryLoader.class)) {
+ for (Class> providerClass : asList(LoadBalancerLoader.class, ServiceDiscoveryLoader.class,
+ ServiceRegistrarLoader.class)) {
services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(providerClass.getName()));
}
}
@@ -47,11 +50,15 @@ void registerServiceProviders(BuildProducer services,
UnremovableBeanBuildItem unremoveableBeans() {
return UnremovableBeanBuildItem.beanTypes(
DotName.createSimple(ServiceDiscoveryProvider.class),
- DotName.createSimple(LoadBalancerProvider.class));
+ DotName.createSimple(ServiceDiscoveryLoader.class),
+ DotName.createSimple(LoadBalancerProvider.class),
+ DotName.createSimple(LoadBalancerLoader.class),
+ DotName.createSimple(ServiceRegistrarProvider.class),
+ DotName.createSimple(ServiceRegistrarLoader.class));
}
/**
- * This build step is the fix for https://github.com/quarkusio/quarkus/issues/24444.
+ * This build step is the fix for #24444 .
* Because Stork itself cannot depend on Quarkus, and we do not want to have extensions for all the service
* discovery and load-balancer providers, we work around the issue by detecting when the kubernetes service
* discovery is used and if the kubernetes extension is used.
diff --git a/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java b/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java
index f0db4f8b07070..5846c627da36b 100644
--- a/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java
+++ b/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/UndertowStaticResourcesBuildStep.java
@@ -12,6 +12,8 @@
import java.util.List;
import java.util.Set;
+import io.quarkus.bootstrap.classloading.ClassPathElement;
+import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.ApplicationArchive;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
@@ -22,7 +24,6 @@
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.runtime.LaunchMode;
-import io.quarkus.runtime.util.ClassPathUtils;
/**
* NOTE: Shared with Resteasy standalone!
@@ -56,8 +57,8 @@ void scanStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem
}
//we need to check for web resources in order to get welcome files to work
//this kinda sucks
- Set knownFiles = new HashSet<>();
- Set knownDirectories = new HashSet<>();
+ final Set knownFiles = new HashSet<>();
+ final Set knownDirectories = new HashSet<>();
for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) {
i.accept(tree -> {
Path resource = tree.getPath(META_INF_RESOURCES);
@@ -67,9 +68,14 @@ void scanStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem
});
}
- ClassPathUtils.consumeAsPaths(META_INF_RESOURCES, resource -> {
- collectKnownPaths(resource, knownFiles, knownDirectories);
- });
+ for (ClassPathElement e : QuarkusClassLoader.getElements(META_INF_RESOURCES, false)) {
+ if (e.isRuntime()) {
+ e.apply(tree -> {
+ collectKnownPaths(tree.getPath(META_INF_RESOURCES), knownFiles, knownDirectories);
+ return null;
+ });
+ }
+ }
for (GeneratedWebResourceBuildItem genResource : generatedWebResources) {
String sub = genResource.getName();
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java
index 78074c4e63ed4..e95d614d3389a 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java
@@ -1,5 +1,20 @@
package io.quarkus.devui.deployment;
+import static java.util.logging.Level.ALL;
+import static java.util.logging.Level.CONFIG;
+import static java.util.logging.Level.FINE;
+import static java.util.logging.Level.FINER;
+import static java.util.logging.Level.FINEST;
+import static java.util.logging.Level.OFF;
+import static java.util.logging.Level.SEVERE;
+import static java.util.logging.Level.WARNING;
+import static org.jboss.logmanager.Level.DEBUG;
+import static org.jboss.logmanager.Level.ERROR;
+import static org.jboss.logmanager.Level.FATAL;
+import static org.jboss.logmanager.Level.INFO;
+import static org.jboss.logmanager.Level.TRACE;
+import static org.jboss.logmanager.Level.WARN;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -32,7 +47,10 @@
import io.quarkus.devui.spi.DevUIContent;
import io.quarkus.devui.spi.buildtime.QuteTemplateBuildItem;
import io.quarkus.devui.spi.buildtime.StaticContentBuildItem;
+import io.quarkus.devui.spi.page.AbstractPageBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
+import io.quarkus.devui.spi.page.FooterPageBuildItem;
+import io.quarkus.devui.spi.page.MenuPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.devui.spi.page.PageBuilder;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
@@ -67,6 +85,8 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu
internalImportMapBuildItem.add("qwc-server-log", contextRoot + "qwc/qwc-server-log.js");
// Quarkus UI
internalImportMapBuildItem.add("qui/", contextRoot + "qui/");
+ internalImportMapBuildItem.add("qui-card", contextRoot + "qui/qui-card.js");
+
internalImportMapBuildItem.add("qui-badge", contextRoot + "qui/qui-badge.js");
internalImportMapBuildItem.add("qui-alert", contextRoot + "qui/qui-alert.js");
internalImportMapBuildItem.add("qui-code-block", contextRoot + "qui/qui-code-block.js");
@@ -103,13 +123,31 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu
* @param buildTimeConstProducer
*/
@BuildStep(onlyIf = IsDevelopment.class)
- void mapPageBuildTimeData(List pageBuildItems,
+ void mapPageBuildTimeData(List cards,
+ List menus,
+ List footers,
CurateOutcomeBuildItem curateOutcomeBuildItem,
BuildProducer buildTimeConstProducer) {
- for (CardPageBuildItem pageBuildItem : pageBuildItems) {
- String extensionPathName = pageBuildItem.getExtensionPathName(curateOutcomeBuildItem);
- Map buildTimeData = getBuildTimeData(curateOutcomeBuildItem, pageBuildItem);
+ for (CardPageBuildItem card : cards) {
+ String extensionPathName = card.getExtensionPathName(curateOutcomeBuildItem);
+ Map buildTimeData = getBuildTimeDataForCard(curateOutcomeBuildItem, card);
+ if (!buildTimeData.isEmpty()) {
+ buildTimeConstProducer.produce(
+ new BuildTimeConstBuildItem(extensionPathName, buildTimeData));
+ }
+ }
+ for (MenuPageBuildItem menu : menus) {
+ String extensionPathName = menu.getExtensionPathName(curateOutcomeBuildItem);
+ Map buildTimeData = getBuildTimeDataForPage(menu);
+ if (!buildTimeData.isEmpty()) {
+ buildTimeConstProducer.produce(
+ new BuildTimeConstBuildItem(extensionPathName, buildTimeData));
+ }
+ }
+ for (FooterPageBuildItem footer : footers) {
+ String extensionPathName = footer.getExtensionPathName(curateOutcomeBuildItem);
+ Map buildTimeData = getBuildTimeDataForPage(footer);
if (!buildTimeData.isEmpty()) {
buildTimeConstProducer.produce(
new BuildTimeConstBuildItem(extensionPathName, buildTimeData));
@@ -117,12 +155,17 @@ void mapPageBuildTimeData(List pageBuildItems,
}
}
- private Map getBuildTimeData(CurateOutcomeBuildItem curateOutcomeBuildItem,
- CardPageBuildItem pageBuildItem) {
+ private Map getBuildTimeDataForPage(AbstractPageBuildItem pageBuildItem) {
Map m = new HashMap<>();
if (pageBuildItem.hasBuildTimeData()) {
m.putAll(pageBuildItem.getBuildTimeData());
}
+ return m;
+ }
+
+ private Map getBuildTimeDataForCard(CurateOutcomeBuildItem curateOutcomeBuildItem,
+ CardPageBuildItem pageBuildItem) {
+ Map m = getBuildTimeDataForPage(pageBuildItem);
if (pageBuildItem.getOptionalCard().isPresent()) {
// Make the pages available for the custom card
@@ -383,6 +426,7 @@ private void addFooterTabBuildTimeData(BuildTimeConstBuildItem internalBuildTime
}
internalBuildTimeData.addBuildTimeData("footerTabs", footerTabs);
+ internalBuildTimeData.addBuildTimeData("loggerLevels", LEVELS);
}
private void addVersionInfoBuildTimeData(BuildTimeConstBuildItem internalBuildTimeData,
@@ -398,6 +442,22 @@ private void addVersionInfoBuildTimeData(BuildTimeConstBuildItem internalBuildTi
internalBuildTimeData.addBuildTimeData("applicationInfo", applicationInfo);
}
+ private static final List