diff --git a/.github/native-tests.json b/.github/native-tests.json
index 87547f9ca469d..cd9b3beb494a9 100644
--- a/.github/native-tests.json
+++ b/.github/native-tests.json
@@ -99,7 +99,7 @@
{
"category": "Misc1",
"timeout": 70,
- "test-modules": "maven, jackson, jsonb, kotlin-serialization, rest-client-reactive-kotlin-serialization, quartz, qute, logging-min-level-unset, logging-min-level-set, simple with space",
+ "test-modules": "maven, jackson, jsonb, kotlin, rest-client-reactive-kotlin-serialization, quartz, qute, logging-min-level-unset, logging-min-level-set, simple with space",
"os-name": "ubuntu-latest"
},
{
diff --git a/.github/quarkus-github-lottery.yml b/.github/quarkus-github-lottery.yml
index e8f8e098d1896..3b59421c5b20b 100644
--- a/.github/quarkus-github-lottery.yml
+++ b/.github/quarkus-github-lottery.yml
@@ -7,6 +7,10 @@ buckets:
delay: PT0S
timeout: P3D
maintenance:
+ created:
+ delay: PT0S
+ timeout: P1D
+ expiry: P14D
feedback:
labels: ["triage/needs-reproducer", "triage/needs-feedback"]
needed:
@@ -30,18 +34,22 @@ participants:
maintenance:
labels: ["area/hibernate-orm", "area/hibernate-search", "area/elasticsearch", "area/jdbc"]
days: ["WEDNESDAY"]
+ created:
+ maxIssues: 3
feedback:
needed:
- maxIssues: 10
+ maxIssues: 3
provided:
- maxIssues: 10
+ maxIssues: 3
stale:
- maxIssues: 5
+ maxIssues: 1
- username: "marko-bekhta"
timezone: "Europe/Warsaw"
maintenance:
labels: ["area/hibernate-search", "area/elasticsearch", "area/hibernate-validator"]
days: ["WEDNESDAY"]
+ created:
+ maxIssues: 3
feedback:
needed:
maxIssues: 10
@@ -57,6 +65,8 @@ participants:
maintenance:
labels: ["area/hibernate-validator", "area/jakarta"]
days: ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"]
+ created:
+ maxIssues: 2
feedback:
needed:
maxIssues: 4
@@ -120,6 +130,8 @@ participants:
maintenance:
labels: ["area/core", "area/testing", "area/kotlin", "area/spring", "area/rest", "area/kubernetes"]
days: ["WEDNESDAY", "FRIDAY"]
+ created:
+ maxIssues: 2
feedback:
needed:
maxIssues: 4
diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml
index df2ab3edf07bd..ab05cfdb4c270 100644
--- a/.github/workflows/ci-actions-incremental.yml
+++ b/.github/workflows/ci-actions-incremental.yml
@@ -315,13 +315,13 @@ jobs:
elif [ "${GIB_IMPACTED_MODULES}" != '_all_' ]
then
# Important: keep -pl ... in actual jobs in sync with the following grep commands!
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -qPv 'integration-tests/(devtools|gradle|maven|devmode|kubernetes/.*)|tcks/.*'; then run_jvm=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'integration-tests/devtools'; then run_devtools=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'integration-tests/gradle'; then run_gradle=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -qP 'integration-tests/(maven|devmode)'; then run_maven=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -qP 'integration-tests/kubernetes/.*'; then run_kubernetes=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -qPv '(docs|integration-tests|tcks)/.*'; then run_quickstarts=false; fi
- if ! echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'tcks/.*'; then run_tcks=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -qPv 'integration-tests/(devtools|gradle|maven|devmode|kubernetes/.*)|tcks/.*'); then run_jvm=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'integration-tests/devtools'); then run_devtools=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'integration-tests/gradle'); then run_gradle=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -qP 'integration-tests/(maven|devmode)'); then run_maven=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -qP 'integration-tests/kubernetes/.*'); then run_kubernetes=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -qPv '(docs|integration-tests|tcks)/.*'); then run_quickstarts=false; fi
+ if ! (echo -n "${GIB_IMPACTED_MODULES}" | grep -q 'tcks/.*'); then run_tcks=false; fi
fi
echo "run_jvm=${run_jvm}, run_devtools=${run_devtools}, run_gradle=${run_gradle}, run_maven=${run_maven}, run_kubernetes=${run_kubernetes}, run_quickstarts=${run_quickstarts}, run_tcks=${run_tcks}"
echo "run_jvm=${run_jvm}" >> $GITHUB_OUTPUT
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index e512e56a78585..9c438a9c1d96d 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -2,7 +2,7 @@
com.gradledevelocity-maven-extension
- 1.22.2
+ 1.23com.gradle
diff --git a/MAINTAINERS.adoc b/MAINTAINERS.adoc
index fa8a42f66b7e7..49d99406a5265 100644
--- a/MAINTAINERS.adoc
+++ b/MAINTAINERS.adoc
@@ -100,9 +100,6 @@ If you think some information is outdated, either provide a pull request or emai
|Jackson
|https://github.com/geoand[Georgios Andrianakis], https://github.com/gsmet[Guillaume Smet]
-|Jaeger
-|https://github.com/objectiser[Gary Brown]
-
|JAXB
|https://github.com/gsmet[Guillaume Smet]
@@ -121,12 +118,6 @@ If you think some information is outdated, either provide a pull request or emai
|JDBC - SQL Server
|https://github.com/Sanne[Sanne Grinovero]
-|JGit
-|https://github.com/gastaldi[George Gastaldi]
-
-|JSch
-|https://github.com/gastaldi[George Gastaldi]
-
|JSON-B
|https://github.com/geoand[Georgios Andrianakis], https://github.com/gsmet[Guillaume Smet]
diff --git a/README.md b/README.md
index bd2037b525683..663a5c0283b13 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,9 @@
[![Project Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?style=for-the-badge&logo=zulip)](https://quarkusio.zulipchat.com/)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?style=for-the-badge&logo=gitpod&logoColor=white)](https://gitpod.io/#https://github.com/quarkusio/quarkus/-/tree/main/)
[![Supported JVM Versions](https://img.shields.io/badge/JVM-17--21-brightgreen.svg?style=for-the-badge&logo=openjdk)](https://github.com/quarkusio/quarkus/actions/runs/113853915/)
-[![Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?style=for-the-badge&logo=gradle)](https://ge.quarkus.io/scans)
+[![Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-007EC5?style=for-the-badge&logo=gradle)](https://ge.quarkus.io/scans)
[![GitHub Repo stars](https://img.shields.io/github/stars/quarkusio/quarkus?style=for-the-badge)](https://github.com/quarkusio/quarkus/stargazers)
+[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Quarkus%20Guru-007EC5?style=for-the-badge)](https://gurubase.io/g/quarkus)
# Quarkus - Supersonic Subatomic Java
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index b1f09a9da9249..fa0dd3f42dde1 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -133,6 +133,31 @@ Stop the application with `CTRL+C` once you have gathered what you want (e.g. ju
It will dump the profiling information.
The name of the file is in the `file=` parameter.
+> [!TIP]
+> You can avoid needing to stop the application manually by writing a custom main that stops the application once it boots up
+>
+> ```java
+> import io.quarkus.runtime.Quarkus;
+> import io.quarkus.runtime.QuarkusApplication;
+> import io.quarkus.runtime.annotations.QuarkusMain;
+>
+> @QuarkusMain
+> public class Main {
+>
+> public static void main(String... args) {
+> Quarkus.run(MyApp.class, args);
+> }
+>
+> public static class MyApp implements QuarkusApplication {
+>
+> @Override
+> public int run(String... args) throws Exception {
+> return 0;
+> }
+> }
+> }
+```
+
For the allocation case, you obtain a JFR file, you can convert it to your typical Async Profiler flamegraph HTML output with:
```shell script
diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index fb54502ae14b7..5a8aebeed6654 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -27,12 +27,12 @@
11.1.73.0.0.Final
- 3.1.3.Final
+ 3.2.0.Final6.2.11.Final2.8.0-alpha1.27.0-alpha
- 5.3.3
- 1.14.1
+ 5.3.4
+ 1.14.22.2.20.22.022.2
@@ -46,19 +46,19 @@
2.12.04.0.2
- 2.8.0
+ 2.9.03.10.24.1.04.0.0
- 4.0.3
+ 4.0.52.11.0
- 6.7.0
+ 6.7.14.6.12.1.21.0.133.0.13.17.1
- 4.25.0
+ 4.26.02.7.02.1.33.0.0
@@ -99,7 +99,7 @@
0.0.9.Final2.58.0.0.Final
- 8.16.1
+ 8.17.02.2.212.2.5.Final2.2.2.SP01
@@ -119,7 +119,7 @@
2.3.23042.7.4
- 3.5.1
+ 3.4.18.3.012.8.1.jre111.6.7
@@ -138,7 +138,7 @@
3.6.1.Final2.7.04.0.5
- 3.7.1
+ 3.7.21.8.01.1.10.50.107.0
@@ -157,8 +157,8 @@
4.1.43.2.04.2.2
- 3.1.0.Final
- 11.0.0
+ 3.1.1.Final
+ 11.1.03.0.44.29.1
@@ -167,7 +167,7 @@
6.0.05.2.10.34.1
- 3.26.2
+ 3.26.30.3.04.18.16.1.SP4
@@ -182,18 +182,18 @@
3.48.32.36.00.27.2
- 1.45.1
+ 1.45.32.14.7.61.1.41.27.1
- 1.12.0
+ 1.13.02.11.02.0.1.Final
- 2.24.2
+ 2.24.31.3.1.Final1.12.0
- 2.6.5.Final
+ 2.6.6.Final0.1.18.Final1.20.43.4.0
@@ -203,7 +203,7 @@
2.8.22.62.4.0
- 7.0.0.202409031743-r
+ 7.1.0.202411261347-r0.15.09.47
@@ -214,11 +214,12 @@
0.8.111.1.03.3.0
- 2.12.3
+ 2.12.40.16.01.0.11
+ 0.28.0.RELEASE
@@ -723,6 +724,11 @@
quarkus-agroal${project.version}
+
+ io.quarkus
+ quarkus-agroal-dev
+ ${project.version}
+ io.quarkusquarkus-agroal-deployment
@@ -3284,7 +3290,7 @@
io.quarkus
- quarkus-junit5-properties
+ quarkus-junit5-config${project.version}
@@ -6509,6 +6515,18 @@
${project.version}
+
+
+ com.webauthn4j
+ webauthn4j-core-async
+ ${webauthn4j.version}
+
+
+ com.webauthn4j
+ webauthn4j-metadata-async
+ ${webauthn4j.version}
+
+
io.quarkus
@@ -6532,6 +6550,7 @@
${project.version}
+
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 332c47bfc7e08..dfbc9cd07aa5c 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -21,7 +21,7 @@
${version.compiler.plugin}2.0.21
- 1.9.20
+ 2.0.02.13.124.9.2
@@ -34,7 +34,7 @@
2.5.134.7.0
- 3.26.2
+ 3.26.32.0.3.Final6.0.1
@@ -101,9 +101,9 @@
quay.io/keycloak/keycloak:${keycloak.version}quay.io/keycloak/keycloak:${keycloak.wildfly.version}-legacy
- 7.0.1
+ 7.0.2
- 3.26.3
+ 3.27.03.10.07.3.0
@@ -116,7 +116,7 @@
2.0.00.45.1
- 3.8.1
+ 3.9.00.14.7
@@ -660,6 +660,11 @@
+
+ io.smallrye.certs
+ smallrye-certificate-generator-maven-plugin
+ ${smallrye-certificate-generator.version}
+
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsLocalDevelopment.java b/core/deployment/src/main/java/io/quarkus/deployment/IsLocalDevelopment.java
new file mode 100644
index 0000000000000..626b76a2af00a
--- /dev/null
+++ b/core/deployment/src/main/java/io/quarkus/deployment/IsLocalDevelopment.java
@@ -0,0 +1,27 @@
+package io.quarkus.deployment;
+
+import java.util.function.BooleanSupplier;
+
+import io.quarkus.dev.spi.DevModeType;
+import io.quarkus.runtime.LaunchMode;
+
+/**
+ * Similar to {@link IsDevelopment} except checks whether the application is being launched in dev mode but not from a
+ * {@code mutable-jar} package,
+ * in other words, not a remote server in a remote dev session.
+ */
+public class IsLocalDevelopment implements BooleanSupplier {
+
+ private final LaunchMode launchMode;
+ private final DevModeType devModeType;
+
+ public IsLocalDevelopment(LaunchMode launchMode, DevModeType devModeType) {
+ this.launchMode = launchMode;
+ this.devModeType = devModeType;
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ return launchMode == LaunchMode.DEVELOPMENT && devModeType != DevModeType.REMOTE_SERVER_SIDE;
+ }
+}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesSharedNetworkBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesSharedNetworkBuildItem.java
index df98eb89db333..e6a60b50116c6 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesSharedNetworkBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/DevServicesSharedNetworkBuildItem.java
@@ -10,6 +10,7 @@
import io.quarkus.builder.BuildStepBuilder;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.dev.devservices.DevServicesConfig;
+import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
/**
* A marker build item that indicates, if any instances are provided during the build, the containers started by DevServices
@@ -81,4 +82,17 @@ public static boolean isSharedNetworkRequired(
(!devServicesSharedNetworkBuildItem.isEmpty()
&& devServicesSharedNetworkBuildItem.get(0).getSource().equals("io.quarkus.test.junit"));
}
+
+ /**
+ * @deprecated Please, use {@link DevServicesSharedNetworkBuildItem#isSharedNetworkRequired(DevServicesConfig, List)}
+ * instead.
+ */
+ @Deprecated(forRemoval = true, since = "3.18")
+ public static boolean isSharedNetworkRequired(
+ GlobalDevServicesConfig globalDevServicesConfig,
+ List devServicesSharedNetworkBuildItem) {
+ return globalDevServicesConfig.launchOnSharedNetwork ||
+ (!devServicesSharedNetworkBuildItem.isEmpty()
+ && devServicesSharedNetworkBuildItem.get(0).getSource().equals("io.quarkus.test.junit"));
+ }
}
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java
index 9227f0b1a06e1..12594375ee6f0 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/ContainerInfo.java
@@ -100,7 +100,7 @@ public String formatPorts() {
return Arrays.stream(getExposedPorts())
.filter(p -> p.getPublicPort() != null)
.map(c -> c.getIp() + ":" + c.getPublicPort() + "->" + c.getPrivatePort() + "/" + c.getType())
- .collect(Collectors.joining(" ,"));
+ .collect(Collectors.joining(", "));
}
public static class ContainerPort {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/GlobalDevServicesConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/GlobalDevServicesConfig.java
index 5f8d619e0b74c..593b8f3514f14 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/GlobalDevServicesConfig.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/devservices/GlobalDevServicesConfig.java
@@ -12,7 +12,7 @@
*
* @deprecated Please, use {@link DevServicesConfig} instead.
*/
-@Deprecated(forRemoval = true)
+@Deprecated(forRemoval = true, since = "3.18")
@ConfigRoot(name = "devservices")
public class GlobalDevServicesConfig {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConfig.java
index da1a31e3f43d9..f93894a7a1e72 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConfig.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConfig.java
@@ -8,7 +8,9 @@
import io.quarkus.runtime.annotations.ConfigDocMapKey;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.quarkus.runtime.configuration.TrimmedStringConverter;
import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import io.smallrye.config.WithParentName;
@@ -45,6 +47,16 @@ public interface TestConfig {
@WithDefault("false")
boolean displayTestOutput();
+ /**
+ * The FQCN of the JUnit ClassOrderer to use. If the class cannot be found, it fallbacks to JUnit
+ * default behaviour which does not set a ClassOrderer at all.
+ *
+ * @see JUnit Class
+ * Order
+ */
+ @WithDefault("io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer")
+ Optional classOrderer();
+
/**
* Tags that should be included for continuous testing. This supports JUnit Tag Expressions.
*
@@ -77,7 +89,6 @@ public interface TestConfig {
* is matched against the test class name (not the file name).
*
* This is ignored if include-pattern has been set.
- *
*/
@WithDefault(".*\\.IT[^.]+|.*IT|.*ITCase")
Optional excludePattern();
@@ -174,7 +185,7 @@ public interface TestConfig {
* When the artifact is a {@code container}, this string is passed right after the {@code docker run} command.
* When the artifact is a {@code native binary}, this string is passed right after the native binary name.
*/
- Optional> argLine();
+ Optional<@WithConverter(TrimmedStringConverter.class) List> argLine();
/**
* Additional environment variables to be set in the process that {@code @QuarkusIntegrationTest} launches.
@@ -241,7 +252,6 @@ public interface TestConfig {
* is matched against the module groupId:artifactId.
*
* This is ignored if include-module-pattern has been set.
- *
*/
Optional excludeModulePattern();
@@ -265,7 +275,7 @@ interface Profile {
* then Quarkus will only execute tests that are annotated with a {@code @TestProfile} that has at least one of the
* supplied (via the aforementioned system property) tags.
*/
- Optional> tags();
+ Optional> tags();
}
interface Container {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java
index 33d544f1fadd4..77f79cb7f3298 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java
@@ -24,15 +24,6 @@ public JavaVersion getJavaVersion() {
public interface JavaVersion {
- @Deprecated(forRemoval = true)
- Status isExactlyJava11();
-
- @Deprecated(forRemoval = true)
- Status isJava11OrHigher();
-
- @Deprecated(forRemoval = true)
- Status isJava17OrHigher();
-
Status isJava21OrHigher();
Status isJava19OrHigher();
@@ -48,21 +39,6 @@ final class Unknown implements JavaVersion {
Unknown() {
}
- @Override
- public Status isExactlyJava11() {
- return Status.UNKNOWN;
- }
-
- @Override
- public Status isJava11OrHigher() {
- return Status.UNKNOWN;
- }
-
- @Override
- public Status isJava17OrHigher() {
- return Status.UNKNOWN;
- }
-
@Override
public Status isJava21OrHigher() {
return Status.UNKNOWN;
@@ -76,8 +52,6 @@ public Status isJava19OrHigher() {
final class Known implements JavaVersion {
- private static final int JAVA_11_MAJOR = 55;
- private static final int JAVA_17_MAJOR = 61;
private static final int JAVA_19_MAJOR = 63;
private static final int JAVA_21_MAJOR = 65;
@@ -87,21 +61,6 @@ final class Known implements JavaVersion {
this.determinedMajor = determinedMajor;
}
- @Override
- public Status isExactlyJava11() {
- return equalStatus(JAVA_11_MAJOR);
- }
-
- @Override
- public Status isJava11OrHigher() {
- return higherOrEqualStatus(JAVA_11_MAJOR);
- }
-
- @Override
- public Status isJava17OrHigher() {
- return higherOrEqualStatus(JAVA_17_MAJOR);
- }
-
@Override
public Status isJava19OrHigher() {
return higherOrEqualStatus(JAVA_19_MAJOR);
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 2a9ce52ff6a43..6d87ce82fa6f1 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
@@ -27,12 +27,19 @@
import java.util.Optional;
import java.util.Set;
+import jakarta.annotation.Priority;
+
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.ConfigValue;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import org.eclipse.microprofile.config.spi.Converter;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.ParameterizedType;
+import org.jboss.jandex.Type;
import org.objectweb.asm.Opcodes;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
@@ -200,6 +207,7 @@ void generateMappings(
@BuildStep
void generateBuilders(
ConfigurationBuildItem configItem,
+ CombinedIndexBuildItem combinedIndex,
List configMappings,
List runTimeDefaults,
List staticInitConfigBuilders,
@@ -246,6 +254,7 @@ void generateBuilders(
staticCustomizers.add(StaticInitConfigBuilder.class.getName());
generateConfigBuilder(generatedClass, reflectiveClass, CONFIG_STATIC_NAME,
+ combinedIndex,
defaultValues,
converters,
interceptors,
@@ -269,6 +278,7 @@ void generateBuilders(
runtimeCustomizers.add(RuntimeConfigBuilder.class.getName());
generateConfigBuilder(generatedClass, reflectiveClass, CONFIG_RUNTIME_NAME,
+ combinedIndex,
defaultValues,
converters,
interceptors,
@@ -520,7 +530,7 @@ private static String getPathWithoutExtension(Path path) {
"withDefaultValue",
void.class, SmallRyeConfigBuilder.class, String.class, String.class);
private static final MethodDescriptor WITH_CONVERTER = MethodDescriptor.ofMethod(AbstractConfigBuilder.class,
- "withConverter", void.class, SmallRyeConfigBuilder.class, Converter.class);
+ "withConverter", void.class, SmallRyeConfigBuilder.class, String.class, int.class, Converter.class);
private static final MethodDescriptor WITH_INTERCEPTOR = MethodDescriptor.ofMethod(AbstractConfigBuilder.class,
"withInterceptor",
void.class, SmallRyeConfigBuilder.class, ConfigSourceInterceptor.class);
@@ -549,17 +559,15 @@ private static String getPathWithoutExtension(Path path) {
private static final MethodDescriptor WITH_BUILDER = MethodDescriptor.ofMethod(AbstractConfigBuilder.class,
"withBuilder",
void.class, SmallRyeConfigBuilder.class, ConfigBuilder.class);
- private static final MethodDescriptor WITH_NAMES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class,
- "withMappingNames",
- SmallRyeConfigBuilder.class, Map.class);
- private static final MethodDescriptor WITH_KEYS = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class,
- "withMappingKeys",
- SmallRyeConfigBuilder.class, Set.class);
+
+ private static final DotName CONVERTER_NAME = DotName.createSimple(Converter.class.getName());
+ private static final DotName PRIORITY_NAME = DotName.createSimple(Priority.class.getName());
private static void generateConfigBuilder(
BuildProducer generatedClass,
BuildProducer reflectiveClass,
String className,
+ CombinedIndexBuildItem combinedIndex,
Map defaultValues,
Set converters,
Set interceptors,
@@ -591,7 +599,13 @@ private static void generateConfigBuilder(
}
for (String converter : converters) {
+ ClassInfo converterClass = combinedIndex.getComputingIndex().getClassByName(converter);
+ Type type = getConverterType(converterClass, combinedIndex);
+ AnnotationInstance priorityAnnotation = converterClass.annotation(PRIORITY_NAME);
+ int priority = priorityAnnotation != null ? priorityAnnotation.value().asInt() : 100;
method.invokeStaticMethod(WITH_CONVERTER, configBuilder,
+ method.load(type.name().toString()),
+ method.load(priority),
method.newInstance(MethodDescriptor.ofConstructor(converter)));
}
@@ -716,4 +730,27 @@ private static Set runtimeConfigMappings(List arguments = parameterizedType.arguments();
+ if (arguments.size() != 1) {
+ throw new IllegalArgumentException(
+ "Converter " + converter.name() + " must be parameterized with a single type");
+ }
+ return arguments.get(0);
+ }
+ }
+ }
+
+ return getConverterType(combinedIndex.getComputingIndex().getClassByName(converter.superName()), combinedIndex);
+ }
}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java
index 949b145da1b7b..2d64701a4593c 100644
--- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java
@@ -5,14 +5,16 @@
import java.util.HashSet;
import java.util.Set;
+import org.eclipse.aether.util.artifact.JavaScopes;
+
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.bootstrap.resolver.TsArtifact;
import io.quarkus.bootstrap.resolver.TsDependency;
import io.quarkus.bootstrap.resolver.TsQuarkusExt;
+import io.quarkus.maven.dependency.ArtifactCoords;
import io.quarkus.maven.dependency.ArtifactDependency;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.maven.dependency.DependencyFlags;
-import io.quarkus.maven.dependency.GACTV;
public class OptionalDepsTest extends BootstrapFromOriginalJarTestBase {
@@ -70,17 +72,73 @@ protected TsArtifact composeApplication() {
@Override
protected void assertAppModel(ApplicationModel model) throws Exception {
final Set expected = new HashSet<>();
- expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), "compile",
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.DIRECT,
+ DependencyFlags.OPTIONAL,
+ DependencyFlags.RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.RUNTIME_CP,
+ DependencyFlags.DEPLOYMENT_CP));
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-dep", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.OPTIONAL,
+ DependencyFlags.RUNTIME_CP,
+ DependencyFlags.DEPLOYMENT_CP));
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.OPTIONAL,
+ DependencyFlags.DEPLOYMENT_CP));
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "app-optional-dep", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
DependencyFlags.OPTIONAL,
+ DependencyFlags.DIRECT,
+ DependencyFlags.RUNTIME_CP,
DependencyFlags.DEPLOYMENT_CP));
- expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-b-deployment-dep", "1"), "compile",
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.OPTIONAL,
+ DependencyFlags.RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.RUNTIME_CP,
+ DependencyFlags.DEPLOYMENT_CP));
+
+ expected.add(new ArtifactDependency(ArtifactCoords.jar(
+ TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
DependencyFlags.OPTIONAL,
DependencyFlags.DEPLOYMENT_CP));
- expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-b-deployment", "1"), "compile",
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment-dep", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
DependencyFlags.OPTIONAL,
DependencyFlags.DEPLOYMENT_CP));
- expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-d-deployment", "1"), "compile",
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.DIRECT,
+ DependencyFlags.RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT,
+ DependencyFlags.RUNTIME_CP,
DependencyFlags.DEPLOYMENT_CP));
- assertEquals(expected, getDeploymentOnlyDeps(model));
+
+ expected.add(new ArtifactDependency(
+ ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION),
+ JavaScopes.COMPILE,
+ DependencyFlags.DEPLOYMENT_CP));
+
+ assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.DEPLOYMENT_CP));
}
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
index 4eb92d7e84277..6e99c7e9b3be1 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
@@ -14,6 +14,8 @@
import io.quarkus.annotation.processor.documentation.config.discovery.EnumDefinition;
import io.quarkus.annotation.processor.documentation.config.discovery.EnumDefinition.EnumConstant;
import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType;
+import io.quarkus.annotation.processor.documentation.config.model.ConfigPhase;
+import io.quarkus.annotation.processor.documentation.config.model.ExtensionModule;
import io.quarkus.annotation.processor.documentation.config.util.Types;
import io.quarkus.annotation.processor.util.Config;
import io.quarkus.annotation.processor.util.Utils;
@@ -66,6 +68,18 @@ public void onResolvedEnum(TypeElement enumTypeElement) {
configCollector.addResolvedEnum(enumDefinition);
}
+ protected void validateRuntimeConfigOnDeploymentModules(ConfigPhase configPhase, TypeElement configRoot) {
+ if (configPhase.equals(ConfigPhase.RUN_TIME) || configPhase.equals(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) {
+ ExtensionModule.ExtensionModuleType type = config.getExtensionModule().type();
+ if (type.equals(ExtensionModule.ExtensionModuleType.DEPLOYMENT)) {
+ throw new IllegalStateException(String.format(
+ "Error on %s: Configuration classes with ConfigPhase.RUN_TIME or " +
+ "ConfigPhase.BUILD_AND_RUNTIME_FIXED phases, must reside in the respective module.",
+ configRoot.getSimpleName().toString()));
+ }
+ }
+ }
+
protected void handleCommonPropertyAnnotations(DiscoveryConfigProperty.Builder builder,
Map propertyAnnotations, ResolvedType resolvedType, String sourceElementName) {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
index cda99142c602e..ea4135f7adfe3 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
@@ -75,6 +75,8 @@ public Optional onConfigRoot(TypeElement configRoot) {
}
}
+ validateRuntimeConfigOnDeploymentModules(configPhase, configRoot);
+
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : configMappingAnnotion.getElementValues()
.entrySet()) {
if ("prefix()".equals(entry.getKey().toString())) {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
index 2410b8c62b9d6..f675f902fc9fe 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
@@ -79,6 +79,8 @@ public Optional onConfigRoot(TypeElement configRoot) {
}
}
+ validateRuntimeConfigOnDeploymentModules(configPhase, configRoot);
+
String overriddenDocPrefix = null;
if (configDocPrefixAnnotation != null) {
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : configDocPrefixAnnotation
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractConfigBuilder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractConfigBuilder.java
index 84f9336c4ad61..15dc4e2604aa2 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractConfigBuilder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractConfigBuilder.java
@@ -21,10 +21,14 @@ protected static void withDefaultValue(SmallRyeConfigBuilder builder, String nam
builder.withDefaultValue(name, value);
}
- // TODO - radcortez - Can be improved by avoiding introspection work in the Converter class.
- // Not a big issue, because registering Converters via ServiceLoader is not a common case
- protected static void withConverter(SmallRyeConfigBuilder builder, Converter> converter) {
- builder.withConverters(new Converter[] { converter });
+ @SuppressWarnings("unchecked")
+ protected static void withConverter(SmallRyeConfigBuilder builder, String type, int priority, Converter converter) {
+ try {
+ // To support converters that are not public
+ builder.withConverter((Class) builder.getClassLoader().loadClass(type), priority, converter);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
}
protected static void withInterceptor(SmallRyeConfigBuilder builder, ConfigSourceInterceptor interceptor) {
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java
index 2adc409fab816..85f778217b946 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java
@@ -1,5 +1,7 @@
package io.quarkus.runtime.configuration;
+import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
+
import io.quarkus.runtime.LaunchMode;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigFactory;
@@ -12,13 +14,6 @@ public final class QuarkusConfigFactory extends SmallRyeConfigFactory {
private static volatile SmallRyeConfig config;
- /**
- * Construct a new instance. Called by service loader.
- */
- public QuarkusConfigFactory() {
- // todo: replace with {@code provider()} post-Java 11
- }
-
@Override
public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configProviderResolver,
final ClassLoader classLoader) {
@@ -30,15 +25,12 @@ public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configPr
}
public static void setConfig(SmallRyeConfig config) {
- SmallRyeConfigProviderResolver configProviderResolver = (SmallRyeConfigProviderResolver) SmallRyeConfigProviderResolver
- .instance();
+ ConfigProviderResolver configProviderResolver = ConfigProviderResolver.instance();
// Uninstall previous config
if (QuarkusConfigFactory.config != null) {
configProviderResolver.releaseConfig(QuarkusConfigFactory.config);
QuarkusConfigFactory.config = null;
}
- // Also release the TCCL config, in case that config was not QuarkusConfigFactory.config
- configProviderResolver.releaseConfig(Thread.currentThread().getContextClassLoader());
// Install new config
if (config != null) {
QuarkusConfigFactory.config = config;
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java
index 50bb36be6c48f..1da00c356683b 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java
@@ -162,6 +162,11 @@ public int handleCommandException(Exception ex, String message) {
CommandLine.UnmatchedArgumentException.printSuggestions((CommandLine.ParameterException) ex, out());
}
error(message);
+
+ if (!isShowErrors()) {
+ info("\nAdd the -e/--errors option to get more information about the error. Add the --verbose option to get even more details.");
+ }
+
return cmd.getExitCodeExceptionMapper() != null ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
: mixee.exitCodeOnInvalidInput();
}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusPlatformGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusPlatformGroup.java
index 113d3c3d2a4ae..f1ba101c2cc35 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusPlatformGroup.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusPlatformGroup.java
@@ -9,7 +9,7 @@
public class TargetQuarkusPlatformGroup {
static final String FULL_EXAMPLE = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID + ":"
- + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + ":2.2.0.Final";
+ + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + ":3.15.2";
PlatformStreamCoords streamCoords = null;
String validStream = null;
@@ -20,7 +20,7 @@ public class TargetQuarkusPlatformGroup {
CommandSpec spec;
@CommandLine.Option(paramLabel = "platformKey:streamId", names = { "-S",
- "--stream" }, description = "A target stream, for example:%n io.quarkus.platform:2.0")
+ "--stream" }, description = "A target stream, for example:%n 3.15 or io.quarkus.platform:3.15")
void setStream(String stream) {
stream = stream.trim();
if (!stream.isEmpty()) {
@@ -39,7 +39,7 @@ void setStream(String stream) {
"--platform-bom" }, description = "A specific Quarkus platform BOM, for example:%n"
+ " " + FULL_EXAMPLE + "%n"
+ " io.quarkus::999-SNAPSHOT"
- + " 2.2.0.Final%n"
+ + " 3.15.2%n"
+ "Default groupId: " + ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID + "%n"
+ "Default artifactId: " + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + "%n")
void setPlatformBom(String bom) {
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java
index 53b2ff28a120b..1ed701fc35355 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java
@@ -5,12 +5,12 @@
public class TargetQuarkusVersionGroup {
@CommandLine.Option(paramLabel = "targetStream", names = { "-S",
- "--stream" }, description = "A target stream, for example:%n 2.0")
+ "--stream" }, description = "A target stream, for example:%n 3.15")
public String streamId;
@CommandLine.Option(paramLabel = "targetPlatformVersion", names = { "-P",
"--platform-version" }, description = "A specific target Quarkus platform version, for example:%n"
- + " 2.2.0.Final%n")
+ + " 3.15.2%n")
public String platformVersion;
//@CommandLine.Option(names = { "-L",
diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java
index 7fc07fdf4f152..e54be14cff418 100644
--- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java
+++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java
@@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -228,32 +229,39 @@ private void setUpDeploymentConfiguration() {
configuration.setCanBeConsumed(false);
Configuration enforcedPlatforms = this.getPlatformConfiguration();
configuration.extendsFrom(enforcedPlatforms);
+ Map> calculatedDependenciesByModeAndConfiguration = new HashMap<>();
ListProperty dependencyListProperty = project.getObjects().listProperty(Dependency.class);
configuration.getDependencies().addAllLater(dependencyListProperty.value(project.provider(() -> {
- ConditionalDependenciesEnabler cdEnabler = new ConditionalDependenciesEnabler(project, mode,
- enforcedPlatforms);
- final Collection> allExtensions = cdEnabler.getAllExtensions();
- Set> extensions = collectFirstMetQuarkusExtensions(getRawRuntimeConfiguration(),
- allExtensions);
- // Add conditional extensions
- for (ExtensionDependency> knownExtension : allExtensions) {
- if (knownExtension.isConditional()) {
- extensions.add(knownExtension);
+ String key = String.format("%s%s%s", mode, configuration.getName(), project.getName());
+ if (!calculatedDependenciesByModeAndConfiguration.containsKey(key)) {
+ ConditionalDependenciesEnabler cdEnabler = new ConditionalDependenciesEnabler(project, mode,
+ enforcedPlatforms);
+ final Collection> allExtensions = cdEnabler.getAllExtensions();
+ Set> extensions = collectFirstMetQuarkusExtensions(getRawRuntimeConfiguration(),
+ allExtensions);
+ // Add conditional extensions
+ for (ExtensionDependency> knownExtension : allExtensions) {
+ if (knownExtension.isConditional()) {
+ extensions.add(knownExtension);
+ }
}
- }
- final Set alreadyProcessed = new HashSet<>(extensions.size());
- final DependencyHandler dependencies = project.getDependencies();
- final Set deploymentDependencies = new HashSet<>();
- for (ExtensionDependency> extension : extensions) {
- if (!alreadyProcessed.add(extension.getExtensionId())) {
- continue;
- }
+ final Set alreadyProcessed = new HashSet<>(extensions.size());
+ final DependencyHandler dependencies = project.getDependencies();
+ final Set deploymentDependencies = new HashSet<>();
+ for (ExtensionDependency> extension : extensions) {
+ if (!alreadyProcessed.add(extension.getExtensionId())) {
+ continue;
+ }
- deploymentDependencies.add(
- DependencyUtils.createDeploymentDependency(dependencies, extension));
+ deploymentDependencies.add(
+ DependencyUtils.createDeploymentDependency(dependencies, extension));
+ }
+ calculatedDependenciesByModeAndConfiguration.put(key, deploymentDependencies);
+ return deploymentDependencies;
+ } else {
+ return calculatedDependenciesByModeAndConfiguration.get(key);
}
- return deploymentDependencies;
})));
});
}
diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml
index e4b86cbd74791..5508dac5d2176 100644
--- a/devtools/gradle/gradle/libs.versions.toml
+++ b/devtools/gradle/gradle/libs.versions.toml
@@ -6,7 +6,7 @@ kotlin = "2.0.21"
smallrye-config = "3.10.2"
junit5 = "5.10.5"
-assertj = "3.26.3"
+assertj = "3.27.0"
[plugins]
plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "plugin-publish" }
diff --git a/devtools/gradle/settings.gradle.kts b/devtools/gradle/settings.gradle.kts
index 4d008c259a339..6532acd5a053f 100644
--- a/devtools/gradle/settings.gradle.kts
+++ b/devtools/gradle/settings.gradle.kts
@@ -1,5 +1,5 @@
plugins {
- id("com.gradle.develocity") version "3.18.2"
+ id("com.gradle.develocity") version "3.19"
}
develocity {
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java
index c35a0befbbef0..d95e82c89028b 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java
@@ -1,5 +1,6 @@
package io.quarkus.maven;
+import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.List;
@@ -16,6 +17,7 @@
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.model.ApplicationModel;
+import io.quarkus.bootstrap.util.BootstrapUtils;
import io.quarkus.maven.dependency.ArtifactCoords;
import io.quarkus.paths.PathCollection;
import io.quarkus.paths.PathList;
@@ -31,8 +33,11 @@ public class GenerateCodeMojo extends QuarkusBootstrapMojo {
* Skip the execution of this mojo
*/
@Parameter(defaultValue = "false", property = "quarkus.generate-code.skip", alias = "quarkus.prepare.skip")
- private boolean skipSourceGeneration = false;
+ boolean skipSourceGeneration = false;
+ /**
+ * Application launch mode for which to generate the source code.
+ */
@Parameter(defaultValue = "NORMAL", property = "launchMode")
String mode;
@@ -55,9 +60,8 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
path -> mavenProject().addCompileSourceRoot(path.toString()), false);
}
- void generateCode(PathCollection sourceParents,
- Consumer sourceRegistrar,
- boolean test) throws MojoFailureException, MojoExecutionException {
+ void generateCode(PathCollection sourceParents, Consumer sourceRegistrar, boolean test)
+ throws MojoExecutionException {
final LaunchMode launchMode;
if (test) {
@@ -97,13 +101,31 @@ void generateCode(PathCollection sourceParents,
if (deploymentClassLoader != null) {
deploymentClassLoader.close();
}
- // in case of test mode, we can't share the bootstrapped app with the testing plugins, so we are closing it right away
+ // In case of the test mode, we can't share the application model with the test plugins, so we are closing it right away,
+ // but we are serializing the application model so the test plugins can deserialize it from disk instead of re-initializing
+ // the resolver and re-resolving it as part of the test bootstrap
if (test && curatedApplication != null) {
- curatedApplication.close();
+ var appModel = curatedApplication.getApplicationModel();
+ closeApplication(LaunchMode.TEST);
+ if (isSerializeTestModel()) {
+ final int workspaceId = getWorkspaceId();
+ if (workspaceId != 0) {
+ try {
+ BootstrapUtils.writeAppModelWithWorkspaceId(appModel, workspaceId, BootstrapUtils
+ .getSerializedTestAppModelPath(Path.of(mavenProject().getBuild().getDirectory())));
+ } catch (IOException e) {
+ getLog().warn("Failed to serialize application model", e);
+ }
+ }
+ }
}
}
}
+ protected boolean isSerializeTestModel() {
+ return false;
+ }
+
protected PathCollection getParentDirs(List sourceDirs) {
if (sourceDirs.size() == 1) {
return PathList.of(Path.of(sourceDirs.get(0)).getParent());
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
index b644af7be0a18..ed5b9bd71e6a4 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java
@@ -13,15 +13,29 @@
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import io.quarkus.bootstrap.app.CuratedApplication;
+import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.builder.Json;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.runtime.LaunchMode;
@Mojo(name = "generate-code-tests", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true)
public class GenerateCodeTestsMojo extends GenerateCodeMojo {
+
+ /**
+ * A switch that enables or disables serialization of an {@link ApplicationModel} to a file for tests.
+ * Deserializing an application model when bootstrapping Quarkus tests has a performance advantage in that
+ * the tests will not have to initialize a Maven resolver and re-resolve the application model, which may save,
+ * depending on a project, ~80-95% of time on {@link ApplicationModel} resolution.
+ *
+ * Serialization of the test model is enabled by default.
+ */
+ @Parameter(property = "quarkus.generate-code.serialize-test-model", defaultValue = "true")
+ boolean serializeTestModel;
+
@Override
protected void doExecute() throws MojoExecutionException, MojoFailureException {
generateCode(getParentDirs(mavenProject().getTestCompileSourceRoots()),
@@ -32,6 +46,11 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
}
}
+ @Override
+ protected boolean isSerializeTestModel() {
+ return serializeTestModel;
+ }
+
private boolean isTestWithNativeAgent() {
String value = System.getProperty("quarkus.test.integration-test-profile");
if ("test-with-native-agent".equals(value)) {
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
index b8cfa1396f9b3..fe53cd99d3433 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapMojo.java
@@ -296,6 +296,20 @@ protected CuratedApplication bootstrapApplication(LaunchMode mode) throws MojoEx
return bootstrapProvider.bootstrapApplication(this, mode);
}
+ protected void closeApplication(LaunchMode mode) {
+ bootstrapProvider.closeApplication(this, mode);
+ }
+
+ /**
+ * Workspace ID associated with a given bootstrap mojo.
+ * If the returned value is {@code 0}, a workspace was not associated with the bootstrap mojo.
+ *
+ * @return workspace ID associated with a given bootstrap mojo
+ */
+ protected int getWorkspaceId() {
+ return bootstrapProvider.getWorkspaceId(this);
+ }
+
protected CuratedApplication bootstrapApplication(LaunchMode mode, Consumer builderCustomizer)
throws MojoExecutionException {
return bootstrapProvider.bootstrapApplication(this, mode, builderCustomizer);
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java
index a2b09b2e7a06c..1341a7d14c9eb 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java
@@ -45,6 +45,7 @@
import io.quarkus.bootstrap.resolver.maven.EffectiveModelResolver;
import io.quarkus.bootstrap.resolver.maven.IncubatingApplicationModelResolver;
import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
import io.quarkus.maven.components.ManifestSection;
import io.quarkus.maven.components.QuarkusWorkspaceProvider;
import io.quarkus.maven.dependency.ArtifactCoords;
@@ -138,6 +139,21 @@ public CuratedApplication bootstrapApplication(QuarkusBootstrapMojo mojo, Launch
return bootstrapper(mojo).bootstrapApplication(mojo, mode, builderCustomizer);
}
+ public void closeApplication(QuarkusBootstrapMojo mojo, LaunchMode mode) {
+ bootstrapper(mojo).closeApplication(mode);
+ }
+
+ /**
+ * Workspace ID associated with a given bootstrap mojo.
+ * If the returned value is {@code 0}, a workspace was not associated with the bootstrap mojo.
+ *
+ * @param mojo bootstrap mojo
+ * @return workspace ID associated with a given bootstrap mojo
+ */
+ public int getWorkspaceId(QuarkusBootstrapMojo mojo) {
+ return bootstrapper(mojo).workspaceId;
+ }
+
public ApplicationModel getResolvedApplicationModel(ArtifactKey projectId, LaunchMode mode, String bootstrapId) {
if (appBootstrapProviders.size() == 0) {
return null;
@@ -180,6 +196,7 @@ private static boolean isWorkspaceDiscovery(QuarkusBootstrapMojo mojo) {
public class QuarkusMavenAppBootstrap implements Closeable {
+ private int workspaceId;
private CuratedApplication prodApp;
private CuratedApplication devApp;
private CuratedApplication testApp;
@@ -187,7 +204,7 @@ public class QuarkusMavenAppBootstrap implements Closeable {
private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, LaunchMode mode) {
try {
if (mode == LaunchMode.DEVELOPMENT || mode == LaunchMode.TEST || isWorkspaceDiscovery(mojo)) {
- return workspaceProvider.createArtifactResolver(
+ var resolver = workspaceProvider.createArtifactResolver(
BootstrapMavenContext.config()
// it's important to pass user settings in case the process was not launched using the original mvn script
// for example using org.codehaus.plexus.classworlds.launcher.Launcher
@@ -199,6 +216,11 @@ private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, Launch
.setRemoteRepositories(mojo.remoteRepositories())
.setEffectiveModelBuilder(BootstrapMavenContextConfig
.getEffectiveModelBuilderProperty(mojo.mavenProject().getProperties())));
+ final LocalProject currentProject = resolver.getMavenContext().getCurrentProject();
+ if (currentProject != null && workspaceId == 0) {
+ workspaceId = currentProject.getWorkspace().getId();
+ }
+ return resolver;
}
// PROD packaging mode with workspace discovery disabled
return MavenArtifactResolver.builder()
@@ -376,6 +398,23 @@ protected CuratedApplication bootstrapApplication(QuarkusBootstrapMojo mojo, Lau
return prodApp == null ? prodApp = doBootstrap(mojo, mode, builderCustomizer) : prodApp;
}
+ protected void closeApplication(LaunchMode mode) {
+ if (mode == LaunchMode.DEVELOPMENT) {
+ if (devApp != null) {
+ devApp.close();
+ devApp = null;
+ }
+ } else if (mode == LaunchMode.TEST) {
+ if (testApp != null) {
+ testApp.close();
+ testApp = null;
+ }
+ } else if (prodApp != null) {
+ prodApp.close();
+ prodApp = null;
+ }
+ }
+
protected ArtifactCoords managingProject(QuarkusBootstrapMojo mojo) {
if (mojo.appArtifactCoords() == null) {
return null;
diff --git a/docs/pom.xml b/docs/pom.xml
index 25280e567b7c7..8dd5d654a0a7c 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -29,7 +29,7 @@
2.26.0.Final3711.1.0
- 7.0.0.202409031743-r
+ 7.1.0.202411261347-rhttps://quarkus.iohttps://github.com/quarkusio/quarkus
diff --git a/docs/src/main/asciidoc/centralized-log-management.adoc b/docs/src/main/asciidoc/centralized-log-management.adoc
index 4fbb13b06388c..f0127b3d339ce 100644
--- a/docs/src/main/asciidoc/centralized-log-management.adoc
+++ b/docs/src/main/asciidoc/centralized-log-management.adoc
@@ -250,7 +250,7 @@ For this you can use the same `docker-compose.yml` file as above but with a diff
input {
tcp {
port => 4560
- coded => json
+ codec => json
}
}
diff --git a/docs/src/main/asciidoc/command-mode-reference.adoc b/docs/src/main/asciidoc/command-mode-reference.adoc
index 14f8f2f1831d0..074fe72adc894 100644
--- a/docs/src/main/asciidoc/command-mode-reference.adoc
+++ b/docs/src/main/asciidoc/command-mode-reference.adoc
@@ -255,7 +255,7 @@ Consequently, mocking CDI beans with `QuarkusMock` or `@InjectMock` is not suppo
It is possible to mock CDI beans by leveraging xref:getting-started-testing.adoc#testing_different_profiles[test profiles] though.
-For instance, in the following test, the singleton `CdiBean1` will be mocked by `MockedCdiBean1`:
+For instance, in the following test, the launched application would receive a mocked singleton `CdiBean1`. The implementation `MockedCdiBean1` is provided by the test:
[source,java]
----
diff --git a/docs/src/main/asciidoc/drools.adoc b/docs/src/main/asciidoc/drools.adoc
new file mode 100644
index 0000000000000..02a3f46bfcc68
--- /dev/null
+++ b/docs/src/main/asciidoc/drools.adoc
@@ -0,0 +1,638 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
+////
+= Defining and executing business rules with Drools
+include::_attributes.adoc[]
+:categories: rule engine
+:summary: Drools is the most used rule engine implementation in the Java ecosystem. The purpose of this guide is to show how to use to define and execute business rules in Quarkus using Drools.
+:topics: drools,rules,rule engine
+:extensions: org.drools:drools-quarkus
+
+This guide demonstrates how your Quarkus application can use https://www.drools.org[Drools] to add intelligent automation
+and power it up with the Drools rule engine.
+
+== Prerequisites
+
+include::{includes}/prerequisites.adoc[]
+
+== Introduction
+
+https://www.drools.org[Drools] is a set of projects focusing on intelligent automation and decision management, most notably providing a forward-chaining and backward-chaining inference-based rule engine, DMN decisions engine and other projects. A rule engine is a fundamental building block to create an expert system which, in artificial intelligence, is a computer system that emulates the decision-making ability of a human expert. You can read more information on the https://www.drools.org[Drools website].
+
+Drools allows defining rules with 2 different programming styles: one more traditional based on the concepts of a KieBase acting as a repository of business rules and a KieSession storing and evaluating the runtime data against them, and the other using a Rule Unit as a single abstraction that encapsulates the definitions of both a set of rules and the facts against which those rules will be matched.
+
+Both these styles are fully supported in the Drools Quarkus extension and this document explains how to use both, outlining the pros and cons of each one.
+
+== Integrating the traditional Drools programming model with Quarkus
+
+This first example demonstrates how to define a set of rules using the traditional Drools style and how to expose their evaluation inside a REST endpoint through Quarkus.
+
+The domain model of this sample project is made only by two classes, a loan application
+
+[source,java]
+----
+public class LoanApplication {
+ private String id;
+ private Applicant applicant;
+ private int amount;
+ private int deposit;
+ private boolean approved = false;
+
+ public LoanApplication(String id, Applicant applicant, int amount, int deposit) {
+ this.id = id;
+ this.applicant = applicant;
+ this.amount = amount;
+ this.deposit = deposit;
+ }
+}
+----
+
+and the applicant who requested it
+
+[source,java]
+----
+public class Applicant {
+ private String name;
+ private int age;
+
+ public Applicant(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+}
+----
+
+The rules set is made of business decisions to approve or reject an application plus one last rule collecting all the approved applications into a list.
+
+[source]
+----
+global Integer maxAmount;
+global java.util.List approvedApplications;
+
+rule LargeDepositApprove when
+ $l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount <= maxAmount )
+then
+ modify($l) { setApproved(true) }; // loan is approved
+end
+
+rule LargeDepositReject when
+ $l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount > maxAmount )
+then
+ modify($l) { setApproved(false) }; // loan is rejected
+end
+
+// ... more loans approval/rejections business rules ...
+
+rule CollectApprovedApplication when
+ $l: LoanApplication( approved )
+then
+ approvedApplications.add($l); // collect all approved loan applications
+end
+----
+
+The goal that we want to achieve is putting the evaluation of these rules in a microservice, exposing them in a REST endpoint developed with Quarkus. To do so it is enough to add the Drools Quarkus extension among the dependencies of your project.
+
+[source,xml]
+----
+
+ org.drools
+ drools-quarkus
+
+----
+
+and at this point it is possible to obtain a reference to the KieSession evaluating the formerly defined rules and use it in a REST endpoint as it follows:
+
+[source,java]
+----
+@Path("/find-approved")
+public class FindApprovedLoansEndpoint {
+
+ @Inject
+ KieRuntimeBuilder kieRuntimeBuilder;
+
+ @POST()
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public List executeQuery(LoanAppDto loanAppDto) {
+ KieSession session = kieRuntimeBuilder.newKieSession();
+ List approvedApplications = new ArrayList<>();
+
+ session.setGlobal("approvedApplications", approvedApplications);
+ session.setGlobal("maxAmount", loanAppDto.getMaxAmount());
+ loanAppDto.getLoanApplications().forEach(session::insert);
+
+ session.fireAllRules();
+ session.dispose();
+ return approvedApplications;
+ }
+}
+----
+
+where an implementation of the `KieRuntimeBuilder` interface is automatically generated and made injectable for you by the Drools extension and allows to obtain with a single statement an instance of any KieBases and KieSessions defined in your Drools project.
+
+Here the `LoanAppDto` is a simple POJO used to submit multiple loan application to the same KieSession
+
+[source,java]
+----
+public class LoanAppDto {
+ private int maxAmount;
+ private List loanApplications;
+
+ public int getMaxAmount() {
+ return maxAmount;
+ }
+
+ public void setMaxAmount(int maxAmount) {
+ this.maxAmount = maxAmount;
+ }
+
+ public List getLoanApplications() {
+ return loanApplications;
+ }
+
+ public void setLoanApplications(List loanApplications) {
+ this.loanApplications = loanApplications;
+ }
+}
+----
+
+thus trying for example to invoke that endpoint with a set of loan applications
+
+[source]
+----
+curl -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' -d
+'{"maxAmount":5000,"loanApplications":[
+ {"id":"ABC10001","amount":2000,"deposit":1000,"applicant":{"age":45,"name":"John"}},
+ {"id":"ABC10002","amount":5000,"deposit":100,"applicant":{"age":25,"name":"Paul"}},
+ {"id":"ABC10015","amount":1000,"deposit":100,"applicant":{"age":12,"name":"George"}}
+]}'
+http://localhost:8080/find-approved
+----
+
+the rule engine will evaluate them against the business rules we have configured before, returning the only one that in this case can be approved according to them
+
+[source]
+----
+[{"id":"ABC10001","applicant":{"name":"John","age":45},"amount":2000,"deposit":1000,"approved":true}]
+----
+
+== Moving to the rule unit programming model
+
+A rule unit is a new concept introduced in Drools encapsulating both a set of rules and the facts against which those rules will be matched. It comes with a second abstraction called data source, defining the sources through which the facts are inserted, acting in practice as typed entry-points. There are two types of data sources:
+
+* DataStream: an append-only data source
+** subscribers only receive new (and possibly past) messages
+** cannot update/remove
+** stream may also be hot/cold in “reactive streams” terminology
+* DataStore: data source for modifiable data
+** subscribers may act upon the data store, by acting upon the fact handle
+
+In order to use rule units in our quarkus application it is necessary to add a second dependency.
+
+[source,xml]
+----
+
+ org.drools
+ drools-ruleunits-engine
+
+----
+
+In essence a rule unit is made of 2 strictly related parts: the definition of the fact to be evaluated and the set of rules evaluating them. The first part is implemented with a POJO, that for the loan example could be something like the following:
+
+[source,java]
+----
+package org.loans;
+
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitData;
+
+public class LoanUnit implements RuleUnitData {
+
+ private int maxAmount;
+ private DataStore loanApplications;
+
+ public LoanUnit() {
+ this(DataSource.createStore(), 0);
+ }
+
+ public LoanUnit(DataStore loanApplications, int maxAmount) {
+ this.loanApplications = loanApplications;
+ this.maxAmount = maxAmount;
+ }
+
+ public DataStore getLoanApplications() {
+ return loanApplications;
+ }
+
+ public void setLoanApplications(DataStore loanApplications) {
+ this.loanApplications = loanApplications;
+ }
+
+ public int getMaxAmount() {
+ return maxAmount;
+ }
+
+ public void setMaxAmount(int maxAmount) {
+ this.maxAmount = maxAmount;
+ }
+}
+----
+
+Here instead of using the `LoanAppDto` that we introduced to marshall/unmarshall the JSON requests we are binding directly the class representing the rule unit. The two relevant differences are that it implements the `RuleUnitData` interface and uses a `DataStore` instead of a plain `List` containing the loan applications to be approved. The first is just a marker interface to notify the engine that this class is part of a rule unit definition. The use of a `DataStore` is necessary to let the rule engine to react accordingly to the changes by firing new rules and triggering other rules. In the example, the consequences of the rules modify the approved property of the loan applications. Conversely, the `maxAmount` value can be considered a configuration parameter of the rule unit and left as it is: it will automatically be processed during the rules evaluation with the same semantic of a global, and automatically set from the value passed by the JSON request as in the first example, so you will still be allowed to use it in your rules.
+
+The second part of the rule unit is the drl file containing the rules belonging to this unit.
+
+[source]
+----
+package org.loans;
+
+unit LoanUnit; // no need to using globals, all variables and facts are stored in the rule unit
+
+rule LargeDepositApprove when
+ $l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount <= maxAmount ] // oopath style
+then
+ modify($l) { setApproved(true) };
+end
+
+rule LargeDepositReject when
+ $l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount > maxAmount ]
+then
+ modify($l) { setApproved(false) };
+end
+
+// ... more loans approval/rejections business rules ...
+
+// approved loan applications are now retrieved through a query
+query FindApproved
+ $l: /loanApplications[ approved ]
+end
+----
+
+This rules file must declare the same package and a unit with the same name of the Java class implementing the `RuleUnitData` interface in order to state that they belong to the same rule unit.
+
+This file has also been rewritten using the new OOPath notation: as anticipated, here the data source acts as a typed entry-point and the oopath expression has its name as root while the constraints are in square brackets, like in the following example.
+
+[source]
+----
+$l: /loanApplications[ applicant.age >= 20, deposit >= 1000, amount <= maxAmount ]
+----
+
+Alternatively you can still use the old DRL syntax, specifying the name of the data source as an entry-point, with the drawback that in this case you need to specify again the type of the matched object, even if the engine can infer it from the type of the datasource, as it follows.
+
+[source]
+----
+$l: LoanApplication( applicant.age >= 20, deposit >= 1000, amount <= maxAmount ) from entry-point loanApplications
+----
+
+Finally note that the last rule collecting all the approved loan applications into a global `List` has been replaced by a query simply retrieving them. One of the advantages in using a rule unit is that it clearly defines the context of computation, in other terms the facts to be passed in input to the rule evaluation. Similarly, the query defines what is the output expected by this evaluation.
+
+This clear definition of the computation boundaries allows Drools to also automatically generate a class executing the query and returning its results, together with a REST endpoint taking the rule unit as input, passing it to the former query executor and returning its as output.
+
+You can have as many query as you want and for each of them it will be generated a different REST endpoint with the same name of the query transformed from camel case (like `FindApproved`) to dash separated (like `find-approved`).
+
+== A more comprehensive example
+
+In this more comprehensive and complete example, we will augment a basic Quarkus application with a few simple rules to infer potential issues with the status of a home automation setup.
+
+We will define a Drools Rule Unit and the rules in the DRL format.
+
+We will wire the Rule Unit into a standard Quarkus CDI bean, for use in the Quarkus application (for instance, wiring MQTT messages from Kafka, etc.).
+
+=== Prerequisites
+
+To complete this guide, you need:
+
+* less than 15 minutes
+* an IDE
+* JDK 17+ installed with `JAVA_HOME` configured appropriately
+* Apache Maven 3.9.3+
+* Docker
+* link:{https://quarkus.io/guides/building-native-image}[GraalVM installed] if you want to run in native mode
+
+=== Creating the Maven Project
+
+First, we need a new Quarkus project.
+To create a new Quarkus project, you can reference the link:{https://quarkus.io/guides/maven-tooling}[Quarkus and Maven Guide]
+
+When you have your Quarkus project configured, you can add the Drools Quarkus extensions to your project by adding the following dependencies to your `pom.xml`:
+
+[source,xml,subs=attributes+]
+----
+
+ org.drools
+ drools-quarkus
+
+
+ org.drools
+ drools-ruleunits-engine
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+----
+
+=== Writing the application
+
+Let's start from the application domain model.
+
+This application goal is to infer potential issues with the status of a home automation setup, so we create the necessary domain models to represent status of sensors, devices and other things inside the house.
+
+Light device domain model:
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test.model;
+
+public class Light {
+ private final String name;
+ private Boolean powered;
+
+ public Light(String name, Boolean powered) {
+ this.name = name;
+ this.powered = powered;
+ }
+
+ // getters, setters, etc.
+}
+----
+
+CCTV security camera domain model:
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test.model;
+
+public class CCTV {
+ private final String name;
+ private Boolean powered;
+
+ public CCTV(String name, Boolean powered) {
+ this.name = name;
+ this.powered = powered;
+ }
+
+ // getters, setters, etc.
+}
+----
+
+Smartphone detected in WiFi domain model:
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test.model;
+
+public class Smartphone {
+ private final String name;
+
+ public Smartphone(String name) {
+ this.name = name;
+ }
+
+ // getters, setters, etc.
+}
+----
+
+Alert class to hold information of the potential detected problems:
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test.model;
+
+public class Alert {
+ private final String notification;
+
+ public Alert(String notification) {
+ this.notification = notification;
+ }
+
+ // getters, setters, etc.
+}
+----
+
+Next, we create a rule file `rules.drl` inside the `src/main/resources/org/drools/quarkus/quickstart/test` folder of the Quarkus project.
+
+[source]
+----
+package org.drools.quarkus.quickstart.test;
+
+unit HomeRuleUnitData;
+
+import org.drools.quarkus.quickstart.test.model.*;
+
+rule "No lights on while outside"
+when
+ $l: /lights[ powered == true ];
+ not( /smartphones );
+then
+ alerts.add(new Alert("You might have forgot one light powered on: " + $l.getName()));
+end
+
+query "AllAlerts"
+ $a: /alerts;
+end
+
+rule "No camera when present at home"
+when
+ accumulate( $s: /smartphones ; $count : count($s) ; $count >= 1 );
+ $l: /cctvs[ powered == true ];
+then
+ alerts.add(new Alert("One CCTV is still operating: " + $l.getName()));
+end
+----
+
+In this file there are some example rules to decide whether the overall status of the house is deemed inappropriate, triggering the necessary `Alert` (s).
+
+Rule Unit a central paradigm introduced in Drools 8 that helps users to encapsulate the set of rules and the facts against which those rules will be matched; you can read more information in the https://www.drools.org/learn/documentation.html[Drools documentation].
+
+The facts will be inserted into a `DataStore`, a type-safe entry point. To make everything work, we need to define both the RuleUnit and the DataStore.
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test;
+
+import org.drools.quarkus.quickstart.test.model.Alert;
+import org.drools.quarkus.quickstart.test.model.CCTV;
+import org.drools.quarkus.quickstart.test.model.Light;
+import org.drools.quarkus.quickstart.test.model.Smartphone;
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitData;
+
+public class HomeRuleUnitData implements RuleUnitData {
+
+ private final DataStore lights;
+ private final DataStore cctvs;
+ private final DataStore smartphones;
+
+ private final DataStore alerts = DataSource.createStore();
+
+ public HomeRuleUnitData() {
+ this(DataSource.createStore(), DataSource.createStore(), DataSource.createStore());
+ }
+
+ public HomeRuleUnitData(DataStore lights, DataStore cctvs, DataStore smartphones) {
+ this.lights = lights;
+ this.cctvs = cctvs;
+ this.smartphones = smartphones;
+ }
+
+ public DataStore getLights() {
+ return lights;
+ }
+
+ public DataStore getCctvs() {
+ return cctvs;
+ }
+
+ public DataStore getSmartphones() {
+ return smartphones;
+ }
+
+ public DataStore getAlerts() {
+ return alerts;
+ }
+}
+----
+
+=== Testing the Application
+
+We can create a standard Quarkus and JUnit test to check the behaviour of the Rule Unit and the defined rules, accordingly to a certain set of scenarios.
+
+[source,java]
+----
+package org.drools.quarkus.quickstart.test;
+
+@QuarkusTest
+public class RuntimeIT {
+
+ @Inject
+ RuleUnit ruleUnit;
+
+ @Test
+ public void testRuleOutside() {
+ HomeRuleUnitData homeUnitData = new HomeRuleUnitData();
+ homeUnitData.getLights().add(new Light("living room", true));
+ homeUnitData.getLights().add(new Light("bedroom", false));
+ homeUnitData.getLights().add(new Light("bathroom", false));
+
+ RuleUnitInstance unitInstance = ruleUnit.createInstance(homeUnitData);
+ List
getTables(String datasource) {
+ if (isDev) {
+ List
tableList = new ArrayList<>();
+ try {
+ AgroalDataSource ads = (AgroalDataSource) checkedDataSources.get(datasource);
+ if (isAllowedDatabase(ads)) {
+ try (Connection connection = ads.getConnection()) {
+ DatabaseMetaData metaData = connection.getMetaData();
+
+ // Get all tables
+ try (ResultSet tables = metaData.getTables(null, null, "%", new String[] { "TABLE" })) {
+ while (tables.next()) {
+ String tableName = tables.getString("TABLE_NAME");
+ String tableSchema = tables.getString("TABLE_SCHEM");
+
+ // Get the Primary Keys
+ List primaryKeyList = getPrimaryKeys(metaData, tableSchema, tableName);
+
+ // Get columns for each table
+ List columnList = new ArrayList<>();
+ try (ResultSet columns = metaData.getColumns(null, tableSchema, tableName, "%")) {
+ while (columns.next()) {
+ String columnName = columns.getString("COLUMN_NAME");
+ String columnType = columns.getString("TYPE_NAME");
+ int columnSize = columns.getInt("COLUMN_SIZE");
+ String nullable = columns.getString("IS_NULLABLE");
+ int dataType = columns.getInt("DATA_TYPE");
+ columnList
+ .add(new Column(columnName, columnType, columnSize, nullable,
+ isBinary(dataType)));
+
+ }
+ }
+ tableList.add(new Table(tableSchema, tableName, primaryKeyList, columnList));
+ }
+ }
+ }
+ }
+ } catch (SQLException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ return tableList;
+ }
+ return null;
+ }
+
+ public DataSet executeSQL(String datasource, String sql, Integer pageNumber, Integer pageSize) {
+ if (isDev && sqlIsValid(sql)) {
+ try {
+ AgroalDataSource ads = (AgroalDataSource) checkedDataSources.get(datasource);
+ if (isAllowedDatabase(ads)) {
+ try (Connection connection = ads.getConnection()) {
+ // Create a scrollable ResultSet
+ try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY)) {
+ boolean hasResultSet = statement.execute(sql);
+ if (hasResultSet) {
+ try (ResultSet resultSet = statement.executeQuery(sql)) {
+
+ // Get the total number of rows
+ resultSet.last();
+ int totalNumberOfElements = resultSet.getRow();
+
+ // Get the column metadata
+ List cols = new ArrayList<>();
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int columnCount = metaData.getColumnCount();
+ for (int i = 1; i <= columnCount; i++) {
+ String columnName = metaData.getColumnName(i);
+ cols.add(columnName);
+ }
+
+ int startRow = (pageNumber - 1) * pageSize + 1;
+
+ List> rows = new ArrayList<>();
+
+ if (resultSet.absolute(startRow)) {
+ int rowCount = 0;
+
+ do {
+ Map row = new HashMap<>();
+ for (int i = 1; i <= columnCount; i++) {
+ String columnName = metaData.getColumnName(i);
+ boolean isBinary = isBinary(metaData.getColumnType(i));
+ if (!isBinary) {
+ Object columnValue = resultSet.getObject(i);
+ row.put(columnName, String.valueOf(columnValue));
+ } else {
+ byte[] binary = resultSet.getBytes(i);
+ if (!resultSet.wasNull()) {
+ row.put(columnName, Base64.getEncoder().encodeToString(binary));
+ } else {
+ row.put(columnName, null);
+ }
+ }
+ }
+ rows.add(row);
+ rowCount++;
+ } while (resultSet.next() && rowCount < pageSize);
+ }
+ return new DataSet(cols, rows, null, null, totalNumberOfElements);
+ }
+ } else {
+ // Query did not return a ResultSet (e.g., DELETE, UPDATE)
+ int updateCount = statement.getUpdateCount();
+ String message = "Query executed successfully. Rows affected: " + updateCount;
+ return new DataSet(null, null, null, message, -1);
+ }
+ } catch (Exception e) {
+ return new DataSet(null, null, e.getMessage(), null, -1);
+ }
+ }
+ } else {
+ return new DataSet(null, null, "Only supported for Local Databases", null, -1);
+ }
+ } catch (SQLException ex) {
+ return new DataSet(null, null, ex.getMessage(), null, -1);
+ }
+ } else {
+ return new DataSet(null, null, "Unknown Error", null, -1);
+ }
+ }
+
+ public String getInsertScript(String datasource) {
+ if (isDev) {
+ try {
+ AgroalDataSource ads = (AgroalDataSource) checkedDataSources.get(datasource);
+ if (isAllowedDatabase(ads)) {
+ try (Connection connection = ads.getConnection();
+ StringWriter writer = new StringWriter()) {
+ DatabaseMetaData metaData = connection.getMetaData();
+ try (ResultSet tables = metaData.getTables(null, null, "%", new String[] { "TABLE" })) {
+ while (tables.next()) {
+ String tableName = tables.getString("TABLE_NAME");
+ exportTable(connection, writer, tableName);
+ }
+ }
+
+ return writer.toString();
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ }
+ } catch (SQLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ return null;
+ }
+
+ private void exportTable(Connection conn, StringWriter writer, String tableName) throws SQLException, IOException {
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) {
+
+ ResultSetMetaData metaData = rs.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ while (rs.next()) {
+ StringBuilder insertQuery = new StringBuilder("INSERT INTO " + tableName + " (");
+
+ for (int i = 1; i <= columnCount; i++) {
+ insertQuery.append(metaData.getColumnName(i));
+ if (i < columnCount)
+ insertQuery.append(", ");
+ }
+
+ insertQuery.append(") VALUES (");
+
+ for (int i = 1; i <= columnCount; i++) {
+ Object value = rs.getObject(i);
+ if (value == null) {
+ insertQuery.append("NULL");
+ } else if (value instanceof String || value instanceof Date || value instanceof Timestamp) {
+ insertQuery.append("'").append(value.toString().replace("'", "''")).append("'");
+ } else {
+ insertQuery.append(value.toString());
+ }
+ if (i < columnCount)
+ insertQuery.append(", ");
+ }
+
+ insertQuery.append(");\n");
+ writer.write(insertQuery.toString());
+ }
+ }
+ }
+
+ private boolean sqlIsValid(String sql) {
+ if (sql == null || sql.isEmpty())
+ return false;
+ if (allowSql) {
+ return true;
+ } else {
+ String lsql = sql.toLowerCase().trim();
+ return lsql.startsWith("select")
+ && !lsql.contains("update ")
+ && !lsql.contains("delete ")
+ && !lsql.contains("insert ")
+ && !lsql.contains("create ")
+ && !lsql.contains("drop "); // Having a sql with those nested is invalid anyway
+ }
+ }
+
+ private List getPrimaryKeys(DatabaseMetaData metaData, String tableSchema, String tableName) throws SQLException {
+ List primaryKeyList = new ArrayList<>();
+ try (ResultSet primaryKeys = metaData.getPrimaryKeys(null, tableSchema, tableName)) {
+ while (primaryKeys.next()) {
+ String primaryKeyColumn = primaryKeys.getString("COLUMN_NAME");
+ primaryKeyList.add(primaryKeyColumn);
+ }
+ }
+ return primaryKeyList;
+ }
+
+ private boolean isAllowedDatabase(AgroalDataSource ads) {
+ AgroalDataSourceConfiguration configuration = ads.getConfiguration();
+ String jdbcUrl = configuration.connectionPoolConfiguration().connectionFactoryConfiguration().jdbcUrl();
+
+ try {
+ if (jdbcUrl.startsWith("jdbc:h2:mem:") || jdbcUrl.startsWith("jdbc:h2:file:")
+ || jdbcUrl.startsWith("jdbc:h2:tcp://localhost")
+ || (this.allowedHost != null && !this.allowedHost.isBlank()
+ && jdbcUrl.startsWith("jdbc:h2:tcp://" + this.allowedHost))
+ || jdbcUrl.startsWith("jdbc:derby:memory:")) {
+ return true;
+ }
+
+ String cleanUrl = jdbcUrl.replace("jdbc:", "");
+ URI uri = new URI(cleanUrl);
+
+ String host = uri.getHost();
+
+ return host != null && ((host.equals("localhost") || host.equals("127.0.0.1") || host.equals("::1")) ||
+ (this.allowedHost != null && !this.allowedHost.isBlank() && host.equalsIgnoreCase(this.allowedHost)));
+
+ } catch (URISyntaxException e) {
+ Log.warn(e.getMessage());
+ }
+
+ return false;
+ }
+
+ private boolean isBinary(int dataType) {
+ return dataType == Types.BLOB ||
+ dataType == Types.VARBINARY ||
+ dataType == Types.LONGVARBINARY ||
+ dataType == Types.BINARY ||
+ dataType == Types.JAVA_OBJECT ||
+ dataType == Types.OTHER;
+ }
+
+ private static record Column(String columnName, String columnType, int columnSize, String nullable, boolean binary) {
+ }
+
+ private static record Table(String tableSchema, String tableName, List primaryKeys, List columns) {
+ }
+
+ private static record Datasource(String name, String jdbcUrl, boolean isDefault) {
+ }
+
+ private static record DataSet(List cols, List> data, String error, String message,
+ int totalNumberOfElements) {
+ }
+}
diff --git a/extensions/agroal/runtime/pom.xml b/extensions/agroal/runtime/pom.xml
index 1706cf5f5d53c..72a2be3bbbfc6 100644
--- a/extensions/agroal/runtime/pom.xml
+++ b/extensions/agroal/runtime/pom.xml
@@ -90,6 +90,19 @@
io.quarkus.agroal
+
+
+ process-resources
+
+ extension-descriptor
+
+
+
+ ${project.groupId}:${project.artifactId}-dev:${project.version}
+
+
+
+ maven-compiler-plugin
diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourcesJdbcBuildTimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourcesJdbcBuildTimeConfig.java
index b8a76bf2158d4..c239b2be94d7f 100644
--- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourcesJdbcBuildTimeConfig.java
+++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourcesJdbcBuildTimeConfig.java
@@ -1,6 +1,7 @@
package io.quarkus.agroal.runtime;
import java.util.Map;
+import java.util.Optional;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.runtime.annotations.ConfigDocMapKey;
@@ -8,7 +9,9 @@
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
import io.smallrye.config.WithDefaults;
+import io.smallrye.config.WithName;
import io.smallrye.config.WithParentName;
import io.smallrye.config.WithUnnamedKey;
@@ -25,6 +28,13 @@ public interface DataSourcesJdbcBuildTimeConfig {
@WithUnnamedKey(DataSourceUtil.DEFAULT_DATASOURCE_NAME)
Map dataSources();
+ /**
+ * Dev UI.
+ */
+ @WithDefaults
+ @WithName("dev-ui")
+ DevUIBuildTimeConfig devui();
+
@ConfigGroup
public interface DataSourceJdbcOuterNamedBuildTimeConfig {
@@ -33,4 +43,30 @@ public interface DataSourceJdbcOuterNamedBuildTimeConfig {
*/
DataSourceJdbcBuildTimeConfig jdbc();
}
+
+ @ConfigGroup
+ public interface DevUIBuildTimeConfig {
+
+ /**
+ * Activate or disable the dev ui page.
+ */
+ @WithDefault("true")
+ public boolean enabled();
+
+ /**
+ * Allow sql queries in the Dev UI page
+ */
+ @WithDefault("false")
+ public boolean allowSql();
+
+ /**
+ * Append this to the select done to fetch the table values. eg: LIMIT 100 or TOP 100
+ */
+ public Optional appendToDefaultSelect();
+
+ /**
+ * Allowed database host. By default only localhost is allowed. Any provided host here will also be allowed
+ */
+ public Optional allowedDBHost();
+ }
}
diff --git a/extensions/amazon-lambda-http/deployment/pom.xml b/extensions/amazon-lambda-http/deployment/pom.xml
index d7088a8788cc3..9dfa37b46b7a1 100644
--- a/extensions/amazon-lambda-http/deployment/pom.xml
+++ b/extensions/amazon-lambda-http/deployment/pom.xml
@@ -68,9 +68,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java b/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
index dbe75f0b8d76f..e64a2b3e3b5a8 100644
--- a/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
+++ b/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
@@ -49,7 +49,7 @@ public void setupCDI(BuildProducer additionalBeans) {
@BuildStep
public void setupSecurity(BuildProducer additionalBeans,
LambdaHttpBuildTimeConfig config) {
- if (!config.enableSecurity)
+ if (!config.enableSecurity())
return;
AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder().setUnremovable();
diff --git a/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java b/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
index ba81e36664ba0..6d4d9c546bc78 100644
--- a/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
+++ b/extensions/amazon-lambda-http/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
@@ -1,14 +1,16 @@
package io.quarkus.amazon.lambda.http.deployment;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
@ConfigRoot
-public class LambdaHttpBuildTimeConfig {
+@ConfigMapping(prefix = "quarkus.lambda-http")
+public interface LambdaHttpBuildTimeConfig {
/**
* Enable security mechanisms to process lambda and AWS based security (i.e. Cognito, IAM) from
* the http event sent from API Gateway
*/
- @ConfigItem(defaultValue = "false")
- public boolean enableSecurity;
+ @WithDefault("false")
+ boolean enableSecurity();
}
diff --git a/extensions/amazon-lambda-http/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/amazon-lambda-http/maven-archetype/src/main/resources/archetype-resources/pom.xml
index 4a7f0c66550b1..fc4a0adb4899e 100644
--- a/extensions/amazon-lambda-http/maven-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/extensions/amazon-lambda-http/maven-archetype/src/main/resources/archetype-resources/pom.xml
@@ -18,7 +18,7 @@
quarkus-bomio.quarkus999-SNAPSHOT
- 3.5.0
+ 3.5.2
diff --git a/extensions/amazon-lambda-http/pom.xml b/extensions/amazon-lambda-http/pom.xml
index 78c20a2fb26b2..4d8f0ce264b8f 100644
--- a/extensions/amazon-lambda-http/pom.xml
+++ b/extensions/amazon-lambda-http/pom.xml
@@ -19,7 +19,6 @@
runtimehttp-event-serverdeployment
-
maven-archetype
diff --git a/extensions/amazon-lambda-http/runtime/pom.xml b/extensions/amazon-lambda-http/runtime/pom.xml
index f71b749e0497d..6a54eb16c6040 100644
--- a/extensions/amazon-lambda-http/runtime/pom.xml
+++ b/extensions/amazon-lambda-http/runtime/pom.xml
@@ -76,9 +76,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
index a6a8714b7bf06..419388e56733c 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
+++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
@@ -76,8 +76,8 @@ public long getIssuedAtTime() {
@Override
public Set getGroups() {
if (groups == null) {
- if (jwt.getClaims().containsKey(LambdaHttpRecorder.config.cognitoRoleClaim)) {
- String claim = jwt.getClaims().get(LambdaHttpRecorder.config.cognitoRoleClaim);
+ if (jwt.getClaims().containsKey(LambdaHttpRecorder.config.cognitoRoleClaim())) {
+ String claim = jwt.getClaims().get(LambdaHttpRecorder.config.cognitoRoleClaim());
Matcher matcher = LambdaHttpRecorder.groupPattern.matcher(claim);
groups = new HashSet<>();
while (matcher.find()) {
diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
index 4984b060fc758..41dce4966df13 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
+++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
@@ -27,7 +27,7 @@ public Class getRequestType() {
public Uni authenticate(DefaultLambdaAuthenticationRequest request,
AuthenticationRequestContext context) {
APIGatewayV2HTTPEvent event = request.getEvent();
- SecurityIdentity identity = authenticate(event, LambdaHttpRecorder.config.mapCognitoToRoles);
+ SecurityIdentity identity = authenticate(event, LambdaHttpRecorder.config.mapCognitoToRoles());
if (identity == null) {
return Uni.createFrom().optional(Optional.empty());
}
diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
index a89d1ec1159fa..acb5db14f201c 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
+++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
@@ -1,34 +1,36 @@
package io.quarkus.amazon.lambda.http;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
-public class LambdaHttpConfig {
+@ConfigMapping(prefix = "quarkus.lambda-http")
+public interface LambdaHttpConfig {
/**
* If true, Quarkus will map claims from Cognito to Quarkus security roles.
* The "cognito:groups" claim will be used by default. Change cognitoRoleClaim
* config value to change the claim source.
- *
+ *
* True by default
*/
- @ConfigItem(defaultValue = "true")
- public boolean mapCognitoToRoles;
+ @WithDefault("true")
+ boolean mapCognitoToRoles();
/**
* Cognito claim that contains roles you want to map. Defaults to "cognito:groups"
*/
- @ConfigItem(defaultValue = "cognito:groups")
- public String cognitoRoleClaim;
+ @WithDefault("cognito:groups")
+ String cognitoRoleClaim();
/**
* Regular expression to locate role values within a Cognito claim string.
- * By default it looks for space delimited strings enclosed in brackets
+ * By default, it looks for space delimited strings enclosed in brackets
* "[^\[\] \t]+"
*/
- @ConfigItem(defaultValue = "[^\\[\\] \\t]+")
- public String cognitoClaimMatcher;
+ @WithDefault(value = "[^\\[\\] \\t]+")
+ String cognitoClaimMatcher();
}
diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
index c5c6a40677b35..616ca65c9c421 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
+++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
@@ -11,7 +11,7 @@ public class LambdaHttpRecorder {
public void setConfig(LambdaHttpConfig c) {
config = c;
- String pattern = c.cognitoClaimMatcher;
+ String pattern = c.cognitoClaimMatcher();
groupPattern = Pattern.compile(pattern);
}
}
diff --git a/extensions/amazon-lambda-rest/deployment/pom.xml b/extensions/amazon-lambda-rest/deployment/pom.xml
index a4680f09844c4..8a9646c238b2d 100644
--- a/extensions/amazon-lambda-rest/deployment/pom.xml
+++ b/extensions/amazon-lambda-rest/deployment/pom.xml
@@ -68,9 +68,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
index d9c77a471769b..8925222267404 100644
--- a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
+++ b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
@@ -49,7 +49,7 @@ public void setupCDI(BuildProducer additionalBeans) {
@BuildStep
public void setupSecurity(BuildProducer additionalBeans,
LambdaHttpBuildTimeConfig config) {
- if (!config.enableSecurity)
+ if (!config.enableSecurity())
return;
AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder().setUnremovable();
diff --git a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
index ba81e36664ba0..6d4d9c546bc78 100644
--- a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
+++ b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/LambdaHttpBuildTimeConfig.java
@@ -1,14 +1,16 @@
package io.quarkus.amazon.lambda.http.deployment;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
@ConfigRoot
-public class LambdaHttpBuildTimeConfig {
+@ConfigMapping(prefix = "quarkus.lambda-http")
+public interface LambdaHttpBuildTimeConfig {
/**
* Enable security mechanisms to process lambda and AWS based security (i.e. Cognito, IAM) from
* the http event sent from API Gateway
*/
- @ConfigItem(defaultValue = "false")
- public boolean enableSecurity;
+ @WithDefault("false")
+ boolean enableSecurity();
}
diff --git a/extensions/amazon-lambda-rest/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/amazon-lambda-rest/maven-archetype/src/main/resources/archetype-resources/pom.xml
index b2102594bf46d..4d72ebc7bf0b7 100644
--- a/extensions/amazon-lambda-rest/maven-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/extensions/amazon-lambda-rest/maven-archetype/src/main/resources/archetype-resources/pom.xml
@@ -18,7 +18,7 @@
quarkus-bomio.quarkus999-SNAPSHOT
- 3.5.0
+ 3.5.2
diff --git a/extensions/amazon-lambda-rest/runtime/pom.xml b/extensions/amazon-lambda-rest/runtime/pom.xml
index 9dd7859810e4c..b5c1e2111b4f0 100644
--- a/extensions/amazon-lambda-rest/runtime/pom.xml
+++ b/extensions/amazon-lambda-rest/runtime/pom.xml
@@ -57,9 +57,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
index 3f49b91c199de..54c9c13530df3 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
+++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/CognitoPrincipal.java
@@ -81,7 +81,7 @@ public long getIssuedAtTime() {
@Override
public Set getGroups() {
if (groups == null) {
- String grpClaim = claims.getClaim(LambdaHttpRecorder.config.cognitoRoleClaim);
+ String grpClaim = claims.getClaim(LambdaHttpRecorder.config.cognitoRoleClaim());
if (grpClaim != null) {
Matcher matcher = LambdaHttpRecorder.groupPattern.matcher(grpClaim);
groups = new HashSet<>();
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
index f9724fa8b4837..002d2af6dd60a 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
+++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/DefaultLambdaIdentityProvider.java
@@ -27,7 +27,7 @@ public Class getRequestType() {
public Uni authenticate(DefaultLambdaAuthenticationRequest request,
AuthenticationRequestContext context) {
AwsProxyRequest event = request.getEvent();
- SecurityIdentity identity = authenticate(event, LambdaHttpRecorder.config.mapCognitoToRoles);
+ SecurityIdentity identity = authenticate(event, LambdaHttpRecorder.config.mapCognitoToRoles());
if (identity == null) {
return Uni.createFrom().optional(Optional.empty());
}
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
index 08155dc49f59c..55ebc8c911fac 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
+++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpConfig.java
@@ -1,11 +1,13 @@
package io.quarkus.amazon.lambda.http;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
-public class LambdaHttpConfig {
+@ConfigMapping(prefix = "quarkus.lambda-http")
+public interface LambdaHttpConfig {
/**
* If true, runtime will search Cognito JWT claims for "cognito:groups"
@@ -13,20 +15,20 @@ public class LambdaHttpConfig {
*
* True by default
*/
- @ConfigItem(defaultValue = "true")
- public boolean mapCognitoToRoles;
+ @WithDefault("true")
+ boolean mapCognitoToRoles();
/**
* Cognito claim that contains roles you want to map. Defaults to "cognito:groups"
*/
- @ConfigItem(defaultValue = "cognito:groups")
- public String cognitoRoleClaim;
+ @WithDefault("cognito:groups")
+ String cognitoRoleClaim();
/**
* Regular expression to locate role values within a Cognito claim string.
- * By default it looks for space delimited strings enclosed in brackets
+ * By default, it looks for space delimited strings enclosed in brackets
* "[^\[\] \t]+"
*/
- @ConfigItem(defaultValue = "[^\\[\\] \\t]+")
- public String cognitoClaimMatcher;
+ @WithDefault("[^\\[\\] \\t]+")
+ String cognitoClaimMatcher();
}
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
index c5c6a40677b35..616ca65c9c421 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
+++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpRecorder.java
@@ -11,7 +11,7 @@ public class LambdaHttpRecorder {
public void setConfig(LambdaHttpConfig c) {
config = c;
- String pattern = c.cognitoClaimMatcher;
+ String pattern = c.cognitoClaimMatcher();
groupPattern = Pattern.compile(pattern);
}
}
diff --git a/extensions/amazon-lambda/common-deployment/pom.xml b/extensions/amazon-lambda/common-deployment/pom.xml
index 6fa00eea9b452..0fd84942a5f72 100644
--- a/extensions/amazon-lambda/common-deployment/pom.xml
+++ b/extensions/amazon-lambda/common-deployment/pom.xml
@@ -61,9 +61,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java
index c30f5e707a21a..84c3f128445fb 100644
--- a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java
@@ -63,7 +63,7 @@ public void startEventServer(LaunchModeBuildItem launchMode,
return;
if (legacyTestingEnabled())
return;
- if (!config.mockEventServer.enabled) {
+ if (!config.mockEventServer().enabled()) {
return;
}
if (server != null) {
@@ -77,8 +77,8 @@ public void startEventServer(LaunchModeBuildItem launchMode,
}
server = supplier.get();
- int port = launchMode.getLaunchMode() == LaunchMode.TEST ? config.mockEventServer.testPort
- : config.mockEventServer.devPort;
+ int port = launchMode.getLaunchMode() == LaunchMode.TEST ? config.mockEventServer().testPort()
+ : config.mockEventServer().devPort();
startMode = launchMode.getLaunchMode();
server.start(port);
int actualPort = server.getPort();
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java
index e9dcc8e4c3e61..36c15e3e3a77e 100644
--- a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java
@@ -2,13 +2,15 @@
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
-public class LambdaConfig {
+@ConfigMapping(prefix = "quarkus.lambda")
+public interface LambdaConfig {
/**
* Configuration for the mock event server that is run
* in dev mode and test mode
*/
- MockEventServerConfig mockEventServer;
+ MockEventServerConfig mockEventServer();
}
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java
index 85cde4c70a1fb..c2aa666e9810f 100644
--- a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java
@@ -1,29 +1,27 @@
package io.quarkus.amazon.lambda.deployment;
-import io.quarkus.runtime.annotations.ConfigGroup;
-import io.quarkus.runtime.annotations.ConfigItem;
+import io.smallrye.config.WithDefault;
/**
* Configuration for the mock event server that is run
* in dev mode and test mode
*/
-@ConfigGroup
-public class MockEventServerConfig {
+public interface MockEventServerConfig {
/**
* Setting to true will start event server even if quarkus.devservices.enabled=false
*/
- @ConfigItem(defaultValue = "true")
- public boolean enabled;
+ @WithDefault("true")
+ boolean enabled();
/**
* Port to access mock event server in dev mode
*/
- @ConfigItem(defaultValue = "8080")
- public int devPort;
+ @WithDefault("8080")
+ int devPort();
/**
* Port to access mock event server in dev mode
*/
- @ConfigItem(defaultValue = "8081")
- public int testPort;
+ @WithDefault("8081")
+ int testPort();
}
diff --git a/extensions/amazon-lambda/common-runtime/pom.xml b/extensions/amazon-lambda/common-runtime/pom.xml
index eb8a8bab3501b..36b98ed34d715 100644
--- a/extensions/amazon-lambda/common-runtime/pom.xml
+++ b/extensions/amazon-lambda/common-runtime/pom.xml
@@ -72,9 +72,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda/deployment/pom.xml b/extensions/amazon-lambda/deployment/pom.xml
index 083bdd130e531..2eb03cfce23ff 100644
--- a/extensions/amazon-lambda/deployment/pom.xml
+++ b/extensions/amazon-lambda/deployment/pom.xml
@@ -62,9 +62,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java
index d7b631b826e24..6ce046aa7da98 100644
--- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java
+++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java
@@ -333,7 +333,7 @@ void startPoolLoopDevOrTest(AmazonLambdaRecorder recorder,
void recordExpectedExceptions(LambdaBuildTimeConfig config,
BuildProducer registerForReflection,
AmazonLambdaStaticRecorder recorder) {
- Set> classes = config.expectedExceptions.map(Set::copyOf).orElseGet(Set::of);
+ Set> classes = config.expectedExceptions().map(Set::copyOf).orElseGet(Set::of);
classes.stream()
.map(clazz -> ReflectiveClassBuildItem.builder(clazz).constructors(false)
.reason(getClass().getName() + " expectedExceptions")
diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
index 601a9675f432a..fe81817155cd8 100644
--- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
@@ -17,7 +17,7 @@
quarkus-bomio.quarkus999-SNAPSHOT
- 3.5.0
+ 3.5.2
diff --git a/extensions/amazon-lambda/runtime/pom.xml b/extensions/amazon-lambda/runtime/pom.xml
index 0a565e1dbe5e1..e12340e996247 100644
--- a/extensions/amazon-lambda/runtime/pom.xml
+++ b/extensions/amazon-lambda/runtime/pom.xml
@@ -51,9 +51,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java
index 49dc6d9d92a2b..6ff6371ae775b 100644
--- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java
+++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java
@@ -120,12 +120,12 @@ public void chooseHandlerClass(List>> unnam
Class extends RequestHandler, ?>> handlerClass = null;
Class extends RequestStreamHandler> handlerStreamClass = null;
- if (config.handler.isPresent()) {
- handlerClass = namedHandlerClasses.get(config.handler.get());
- handlerStreamClass = namedStreamHandlerClasses.get(config.handler.get());
+ if (config.handler().isPresent()) {
+ handlerClass = namedHandlerClasses.get(config.handler().get());
+ handlerStreamClass = namedStreamHandlerClasses.get(config.handler().get());
if (handlerClass == null && handlerStreamClass == null) {
- String errorMessage = "Unable to find handler class with name " + config.handler.get()
+ String errorMessage = "Unable to find handler class with name " + config.handler().get()
+ " make sure there is a handler class in the deployment with the correct @Named annotation";
throw new RuntimeException(errorMessage);
}
diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java
index 4a035af4fb7ee..5bf355cef4911 100644
--- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java
+++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java
@@ -3,12 +3,13 @@
import java.util.List;
import java.util.Optional;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
-@ConfigRoot(name = "lambda", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
-public class LambdaBuildTimeConfig {
+@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+@ConfigMapping(prefix = "quarkus.lambda")
+public interface LambdaBuildTimeConfig {
/**
* The exception classes expected to be thrown by the handler.
@@ -17,6 +18,5 @@ public class LambdaBuildTimeConfig {
* but will otherwise be handled normally by the lambda runtime. This is useful for avoiding unnecessary
* stack traces while preserving the ability to log unexpected exceptions.
*/
- @ConfigItem
- public Optional>> expectedExceptions;
+ Optional>> expectedExceptions();
}
diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
index 8335b3f3d3a08..0aceb7a4f08ef 100644
--- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
+++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java
@@ -2,12 +2,13 @@
import java.util.Optional;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
-public class LambdaConfig {
+@ConfigMapping(prefix = "quarkus.lambda")
+public interface LambdaConfig {
/**
* The handler name. Handler names are specified on handler classes using the {@link @jakarta.inject.Named} annotation.
@@ -18,6 +19,5 @@ public class LambdaConfig {
* then the named handler will be used.
*
*/
- @ConfigItem
- public Optional handler;
+ Optional handler();
}
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
index ff175046a6960..a89b988cb9184 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java
@@ -68,16 +68,13 @@
import io.quarkus.arc.runtime.LoggerProducer;
import io.quarkus.arc.runtime.appcds.AppCDSRecorder;
import io.quarkus.arc.runtime.context.ArcContextProvider;
-import io.quarkus.arc.runtime.test.PreloadedTestApplicationClassPredicate;
import io.quarkus.bootstrap.BootstrapDebug;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
-import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
-import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem;
@@ -653,27 +650,6 @@ public void signalBeanContainerReady(AppCDSRecorder recorder, PreBeanContainerBu
beanContainerProducer.produce(new BeanContainerBuildItem(bi.getValue()));
}
- @BuildStep(onlyIf = IsTest.class)
- public AdditionalBeanBuildItem testApplicationClassPredicateBean() {
- // We need to register the bean implementation for TestApplicationClassPredicate
- // TestApplicationClassPredicate is used programmatically in the ArC recorder when StartupEvent is fired
- return AdditionalBeanBuildItem.unremovableOf(PreloadedTestApplicationClassPredicate.class);
- }
-
- @BuildStep(onlyIf = IsTest.class)
- @Record(ExecutionTime.STATIC_INIT)
- void initTestApplicationClassPredicateBean(ArcRecorder recorder, BeanContainerBuildItem beanContainer,
- BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
- CompletedApplicationClassPredicateBuildItem predicate) {
- Set applicationBeanClasses = new HashSet<>();
- for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {
- if (predicate.test(bean.getBeanClass())) {
- applicationBeanClasses.add(bean.getBeanClass().toString());
- }
- }
- recorder.initTestApplicationClassPredicate(applicationBeanClasses);
- }
-
@BuildStep
List marker() {
return Arrays.asList(new AdditionalApplicationArchiveMarkerBuildItem("META-INF/beans.xml"),
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java
new file mode 100644
index 0000000000000..66c86e0b055e5
--- /dev/null
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestSteps.java
@@ -0,0 +1,71 @@
+package io.quarkus.arc.deployment;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTransformation;
+import org.jboss.jandex.DotName;
+
+import io.quarkus.arc.processor.BeanInfo;
+import io.quarkus.arc.runtime.ArcRecorder;
+import io.quarkus.arc.runtime.test.ActivateSessionContextInterceptor;
+import io.quarkus.arc.runtime.test.PreloadedTestApplicationClassPredicate;
+import io.quarkus.deployment.IsTest;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.BuildSteps;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem;
+
+@BuildSteps(onlyIf = IsTest.class)
+public class ArcTestSteps {
+
+ @BuildStep
+ public void additionalBeans(BuildProducer additionalBeans) {
+ // We need to register the bean implementation for TestApplicationClassPredicate
+ // TestApplicationClassPredicate is used programmatically in the ArC recorder when StartupEvent is fired
+ additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(PreloadedTestApplicationClassPredicate.class));
+ // In tests, register the ActivateSessionContextInterceptor and ActivateSessionContext interceptor binding
+ additionalBeans.produce(new AdditionalBeanBuildItem(ActivateSessionContextInterceptor.class));
+ additionalBeans.produce(new AdditionalBeanBuildItem("io.quarkus.test.ActivateSessionContext"));
+ }
+
+ @BuildStep
+ AnnotationsTransformerBuildItem addInterceptorBinding() {
+ return new AnnotationsTransformerBuildItem(
+ AnnotationTransformation.forClasses().whenClass(ActivateSessionContextInterceptor.class).transform(tc -> tc.add(
+ AnnotationInstance.builder(DotName.createSimple("io.quarkus.test.ActivateSessionContext")).build())));
+ }
+
+ // For some reason the annotation literal generated for io.quarkus.test.ActivateSessionContext lives in app class loader.
+ // This predicates ensures that the generated bean is considered an app class too.
+ // As a consequence, the type and all methods of ActivateSessionContextInterceptor must be public.
+ @BuildStep
+ ApplicationClassPredicateBuildItem appClassPredicate() {
+ return new ApplicationClassPredicateBuildItem(new Predicate() {
+
+ @Override
+ public boolean test(String name) {
+ return name.startsWith(ActivateSessionContextInterceptor.class.getName());
+ }
+ });
+ }
+
+ @BuildStep
+ @Record(ExecutionTime.STATIC_INIT)
+ void initTestApplicationClassPredicateBean(ArcRecorder recorder, BeanContainerBuildItem beanContainer,
+ BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
+ CompletedApplicationClassPredicateBuildItem predicate) {
+ Set applicationBeanClasses = new HashSet<>();
+ for (BeanInfo bean : beanDiscoveryFinished.beanStream().classBeans()) {
+ if (predicate.test(bean.getBeanClass())) {
+ applicationBeanClasses.add(bean.getBeanClass().toString());
+ }
+ }
+ recorder.initTestApplicationClassPredicate(applicationBeanClasses);
+ }
+
+}
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java
index 0bee6f858918c..91b10940263cf 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java
@@ -32,6 +32,9 @@
public final class SyntheticBeanBuildItem extends MultiBuildItem {
/**
+ * Returns a configurator object allowing for further customization of the synthetic bean.
+ *
+ * The implementation class is automatically registered as a resulting bean type.
*
* @param implClazz
* @return a new configurator instance
@@ -42,6 +45,9 @@ public static ExtendedBeanConfigurator configure(Class> implClazz) {
}
/**
+ * Returns a configurator object allowing for further customization of the synthetic bean.
+ *
+ * The implementation class is automatically registered as a resulting bean type.
*
* @param implClazz
* @return a new configurator instance
@@ -51,6 +57,32 @@ public static ExtendedBeanConfigurator configure(DotName implClazz) {
return new ExtendedBeanConfigurator(implClazz).addType(implClazz);
}
+ /**
+ * Returns a configurator object allowing for further customization of the synthetic bean.
+ *
+ * Unlike {@link #configure(Class)}, the implementation class is not registered as a resulting bean type.
+ *
+ * @param implClazz
+ * @return a new configurator instance
+ * @see ExtendedBeanConfigurator#done()
+ */
+ public static ExtendedBeanConfigurator create(Class> implClazz) {
+ return create(DotName.createSimple(implClazz.getName()));
+ }
+
+ /**
+ * Returns a configurator object allowing for further customization of the synthetic bean.
+ *
+ * Unlike {@link #configure(DotName)}, the implementation class is not registered as a resulting bean type.
+ *
+ * @param implClazz
+ * @return a new configurator instance
+ * @see ExtendedBeanConfigurator#done()
+ */
+ public static ExtendedBeanConfigurator create(DotName implClazz) {
+ return new ExtendedBeanConfigurator(implClazz);
+ }
+
private final ExtendedBeanConfigurator configurator;
SyntheticBeanBuildItem(ExtendedBeanConfigurator configurator) {
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java
new file mode 100644
index 0000000000000..2be51f601a58f
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/Client.java
@@ -0,0 +1,32 @@
+package io.quarkus.arc.test.context.session;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import jakarta.enterprise.context.Dependent;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.inject.Inject;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ClientProxy;
+import io.quarkus.test.ActivateSessionContext;
+
+@Dependent
+class Client {
+
+ @Inject
+ SimpleBean bean;
+
+ @ActivateSessionContext
+ public String ping() {
+ assertTrue(Arc.container().sessionContext().isActive());
+ if (bean instanceof ClientProxy proxy) {
+ assertEquals(SessionScoped.class, proxy.arc_bean().getScope());
+ } else {
+ fail("Not a client proxy");
+ }
+ return bean.ping();
+ }
+
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java
new file mode 100644
index 0000000000000..b45fbfa2979af
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SessionContextTest.java
@@ -0,0 +1,49 @@
+package io.quarkus.arc.test.context.session;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ManagedContext;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class SessionContextTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(root -> root
+ .addClasses(SimpleBean.class, Client.class));
+
+ @Inject
+ Client client;
+
+ @Inject
+ SimpleBean simpleBean;
+
+ @Test
+ public void testContexts() {
+ assertFalse(Arc.container().sessionContext().isActive());
+ assertNotNull(client.ping());
+ assertTrue(SimpleBean.DESTROYED.get());
+ assertFalse(Arc.container().sessionContext().isActive());
+ SimpleBean.DESTROYED.set(false);
+
+ ManagedContext sessionContext = Arc.container().sessionContext();
+ try {
+ sessionContext.activate();
+ String id = simpleBean.ping();
+ assertEquals(id, client.ping());
+ assertFalse(SimpleBean.DESTROYED.get());
+ } finally {
+ sessionContext.terminate();
+ }
+ assertTrue(SimpleBean.DESTROYED.get());
+ }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java
new file mode 100644
index 0000000000000..77bb8f5f81483
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/session/SimpleBean.java
@@ -0,0 +1,30 @@
+package io.quarkus.arc.test.context.session;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.context.SessionScoped;
+
+@SessionScoped
+class SimpleBean {
+
+ static final AtomicBoolean DESTROYED = new AtomicBoolean();
+
+ private String id;
+
+ @PostConstruct
+ void init() {
+ id = UUID.randomUUID().toString();
+ }
+
+ public String ping() {
+ return id;
+ }
+
+ @PreDestroy
+ void destroy() {
+ DESTROYED.set(true);
+ }
+}
\ No newline at end of file
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/create/SyntheticBeanBuildItemCreateTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/create/SyntheticBeanBuildItemCreateTest.java
new file mode 100644
index 0000000000000..9dbcf9e929612
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/create/SyntheticBeanBuildItemCreateTest.java
@@ -0,0 +1,77 @@
+package io.quarkus.arc.test.synthetic.create;
+
+import java.util.function.Consumer;
+
+import jakarta.enterprise.inject.Vetoed;
+
+import org.jboss.jandex.DotName;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.BeanCreator;
+import io.quarkus.arc.SyntheticCreationalContext;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+
+/**
+ * Tests that {@link SyntheticBeanBuildItem#create(DotName)} does not add automatically register the param type as bean type
+ */
+public class SyntheticBeanBuildItemCreateTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(SyntheticBeanBuildItemCreateTest.class, FooCreator.class, FooInterface.class, Foo.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+
+ @Override
+ public void execute(BuildContext context) {
+ context.produce(SyntheticBeanBuildItem.create(Foo.class)
+ .addType(FooInterface.class)
+ .scope(BuiltinScope.SINGLETON.getInfo())
+ .unremovable()
+ .creator(FooCreator.class)
+ .done());
+ }
+ }).produces(SyntheticBeanBuildItem.class).build();
+ }
+ };
+ }
+
+ @Test
+ public void testBeanTypes() {
+ ArcContainer container = Arc.container();
+ Assertions.assertFalse(container.select(Foo.class).isResolvable());
+ Assertions.assertTrue(container.select(FooInterface.class).isResolvable());
+ }
+
+ @Vetoed
+ public static class Foo implements FooInterface {
+ }
+
+ interface FooInterface {
+ }
+
+ public static class FooCreator implements BeanCreator {
+
+ @Override
+ public Foo create(SyntheticCreationalContext context) {
+ return new Foo();
+ }
+
+ }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/removeTypes/SyntheticBeanBuildItemRemoveTypesTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/removeTypes/SyntheticBeanBuildItemRemoveTypesTest.java
new file mode 100644
index 0000000000000..cbe724fbfc236
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/removeTypes/SyntheticBeanBuildItemRemoveTypesTest.java
@@ -0,0 +1,103 @@
+package io.quarkus.arc.test.synthetic.removeTypes;
+
+import java.util.function.Consumer;
+
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.Type;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.BeanCreator;
+import io.quarkus.arc.SyntheticCreationalContext;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class SyntheticBeanBuildItemRemoveTypesTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(SyntheticBeanBuildItemRemoveTypesTest.class, FooCreator.class, FooInterface.class, Foo.class,
+ FooSubclass.class, Charlie.class, CharlieSubclass.class, CharlieInterface.class, BarInterface.class,
+ BazInterface.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+
+ @Override
+ public void execute(BuildContext context) {
+ context.produce(SyntheticBeanBuildItem.create(FooSubclass.class)
+ .addTypeClosure(FooSubclass.class)
+ .removeTypes(DotName.createSimple(CharlieSubclass.class))
+ .removeTypes(DotName.createSimple(FooSubclass.class))
+ .removeTypes(Type.create(BazInterface.class))
+ .scope(BuiltinScope.SINGLETON.getInfo())
+ .unremovable()
+ .creator(FooCreator.class)
+ .done());
+ }
+ }).produces(SyntheticBeanBuildItem.class).build();
+ }
+ };
+ }
+
+ @Test
+ public void testRemovingBeanTypes() {
+ ArcContainer container = Arc.container();
+ Assertions.assertTrue(container.select(Foo.class).isResolvable());
+ Assertions.assertTrue(container.select(FooInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(BarInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(Charlie.class).isResolvable());
+ Assertions.assertTrue(container.select(CharlieInterface.class).isResolvable());
+
+ // CharlieSubclass, FooSubclass and BazInterface should not be registered as bean types
+ Assertions.assertFalse(container.select(CharlieSubclass.class).isResolvable());
+ Assertions.assertFalse(container.select(FooSubclass.class).isResolvable());
+ Assertions.assertFalse(container.select(BazInterface.class).isResolvable());
+ }
+
+ public static class FooSubclass extends Foo implements FooInterface {
+ }
+
+ public static class Foo extends Charlie implements BazInterface {
+ }
+
+ public static class CharlieSubclass extends Charlie {
+ }
+
+ public static class Charlie implements CharlieInterface {
+ }
+
+ interface CharlieInterface {
+ }
+
+ interface FooInterface extends BarInterface {
+ }
+
+ interface BarInterface {
+ }
+
+ interface BazInterface {
+ }
+
+ public static class FooCreator implements BeanCreator {
+
+ @Override
+ public FooSubclass create(SyntheticCreationalContext context) {
+ return new FooSubclass();
+ }
+
+ }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureGenericsTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureGenericsTest.java
new file mode 100644
index 0000000000000..779aeff0de853
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureGenericsTest.java
@@ -0,0 +1,129 @@
+package io.quarkus.arc.test.synthetic.typeClosure;
+
+import java.util.function.Consumer;
+
+import jakarta.enterprise.util.TypeLiteral;
+
+import org.jboss.jandex.ParameterizedType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.BeanCreator;
+import io.quarkus.arc.SyntheticCreationalContext;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class SyntheticBeanBuildItemAddTypeClosureGenericsTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(SyntheticBeanBuildItemAddTypeClosureGenericsTest.class, FooCreator.class, FooInterface.class,
+ Foo.class,
+ FooSubclass.class, Charlie.class, CharlieSubclass.class, CharlieInterface.class, BarInterface.class,
+ BazInterface.class, Alpha.class, Beta.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+
+ @Override
+ public void execute(BuildContext context) {
+ context.produce(SyntheticBeanBuildItem.create(FooSubclass.class)
+ .addTypeClosure(ParameterizedType.builder(FooSubclass.class).addArgument(Beta.class).build())
+ .scope(BuiltinScope.SINGLETON.getInfo())
+ .unremovable()
+ .creator(FooCreator.class)
+ .done());
+ }
+ }).produces(SyntheticBeanBuildItem.class).build();
+ }
+ };
+ }
+
+ @Test
+ public void testBeanTypesDiscovered() {
+ ArcContainer container = Arc.container();
+
+ // Foo/Bar/Baz interfaces should work normally, no generics there
+ Assertions.assertTrue(container.select(FooInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(BarInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(BazInterface.class).isResolvable());
+
+ // FooSubclass is resolvable only as correct parameterized type
+ Assertions.assertTrue(container.select(new TypeLiteral>() {
+ }).isResolvable());
+ Assertions.assertFalse(container.select(new TypeLiteral>() {
+ }).isResolvable());
+ Assertions.assertFalse(container.select(FooSubclass.class).isResolvable());
+
+ // Foo type should work only parameterized
+ Assertions.assertTrue(container.select(new TypeLiteral>() {
+ }).isResolvable());
+ Assertions.assertFalse(container.select(Foo.class).isResolvable());
+
+ // Foo extends Charlie raw type
+ // we should be able to perform resolution for raw type but not for a parameterized type
+ Assertions.assertTrue(container.select(Charlie.class).isResolvable());
+ Assertions.assertTrue(container.select(CharlieInterface.class).isResolvable());
+ Assertions.assertFalse(container.select(new TypeLiteral>() {
+ }).isResolvable());
+ Assertions.assertFalse(container.select(new TypeLiteral>() {
+ }).isResolvable());
+
+ // CharlieSubclass should not be discovered as bean type
+ Assertions.assertFalse(container.select(CharlieSubclass.class).isResolvable());
+ }
+
+ public static class Alpha {
+
+ }
+
+ public static class Beta {
+
+ }
+
+ public static class FooSubclass extends Foo implements FooInterface {
+ }
+
+ public static class Foo extends Charlie implements BazInterface {
+ }
+
+ public static class CharlieSubclass extends Charlie {
+ }
+
+ public static class Charlie implements CharlieInterface {
+ }
+
+ interface CharlieInterface {
+ }
+
+ interface FooInterface extends BarInterface {
+ }
+
+ interface BarInterface {
+ }
+
+ interface BazInterface {
+ }
+
+ public static class FooCreator implements BeanCreator> {
+
+ @Override
+ public FooSubclass create(SyntheticCreationalContext> context) {
+ return new FooSubclass();
+ }
+
+ }
+}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureTest.java
new file mode 100644
index 0000000000000..3801726f44eb7
--- /dev/null
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/synthetic/typeClosure/SyntheticBeanBuildItemAddTypeClosureTest.java
@@ -0,0 +1,100 @@
+package io.quarkus.arc.test.synthetic.typeClosure;
+
+import java.util.function.Consumer;
+
+import org.jboss.jandex.DotName;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.BeanCreator;
+import io.quarkus.arc.SyntheticCreationalContext;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class SyntheticBeanBuildItemAddTypeClosureTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(SyntheticBeanBuildItemAddTypeClosureTest.class, FooCreator.class, FooInterface.class, Foo.class,
+ FooSubclass.class, Charlie.class, CharlieSubclass.class, CharlieInterface.class, BarInterface.class,
+ BazInterface.class))
+ .addBuildChainCustomizer(buildCustomizer());
+
+ static Consumer buildCustomizer() {
+ return new Consumer() {
+
+ @Override
+ public void accept(BuildChainBuilder builder) {
+ builder.addBuildStep(new BuildStep() {
+
+ @Override
+ public void execute(BuildContext context) {
+ context.produce(SyntheticBeanBuildItem.create(FooSubclass.class)
+ .addTypeClosure(FooInterface.class)
+ .addTypeClosure(DotName.createSimple(Foo.class))
+ .scope(BuiltinScope.SINGLETON.getInfo())
+ .unremovable()
+ .creator(FooCreator.class)
+ .done());
+ }
+ }).produces(SyntheticBeanBuildItem.class).build();
+ }
+ };
+ }
+
+ @Test
+ public void testBeanTypesDiscovered() {
+ ArcContainer container = Arc.container();
+ Assertions.assertTrue(container.select(Foo.class).isResolvable());
+ Assertions.assertTrue(container.select(FooInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(BarInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(Charlie.class).isResolvable());
+ Assertions.assertTrue(container.select(CharlieInterface.class).isResolvable());
+ Assertions.assertTrue(container.select(BazInterface.class).isResolvable());
+
+ // Charlie and Foo subclasses should not be registered as bean types
+ Assertions.assertFalse(container.select(CharlieSubclass.class).isResolvable());
+ Assertions.assertFalse(container.select(FooSubclass.class).isResolvable());
+ }
+
+ public static class FooSubclass extends Foo implements FooInterface {
+ }
+
+ public static class Foo extends Charlie implements BazInterface {
+ }
+
+ public static class CharlieSubclass extends Charlie {
+ }
+
+ public static class Charlie implements CharlieInterface {
+ }
+
+ interface CharlieInterface {
+ }
+
+ interface FooInterface extends BarInterface {
+ }
+
+ interface BarInterface {
+ }
+
+ interface BazInterface {
+ }
+
+ public static class FooCreator implements BeanCreator {
+
+ @Override
+ public FooSubclass create(SyntheticCreationalContext context) {
+ return new FooSubclass();
+ }
+
+ }
+}
diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java
new file mode 100644
index 0000000000000..ea5452e2babc7
--- /dev/null
+++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/test/ActivateSessionContextInterceptor.java
@@ -0,0 +1,30 @@
+package io.quarkus.arc.runtime.test;
+
+import jakarta.annotation.Priority;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InvocationContext;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ManagedContext;
+
+// The @ActivateSessionContext interceptor binding is added by the extension
+@Interceptor
+@Priority(Interceptor.Priority.PLATFORM_BEFORE + 100)
+public class ActivateSessionContextInterceptor {
+
+ @AroundInvoke
+ public Object aroundInvoke(InvocationContext ctx) throws Exception {
+ ManagedContext sessionContext = Arc.container().sessionContext();
+ if (sessionContext.isActive()) {
+ return ctx.proceed();
+ }
+ try {
+ sessionContext.activate();
+ return ctx.proceed();
+ } finally {
+ sessionContext.terminate();
+ }
+ }
+
+}
diff --git a/extensions/azure-functions/deployment/pom.xml b/extensions/azure-functions/deployment/pom.xml
index 10437cac5561b..f01b722b1d4d1 100644
--- a/extensions/azure-functions/deployment/pom.xml
+++ b/extensions/azure-functions/deployment/pom.xml
@@ -132,9 +132,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsConfig.java b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsConfig.java
index b2994e55bd132..75e3954f397cf 100644
--- a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsConfig.java
+++ b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsConfig.java
@@ -9,7 +9,6 @@
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
-import org.jboss.logging.Logger;
import com.azure.core.management.AzureEnvironment;
import com.microsoft.azure.toolkit.lib.Azure;
@@ -31,27 +30,29 @@
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
/**
* Azure Functions configuration.
* Most options supported and name similarly to azure-functions-maven-plugin config
*/
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
-public class AzureFunctionsConfig {
+@ConfigMapping(prefix = "quarkus.azure-functions")
+public interface AzureFunctionsConfig {
/**
* App name for azure function project. This is required setting.
*
* Defaults to the base artifact name
*/
- @ConfigItem
- public Optional appName;
+ Optional appName();
/**
* Azure Resource Group for your Azure Functions
*/
- @ConfigItem(defaultValue = "quarkus")
- public String resourceGroup;
+ @WithDefault("quarkus")
+ String resourceGroup();
/**
* Specifies the region where your Azure Functions will be hosted; default value is westus.
@@ -59,114 +60,105 @@ public class AzureFunctionsConfig {
* "https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-regions">Valid
* values
*/
- @ConfigItem(defaultValue = "westus")
- public String region;
+ @WithDefault("westus")
+ String region();
/**
* Specifies whether to disable application insights for your function app
*/
- @ConfigItem(defaultValue = "false")
- public boolean disableAppInsights;
+ @WithDefault("false")
+ boolean disableAppInsights();
/**
* Specifies the instrumentation key of application insights which will bind to your function app
*/
- @ConfigItem
- public Optional appInsightsKey;
+ Optional appInsightsKey();
- public RuntimeConfig runtime;
+ RuntimeConfig runtime();
- public AuthConfig auth;
+ AuthConfig auth();
/**
* Specifies the name of the existing App Service Plan when you do not want to create a new one.
*/
- @ConfigItem(defaultValue = "java-functions-app-service-plan")
- public String appServicePlanName;
+ @WithDefault("java-functions-app-service-plan")
+ String appServicePlanName();
/**
* The app service plan resource group.
*/
- @ConfigItem
- public Optional appServicePlanResourceGroup;
+ Optional appServicePlanResourceGroup();
/**
* Azure subscription id. Required only if there are more than one subscription in your account
*/
- @ConfigItem
- public Optional subscriptionId;
+ Optional subscriptionId();
/**
* The pricing tier.
*/
- @ConfigItem
- public Optional pricingTier;
+ Optional pricingTier();
/**
* Port to run azure function in local runtime.
* Will default to quarkus.http.test-port or 8081
*/
- @ConfigItem
- public Optional funcPort;
+ Optional funcPort();
/**
* Config String for local debug
*/
- @ConfigItem(defaultValue = "transport=dt_socket,server=y,suspend=n,address=5005")
- public String localDebugConfig;
+ @WithDefault("transport=dt_socket,server=y,suspend=n,address=5005")
+ String localDebugConfig();
/**
* Specifies the application settings for your Azure Functions, which are defined in name-value pairs
*/
@ConfigItem
@ConfigDocMapKey("setting-name")
- public Map appSettings = Collections.emptyMap();
+ Map appSettings = Collections.emptyMap();
@ConfigGroup
- public static class RuntimeConfig {
+ interface RuntimeConfig {
/**
* Valid values are linux, windows, and docker
*/
- @ConfigItem(defaultValue = "linux")
- public String os;
+ @WithDefault("linux")
+ String os();
/**
* Valid values are 8, 11, and 17
*/
- @ConfigItem(defaultValue = "11")
- public String javaVersion;
+ @WithDefault("11")
+ String javaVersion();
/**
* URL of docker image if deploying via docker
*/
- @ConfigItem
- public Optional image;
+ Optional image();
/**
* If using docker, url of registry
*/
- @ConfigItem
- public Optional registryUrl;
+ Optional registryUrl();
}
- public FunctionAppConfig toFunctionAppConfig(String subscriptionId, String appName) {
+ default FunctionAppConfig toFunctionAppConfig(String subscriptionId, String appName) {
Map appSettings = this.appSettings;
if (appSettings.isEmpty()) {
appSettings = new HashMap<>();
appSettings.put("FUNCTIONS_EXTENSION_VERSION", "~4");
}
return (FunctionAppConfig) new FunctionAppConfig()
- .disableAppInsights(disableAppInsights)
- .appInsightsKey(appInsightsKey.orElse(null))
- .appInsightsInstance(appInsightsKey.orElse(null))
+ .disableAppInsights(disableAppInsights())
+ .appInsightsKey(appInsightsKey().orElse(null))
+ .appInsightsInstance(appInsightsKey().orElse(null))
.subscriptionId(subscriptionId)
- .resourceGroup(resourceGroup)
+ .resourceGroup(resourceGroup())
.appName(appName)
- .servicePlanName(appServicePlanName)
- .servicePlanResourceGroup(appServicePlanResourceGroup.orElse(null))
- //.deploymentSlotName(getDeploymentSlotName())
- //.deploymentSlotConfigurationSource(getDeploymentSlotConfigurationSource())
+ .servicePlanName(appServicePlanName())
+ .servicePlanResourceGroup(appServicePlanResourceGroup().orElse(null))
.pricingTier(getParsedPricingTier(subscriptionId))
.region(getParsedRegion())
.runtime(getRuntimeConfig(subscriptionId))
@@ -174,42 +166,42 @@ public FunctionAppConfig toFunctionAppConfig(String subscriptionId, String appNa
}
private PricingTier getParsedPricingTier(String subscriptionId) {
- return Optional.ofNullable(this.pricingTier.orElse(null)).map(PricingTier::fromString)
+ return Optional.ofNullable(this.pricingTier().orElse(null)).map(PricingTier::fromString)
.orElseGet(() -> Optional.ofNullable(getServicePlan(subscriptionId)).map(AppServicePlan::getPricingTier)
.orElse(null));
}
private AppServicePlan getServicePlan(String subscriptionId) {
- final String servicePlan = this.appServicePlanName;
- final String servicePlanGroup = StringUtils.firstNonBlank(this.appServicePlanResourceGroup.orElse(null),
- this.resourceGroup);
+ final String servicePlan = this.appServicePlanName();
+ final String servicePlanGroup = StringUtils.firstNonBlank(this.appServicePlanResourceGroup().orElse(null),
+ this.resourceGroup());
return StringUtils.isAnyBlank(subscriptionId, servicePlan, servicePlanGroup) ? null
: Azure.az(AzureAppService.class).plans(subscriptionId).get(servicePlan, servicePlanGroup);
}
private com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig getRuntimeConfig(String subscriptionId) {
- final RuntimeConfig runtime = this.runtime;
+ final RuntimeConfig runtime = this.runtime();
if (runtime == null) {
return null;
}
- final OperatingSystem os = Optional.ofNullable(runtime.os).map(OperatingSystem::fromString)
+ final OperatingSystem os = Optional.ofNullable(runtime.os()).map(OperatingSystem::fromString)
.orElseGet(
() -> Optional.ofNullable(getServicePlan(subscriptionId)).map(AppServicePlan::getOperatingSystem)
.orElse(null));
- final JavaVersion javaVersion = Optional.ofNullable(runtime.javaVersion).map(JavaVersion::fromString).orElse(null);
+ final JavaVersion javaVersion = Optional.ofNullable(runtime.javaVersion()).map(JavaVersion::fromString).orElse(null);
final com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig result = new com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig()
.os(os)
.javaVersion(javaVersion).webContainer(WebContainer.JAVA_OFF)
- .image(runtime.image.orElse(null)).registryUrl(runtime.registryUrl.orElse(null));
+ .image(runtime.image().orElse(null)).registryUrl(runtime.registryUrl().orElse(null));
return result;
}
private Region getParsedRegion() {
- return Optional.ofNullable(region).map(Region::fromName).orElse(null);
+ return Optional.ofNullable(region()).map(Region::fromName).orElse(null);
}
@ConfigGroup
- public static class AuthConfig {
+ interface AuthConfig {
/**
* Description of each type can be found
@@ -234,28 +226,23 @@ public static class AuthConfig {
*
* Defaults to "azure_cli" for authentication
*/
- @ConfigItem(defaultValue = "azure_cli")
- public String type;
+ @WithDefault("azure_cli")
+ String type();
/**
* Filesystem path to properties file if using file type
*/
- @ConfigItem
- public Optional path;
+ Optional path();
/**
* Client or App Id required if using managed_identity type
*/
- @ConfigItem
- public Optional client;
+ Optional client();
/**
- * Tenant Id required if using oauth2 or device_code type
+ * Tenant ID required if using oauth2 or device_code type
*/
- @ConfigItem
- public Optional tenant;
-
- private static final Logger log = Logger.getLogger(AzureFunctionsConfig.class);
+ Optional tenant();
private static String findValue(Properties props, String key) {
if (props.contains(key))
@@ -301,15 +288,15 @@ private static AuthConfiguration fromFile(Optional path) {
}
}
- public AuthConfiguration toAuthConfiguration() {
+ default AuthConfiguration toAuthConfiguration() {
try {
- if (this.type.equalsIgnoreCase("file")) {
- return fromFile(this.path);
+ if (this.type().equalsIgnoreCase("file")) {
+ return fromFile(this.path());
}
- final AuthType type = AuthType.parseAuthType(this.type);
+ final AuthType type = AuthType.parseAuthType(this.type());
final AuthConfiguration authConfiguration = new AuthConfiguration(type);
- authConfiguration.setClient(client.orElse(null));
- authConfiguration.setTenant(tenant.orElse(null));
+ authConfiguration.setClient(client().orElse(null));
+ authConfiguration.setTenant(tenant().orElse(null));
authConfiguration.setEnvironment(AzureEnvironmentUtils.azureEnvironmentToString(AzureEnvironment.AZURE));
return authConfiguration;
} catch (InvalidConfigurationException e) {
diff --git a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsDeployCommand.java b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsDeployCommand.java
index aff10a43c92f2..544240480aead 100644
--- a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsDeployCommand.java
+++ b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsDeployCommand.java
@@ -167,22 +167,22 @@ protected void validateParameters(AzureFunctionsConfig config, String appName) t
throw new BuildException(INVALID_APP_NAME);
}
// resource group
- if (StringUtils.isBlank(config.resourceGroup)) {
+ if (StringUtils.isBlank(config.resourceGroup())) {
throw new BuildException(EMPTY_RESOURCE_GROUP);
}
- if (config.resourceGroup.endsWith(".") || !config.resourceGroup.matches(RESOURCE_GROUP_PATTERN)) {
+ if (config.resourceGroup().endsWith(".") || !config.resourceGroup().matches(RESOURCE_GROUP_PATTERN)) {
throw new BuildException(INVALID_RESOURCE_GROUP_NAME);
}
// asp name & resource group
- if (StringUtils.isNotEmpty(config.appServicePlanName)
- && !config.appServicePlanName.matches(APP_SERVICE_PLAN_NAME_PATTERN)) {
+ if (StringUtils.isNotEmpty(config.appServicePlanName())
+ && !config.appServicePlanName().matches(APP_SERVICE_PLAN_NAME_PATTERN)) {
throw new BuildException(String.format(INVALID_SERVICE_PLAN_NAME, APP_SERVICE_PLAN_NAME_PATTERN));
}
- if (config.appServicePlanResourceGroup.isPresent()
- && StringUtils.isNotEmpty(config.appServicePlanResourceGroup.orElse(null))
+ if (config.appServicePlanResourceGroup().isPresent()
+ && StringUtils.isNotEmpty(config.appServicePlanResourceGroup().orElse(null))
&&
- (config.appServicePlanResourceGroup.orElse(null).endsWith(".")
- || !config.appServicePlanResourceGroup.orElse(null).matches(RESOURCE_GROUP_PATTERN))) {
+ (config.appServicePlanResourceGroup().orElse(null).endsWith(".")
+ || !config.appServicePlanResourceGroup().orElse(null).matches(RESOURCE_GROUP_PATTERN))) {
throw new BuildException(INVALID_SERVICE_PLAN_RESOURCE_GROUP_NAME);
}
// slot name
@@ -196,26 +196,26 @@ protected void validateParameters(AzureFunctionsConfig config, String appName) t
*
*/
// region
- if (StringUtils.isNotEmpty(config.region) && Region.fromName(config.region).isExpandedValue()) {
- log.warn(String.format(EXPANDABLE_REGION_WARNING, config.region));
+ if (StringUtils.isNotEmpty(config.region()) && Region.fromName(config.region()).isExpandedValue()) {
+ log.warn(String.format(EXPANDABLE_REGION_WARNING, config.region()));
}
// os
- if (StringUtils.isNotEmpty(config.runtime.os) && OperatingSystem.fromString(config.runtime.os) == null) {
+ if (StringUtils.isNotEmpty(config.runtime().os()) && OperatingSystem.fromString(config.runtime().os()) == null) {
throw new BuildException(INVALID_OS);
}
// java version
- if (StringUtils.isNotEmpty(config.runtime.javaVersion)
- && JavaVersion.fromString(config.runtime.javaVersion).isExpandedValue()) {
- log.warn(String.format(EXPANDABLE_JAVA_VERSION_WARNING, config.runtime.javaVersion));
+ if (StringUtils.isNotEmpty(config.runtime().javaVersion())
+ && JavaVersion.fromString(config.runtime().javaVersion()).isExpandedValue()) {
+ log.warn(String.format(EXPANDABLE_JAVA_VERSION_WARNING, config.runtime().javaVersion()));
}
// pricing tier
- if (config.pricingTier.isPresent() && StringUtils.isNotEmpty(config.pricingTier.orElse(null))
- && PricingTier.fromString(config.pricingTier.orElse(null)).isExpandedValue()) {
- log.warn(String.format(EXPANDABLE_PRICING_TIER_WARNING, config.pricingTier.orElse(null)));
+ if (config.pricingTier().isPresent() && StringUtils.isNotEmpty(config.pricingTier().orElse(null))
+ && PricingTier.fromString(config.pricingTier().orElse(null)).isExpandedValue()) {
+ log.warn(String.format(EXPANDABLE_PRICING_TIER_WARNING, config.pricingTier().orElse(null)));
}
// docker image
- if (OperatingSystem.fromString(config.runtime.os) == OperatingSystem.DOCKER
- && StringUtils.isEmpty(config.runtime.image.orElse(null))) {
+ if (OperatingSystem.fromString(config.runtime().os()) == OperatingSystem.DOCKER
+ && StringUtils.isEmpty(config.runtime().image().orElse(null))) {
throw new BuildException(EMPTY_IMAGE_NAME);
}
}
@@ -226,9 +226,9 @@ protected void validateParameters(AzureFunctionsConfig config, String appName) t
protected AzureAppService initAzureAppServiceClient(AzureFunctionsConfig config) throws BuildException {
if (appServiceClient == null) {
- final Account account = loginAzure(config.auth);
+ final Account account = loginAzure(config.auth());
final List subscriptions = account.getSubscriptions();
- final String targetSubscriptionId = getTargetSubscriptionId(config.subscriptionId.orElse(null), subscriptions,
+ final String targetSubscriptionId = getTargetSubscriptionId(config.subscriptionId().orElse(null), subscriptions,
account.getSelectedSubscriptions());
checkSubscription(subscriptions, targetSubscriptionId);
com.microsoft.azure.toolkit.lib.Azure.az(AzureAccount.class).account()
diff --git a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsProcessor.java b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsProcessor.java
index 04ef96151d917..438d5a70fe8f6 100644
--- a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsProcessor.java
+++ b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsProcessor.java
@@ -71,7 +71,7 @@ FeatureBuildItem feature() {
@BuildStep
AzureFunctionsAppNameBuildItem appName(OutputTargetBuildItem output, AzureFunctionsConfig functionsConfig) {
- String appName = functionsConfig.appName.orElse(output.getBaseName());
+ String appName = functionsConfig.appName().orElse(output.getBaseName());
return new AzureFunctionsAppNameBuildItem(appName);
}
diff --git a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java
index 7797fd7b49901..890e08a6f7ec5 100644
--- a/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java
+++ b/extensions/azure-functions/deployment/src/main/java/io/quarkus/azure/functions/deployment/AzureFunctionsRunCommand.java
@@ -77,15 +77,15 @@ protected String getCheckRuntimeCommand() {
protected String getStartFunctionHostCommand(AzureFunctionsConfig azureConfig) {
int funcPort;
- if (azureConfig.funcPort.isPresent()) {
- funcPort = azureConfig.funcPort.get();
+ if (azureConfig.funcPort().isPresent()) {
+ funcPort = azureConfig.funcPort().get();
} else {
Config config = ConfigProviderResolver.instance().getConfig();
funcPort = config.getValue("quarkus.http.test-port", OptionalInt.class).orElse(8081);
}
final String enableDebug = System.getProperty("enableDebug");
if (StringUtils.isNotEmpty(enableDebug) && enableDebug.equalsIgnoreCase("true")) {
- return String.format(FUNC_HOST_START_WITH_DEBUG_CMD, funcPort, azureConfig.localDebugConfig);
+ return String.format(FUNC_HOST_START_WITH_DEBUG_CMD, funcPort, azureConfig.localDebugConfig());
} else {
return String.format(FUNC_HOST_START_CMD, funcPort);
}
diff --git a/extensions/azure-functions/runtime/pom.xml b/extensions/azure-functions/runtime/pom.xml
index 5440764ba4778..0901929c5192b 100644
--- a/extensions/azure-functions/runtime/pom.xml
+++ b/extensions/azure-functions/runtime/pom.xml
@@ -53,9 +53,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java
index da9de6071daba..48b2e42fbd829 100644
--- a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java
+++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/DevServicesProcessor.java
@@ -213,10 +213,10 @@ private static String[] getNetworks(Container container) {
return networks.entrySet().stream()
.map(e -> {
List aliases = e.getValue().getAliases();
- if (aliases == null) {
+ if (aliases == null || aliases.isEmpty()) {
return e.getKey();
}
- return e.getKey() + " (" + String.join(",", aliases) + ")";
+ return e.getKey() + " (" + String.join(", ", aliases) + ")";
})
.toArray(String[]::new);
}
diff --git a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java
index 49cd131711bb1..5093a9f369185 100644
--- a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java
+++ b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java
@@ -4,10 +4,11 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.jboss.logging.Logger;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -24,6 +25,7 @@ public final class KeycloakDevServicesRequiredBuildItem extends MultiBuildItem {
private static final Logger LOG = Logger.getLogger(KeycloakDevServicesProcessor.class);
public static final String OIDC_AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc.auth-server-url";
+ private static final String OIDC_PROVIDER_CONFIG_KEY = "quarkus.oidc.provider";
private final KeycloakDevServicesConfigurator devServicesConfigurator;
private final String authServerUrl;
@@ -39,8 +41,17 @@ String getAuthServerUrl() {
}
public static KeycloakDevServicesRequiredBuildItem of(KeycloakDevServicesConfigurator devServicesConfigurator,
- String authServerUrl, String... dontStartConfigProperties) {
- if (shouldStartDevService(dontStartConfigProperties, authServerUrl)) {
+ String authServerUrl, String... additionalDontStartConfigProperties) {
+ final Set dontStartConfigProperties = new HashSet<>(Arrays.asList(additionalDontStartConfigProperties));
+ dontStartConfigProperties.add(authServerUrl);
+ dontStartConfigProperties.add(OIDC_AUTH_SERVER_URL_CONFIG_KEY);
+ dontStartConfigProperties.add(OIDC_PROVIDER_CONFIG_KEY);
+ return of(devServicesConfigurator, authServerUrl, dontStartConfigProperties);
+ }
+
+ private static KeycloakDevServicesRequiredBuildItem of(KeycloakDevServicesConfigurator devServicesConfigurator,
+ String authServerUrl, Set dontStartConfigProperties) {
+ if (shouldStartDevService(dontStartConfigProperties)) {
return new KeycloakDevServicesRequiredBuildItem(devServicesConfigurator, authServerUrl);
}
return null;
@@ -69,10 +80,8 @@ public void customizeDefaultRealm(RealmRepresentation realmRepresentation) {
};
}
- private static boolean shouldStartDevService(String[] dontStartConfigProperties, String authServerUrl) {
- return Stream
- .concat(Stream.of(authServerUrl), Arrays.stream(dontStartConfigProperties))
- .allMatch(KeycloakDevServicesRequiredBuildItem::shouldStartDevService);
+ private static boolean shouldStartDevService(Set dontStartConfigProperties) {
+ return dontStartConfigProperties.stream().allMatch(KeycloakDevServicesRequiredBuildItem::shouldStartDevService);
}
private static boolean shouldStartDevService(String dontStartConfigProperty) {
diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java
index bf5a400c5610d..76dc5882235b2 100644
--- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java
+++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/deployment/FlywayProcessor.java
@@ -61,7 +61,6 @@
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
-import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.flyway.FlywayDataSource;
import io.quarkus.flyway.runtime.FlywayBuildTimeConfig;
@@ -184,7 +183,6 @@ private void addJavaMigrations(Collection candidates, RecorderContext
@BuildStep
@Produce(SyntheticBeansRuntimeInitBuildItem.class)
- @Consume(LoggingSetupBuildItem.class)
@Record(ExecutionTime.RUNTIME_INIT)
void createBeans(FlywayRecorder recorder,
List jdbcDataSourceBuildItems,
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java
index bcdbb76e0361d..9401b42d184fe 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java
@@ -117,7 +117,6 @@ public Flyway createFlyway(DataSource dataSource) {
}
configure.ignoreMigrationPatterns(ignoreMigrationPatterns);
- configure.cleanOnValidationError(flywayRuntimeConfig.cleanOnValidationError);
configure.outOfOrder(flywayRuntimeConfig.outOfOrder);
if (flywayRuntimeConfig.baselineVersion.isPresent()) {
configure.baselineVersion(flywayRuntimeConfig.baselineVersion.get());
diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java
index 772ba52458280..ed0c82aaa2a76 100644
--- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java
+++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java
@@ -131,12 +131,6 @@ public static FlywayDataSourceRuntimeConfig defaultConfig() {
@ConfigItem
public boolean cleanDisabled;
- /**
- * true to automatically call clean when a validation error occurs, false otherwise.
- */
- @ConfigItem
- public boolean cleanOnValidationError;
-
/**
* true to execute Flyway automatically when the application starts, false otherwise.
*
diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java
index 17764887791ed..4995671779b3f 100644
--- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java
+++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java
@@ -222,22 +222,6 @@ void testIgnoreFutureMigrations() {
assertTrue(ValidatePatternUtils.isFutureIgnored(createdFlywayConfig().getIgnoreMigrationPatterns()));
}
- @Test
- @DisplayName("cleanOnValidationError defaults to false and is correctly set")
- void testCleanOnValidationError() {
- creator = new FlywayCreator(runtimeConfig, buildConfig);
- assertEquals(runtimeConfig.cleanOnValidationError, createdFlywayConfig().isCleanOnValidationError());
- assertFalse(runtimeConfig.cleanOnValidationError);
-
- runtimeConfig.cleanOnValidationError = false;
- creator = new FlywayCreator(runtimeConfig, buildConfig);
- assertFalse(createdFlywayConfig().isCleanOnValidationError());
-
- runtimeConfig.cleanOnValidationError = true;
- creator = new FlywayCreator(runtimeConfig, buildConfig);
- assertTrue(createdFlywayConfig().isCleanOnValidationError());
- }
-
@ParameterizedTest
@MethodSource("validateOnMigrateOverwritten")
@DisplayName("validate on migrate overwritten in configuration")
diff --git a/extensions/funqy/funqy-amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/funqy/funqy-amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
index a8f4423f490e9..3f395b13a370c 100644
--- a/extensions/funqy/funqy-amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/extensions/funqy/funqy-amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
@@ -17,7 +17,7 @@
quarkus-bomio.quarkus999-SNAPSHOT
- 3.5.0
+ 3.5.2
diff --git a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcCodeGen.java
similarity index 99%
rename from extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java
rename to extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcCodeGen.java
index d4e67796e2420..f17358b1bf27c 100644
--- a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcCodeGen.java
+++ b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcCodeGen.java
@@ -1,4 +1,4 @@
-package io.quarkus.grpc.deployment;
+package io.quarkus.grpc.codegen;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
diff --git a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcPostProcessing.java
similarity index 99%
rename from extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java
rename to extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcPostProcessing.java
index abe19f5de0934..72406fd7c1565 100644
--- a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java
+++ b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/codegen/GrpcPostProcessing.java
@@ -1,4 +1,4 @@
-package io.quarkus.grpc.deployment;
+package io.quarkus.grpc.codegen;
import java.io.File;
import java.nio.file.Path;
diff --git a/extensions/grpc/codegen/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider b/extensions/grpc/codegen/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
index 79d063f5e2df5..a2c6cd0c7d022 100644
--- a/extensions/grpc/codegen/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
+++ b/extensions/grpc/codegen/src/main/resources/META-INF/services/io.quarkus.deployment.CodeGenProvider
@@ -1 +1 @@
-io.quarkus.grpc.deployment.GrpcCodeGen
\ No newline at end of file
+io.quarkus.grpc.codegen.GrpcCodeGen
\ No newline at end of file
diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/interceptors/ClientInterceptorConstructorRegistrationTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/interceptors/ClientInterceptorConstructorRegistrationTest.java
new file mode 100644
index 0000000000000..d5148fa9914a9
--- /dev/null
+++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/interceptors/ClientInterceptorConstructorRegistrationTest.java
@@ -0,0 +1,61 @@
+package io.quarkus.grpc.client.interceptors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.grpc.examples.helloworld.Greeter;
+import io.grpc.examples.helloworld.GreeterBean;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloReplyOrBuilder;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.examples.helloworld.HelloRequestOrBuilder;
+import io.grpc.examples.helloworld.MutinyGreeterGrpc;
+import io.quarkus.grpc.GrpcClient;
+import io.quarkus.grpc.RegisterClientInterceptor;
+import io.quarkus.grpc.server.services.MutinyHelloService;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class ClientInterceptorConstructorRegistrationTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
+ () -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(MutinyHelloService.class, MyThirdClientInterceptor.class, MyLastClientInterceptor.class,
+ Calls.class,
+ GreeterGrpc.class, Greeter.class, GreeterBean.class, HelloRequest.class, HelloReply.class,
+ MutinyGreeterGrpc.class,
+ HelloRequestOrBuilder.class, HelloReplyOrBuilder.class))
+ .withConfigurationResource("hello-config.properties");
+ private static final Logger log = LoggerFactory.getLogger(ClientInterceptorConstructorRegistrationTest.class);
+
+ private GreeterGrpc.GreeterBlockingStub client;
+
+ public ClientInterceptorConstructorRegistrationTest(
+ @RegisterClientInterceptor(MyLastClientInterceptor.class) @RegisterClientInterceptor(MyThirdClientInterceptor.class) @GrpcClient("hello-service") GreeterGrpc.GreeterBlockingStub client) {
+ this.client = client;
+ }
+
+ @Test
+ public void testInterceptorRegistration() {
+ Calls.LIST.clear();
+
+ HelloReply reply = client
+ .sayHello(HelloRequest.newBuilder().setName("neo").build());
+ assertThat(reply.getMessage()).isEqualTo("Hello neo");
+
+ List calls = Calls.LIST;
+ assertEquals(2, calls.size());
+ assertEquals(MyThirdClientInterceptor.class.getName(), calls.get(0));
+ assertEquals(MyLastClientInterceptor.class.getName(), calls.get(1));
+ }
+}
diff --git a/extensions/grpc/stubs/pom.xml b/extensions/grpc/stubs/pom.xml
index d3a387d227220..83fd66307ca75 100644
--- a/extensions/grpc/stubs/pom.xml
+++ b/extensions/grpc/stubs/pom.xml
@@ -127,7 +127,7 @@
${project.build.directory}/generated-sources/protobuf/grpc-java
- io.quarkus.grpc.deployment.GrpcPostProcessing
+ io.quarkus.grpc.codegen.GrpcPostProcessing
diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/GitUtil.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/GitUtil.java
new file mode 100644
index 0000000000000..e6aa16603a5a3
--- /dev/null
+++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/GitUtil.java
@@ -0,0 +1,30 @@
+package io.quarkus.info.deployment;
+
+class GitUtil {
+
+ static String sanitizeRemoteUrl(String remoteUrl) {
+ if (remoteUrl == null || remoteUrl.isBlank()) {
+ return null;
+ }
+
+ String sanitizedRemoteUrl = remoteUrl.trim();
+ if (sanitizedRemoteUrl.startsWith("https://")) {
+ int atSign = sanitizedRemoteUrl.indexOf('@');
+ if (atSign > 0) {
+ sanitizedRemoteUrl = "https://" + sanitizedRemoteUrl.substring(atSign + 1);
+ }
+ } else if (sanitizedRemoteUrl.startsWith("http://")) {
+ int atSign = sanitizedRemoteUrl.indexOf('@');
+ if (atSign > 0) {
+ sanitizedRemoteUrl = "http://" + sanitizedRemoteUrl.substring(atSign + 1);
+ }
+ } else {
+ int atSign = sanitizedRemoteUrl.indexOf('@');
+ if (atSign > 0) {
+ sanitizedRemoteUrl = sanitizedRemoteUrl.substring(atSign + 1);
+ }
+ }
+
+ return sanitizedRemoteUrl;
+ }
+}
diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
index c31f9dae6c19d..135751323adef 100644
--- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
+++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java
@@ -4,13 +4,13 @@
import java.io.File;
import java.net.InetAddress;
+import java.time.Instant;
import java.time.OffsetDateTime;
+import java.time.ZoneId;
import java.util.Collection;
-import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.TimeZone;
import java.util.stream.Collectors;
import jakarta.enterprise.context.ApplicationScoped;
@@ -34,6 +34,7 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.info.BuildInfo;
@@ -70,11 +71,10 @@ void gitInfo(InfoBuildTimeConfig config,
log.debug("Project is not checked in to git");
return;
}
- try (Repository repository = repositoryBuilder.build()) {
+ try (Repository repository = repositoryBuilder.build();
+ Git git = Git.wrap(repository)) {
- RevCommit latestCommit = new Git(repository).log().setMaxCount(1).call().iterator().next();
- Date commitDate = new Date(latestCommit.getCommitTime() * 1000L);
- TimeZone commitTimeZone = TimeZone.getDefault();
+ RevCommit latestCommit = git.log().setMaxCount(1).call().iterator().next();
boolean addFullInfo = config.git().mode() == InfoBuildTimeConfig.Git.Mode.FULL;
@@ -85,16 +85,17 @@ void gitInfo(InfoBuildTimeConfig config,
Map commit = new LinkedHashMap<>();
String latestCommitId = latestCommit.getName();
commit.put("id", latestCommitId);
- String latestCommitTime = formatDate(commitDate, commitTimeZone);
+ String latestCommitTime = formatDate(Instant.ofEpochSecond(latestCommit.getCommitTime()), ZoneId.systemDefault());
commit.put("time", latestCommitTime);
if (addFullInfo) {
PersonIdent authorIdent = latestCommit.getAuthorIdent();
- commit.put("author", Map.of("time", formatDate(authorIdent.getWhen(), authorIdent.getTimeZone())));
+ commit.put("author", Map.of("time", formatDate(authorIdent.getWhenAsInstant(), authorIdent.getZoneId())));
PersonIdent committerIdent = latestCommit.getCommitterIdent();
- commit.put("committer", Map.of("time", formatDate(committerIdent.getWhen(), committerIdent.getTimeZone())));
+ commit.put("committer",
+ Map.of("time", formatDate(committerIdent.getWhenAsInstant(), committerIdent.getZoneId())));
Map user = new LinkedHashMap<>();
user.put("email", authorIdent.getEmailAddress());
@@ -111,7 +112,9 @@ void gitInfo(InfoBuildTimeConfig config,
commit.put("id", id);
- data.put("tags", getTags(repository, latestCommit));
+ data.put("remote",
+ GitUtil.sanitizeRemoteUrl(git.getRepository().getConfig().getString("remote", "origin", "url")));
+ data.put("tags", getTags(git, latestCommit));
}
data.put("commit", commit);
@@ -130,9 +133,8 @@ void gitInfo(InfoBuildTimeConfig config,
}
}
- private String formatDate(Date date, TimeZone timeZone) {
- return ISO_OFFSET_DATE_TIME.format(
- OffsetDateTime.ofInstant(date.toInstant(), timeZone.toZoneId()));
+ private String formatDate(Instant instant, ZoneId zoneId) {
+ return ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(instant, zoneId));
}
private Map obtainBuildInfo(CurateOutcomeBuildItem curateOutcomeBuildItem,
@@ -160,13 +162,11 @@ private Map obtainBuildInfo(CurateOutcomeBuildItem curateOutcome
return build;
}
- public Collection getTags(Repository repo, final ObjectId objectId) throws GitAPIException {
- try (Git git = Git.wrap(repo)) {
- try (RevWalk walk = new RevWalk(repo)) {
- Collection tags = getTags(git, objectId, walk);
- walk.dispose();
- return tags;
- }
+ public Collection getTags(final Git git, final ObjectId objectId) throws GitAPIException {
+ try (RevWalk walk = new RevWalk(git.getRepository())) {
+ Collection tags = getTags(git, objectId, walk);
+ walk.dispose();
+ return tags;
}
}
@@ -211,10 +211,13 @@ void buildInfo(CurateOutcomeBuildItem curateOutcomeBuildItem,
InfoBuildTimeConfig config,
BuildProducer valuesProducer,
BuildProducer beanProducer,
+ ApplicationInfoBuildItem infoApplication,
InfoRecorder recorder) {
ApplicationModel applicationModel = curateOutcomeBuildItem.getApplicationModel();
ResolvedDependency appArtifact = applicationModel.getAppArtifact();
Map buildData = new LinkedHashMap<>();
+ String name = infoApplication.getName();
+ buildData.put("name", name);
String group = appArtifact.getGroupId();
buildData.put("group", group);
String artifact = appArtifact.getArtifactId();
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
index a89b011d4f67b..2590edb796c58 100644
--- a/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js
+++ b/extensions/info/deployment/src/main/resources/dev-ui/qwc-info.js
@@ -104,6 +104,8 @@ export class QwcInfo extends LitElement {
diff --git a/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java
index fbac5fd326911..af7ca758626b2 100644
--- a/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java
+++ b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/EnabledInfoTest.java
@@ -70,5 +70,7 @@ public void test() {
assertNotNull(javaInfo);
assertNotNull(javaInfo.version());
+ assertNotNull(javaInfo.vendor());
+ assertNotNull(javaInfo.vendorVersion());
}
}
diff --git a/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/GitUtilTest.java b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/GitUtilTest.java
new file mode 100644
index 0000000000000..bdab391a44ce8
--- /dev/null
+++ b/extensions/info/deployment/src/test/java/io/quarkus/info/deployment/GitUtilTest.java
@@ -0,0 +1,26 @@
+package io.quarkus.info.deployment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class GitUtilTest {
+
+ @Test
+ public void testSanitizeRemoteUrl() {
+ assertNull(GitUtil.sanitizeRemoteUrl(null));
+ assertNull(GitUtil.sanitizeRemoteUrl(""));
+ assertNull(GitUtil.sanitizeRemoteUrl(" "));
+ assertEquals("github.com:gsmet/quarkusio.github.io.git",
+ GitUtil.sanitizeRemoteUrl("git@github.com:gsmet/quarkusio.github.io.git"));
+ assertEquals("github.com:gsmet/quarkusio.github.io.git",
+ GitUtil.sanitizeRemoteUrl(" git@github.com:gsmet/quarkusio.github.io.git "));
+ assertEquals("https://github.com/gsmet/quarkusio.github.io.git",
+ GitUtil.sanitizeRemoteUrl("https://github.com/gsmet/quarkusio.github.io.git"));
+ assertEquals("https://github.com/gsmet/quarkusio.github.io.git",
+ GitUtil.sanitizeRemoteUrl("https://gsmet:password@github.com/gsmet/quarkusio.github.io.git"));
+ assertEquals("http://github.com/gsmet/quarkusio.github.io.git",
+ GitUtil.sanitizeRemoteUrl("http://gsmet:password@github.com/gsmet/quarkusio.github.io.git"));
+ }
+}
diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java b/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java
index e7e4ed3c7fa90..35a76cf26c9ad 100644
--- a/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java
+++ b/extensions/info/runtime/src/main/java/io/quarkus/info/JavaInfo.java
@@ -1,6 +1,31 @@
package io.quarkus.info;
+/**
+ * This interface provides information about the Java runtime.
+ *
+ * @see io.quarkus.info.runtime.InfoRecorder
+ * @see io.quarkus.info.runtime.JavaInfoContributor
+ */
public interface JavaInfo {
+ /**
+ * Return the Java runtime version.
+ *
+ * @return string that represent the Java version
+ */
String version();
+
+ /**
+ * Return the Java vendor.
+ *
+ * @return string that represent the Java vendor
+ */
+ String vendor();
+
+ /**
+ * Return the Java vendor runtime version.
+ *
+ * @return string that represent the Java vendor version
+ */
+ String vendorVersion();
}
diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java
index c3380f685abca..0411426bf63ee 100644
--- a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java
+++ b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/InfoRecorder.java
@@ -134,6 +134,16 @@ public JavaInfo get() {
public String version() {
return JavaInfoContributor.getVersion();
}
+
+ @Override
+ public String vendor() {
+ return JavaInfoContributor.getVendor();
+ }
+
+ @Override
+ public String vendorVersion() {
+ return JavaInfoContributor.getVendorVersion();
+ }
};
}
};
diff --git a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java
index 11a88a612afbd..e381b3ba41b6e 100644
--- a/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java
+++ b/extensions/info/runtime/src/main/java/io/quarkus/info/runtime/JavaInfoContributor.java
@@ -17,10 +17,20 @@ public Map data() {
//TODO: should we add more information like 'java.runtime.*' and 'java.vm.*' ?
Map result = new LinkedHashMap<>();
result.put("version", getVersion());
+ result.put("vendor", getVendor());
+ result.put("vendorVersion", getVendorVersion());
return result;
}
static String getVersion() {
return System.getProperty("java.version");
}
+
+ static String getVendor() {
+ return System.getProperty("java.vendor");
+ }
+
+ static String getVendorVersion() {
+ return System.getProperty("java.vendor.version");
+ }
}
diff --git a/extensions/jaxb/deployment/pom.xml b/extensions/jaxb/deployment/pom.xml
index 15589c3c49e5b..53d532adc436b 100644
--- a/extensions/jaxb/deployment/pom.xml
+++ b/extensions/jaxb/deployment/pom.xml
@@ -56,9 +56,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java
index b08e753e7b3fa..aaf0c32bc958f 100644
--- a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java
+++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java
@@ -342,8 +342,8 @@ FilteredJaxbClassesToBeBoundBuildItem filterBoundClasses(
.forEach(builder::classNames);
// remove classes that have been excluded by users
- if (config.excludeClasses.isPresent()) {
- builder.classNameExcludes(config.excludeClasses.get());
+ if (config.excludeClasses().isPresent()) {
+ builder.classNameExcludes(config.excludeClasses().get());
}
return builder.build();
}
@@ -362,7 +362,7 @@ void bindClassesToJaxbContext(
.resolveBeans(Type.create(DotName.createSimple(JAXBContext.class), org.jboss.jandex.Type.Kind.CLASS));
if (!beans.isEmpty()) {
jaxbContextConfig.addClassesToBeBound(filteredClassesToBeBound.getClasses());
- if (config.validateJaxbContext) {
+ if (config.validateJaxbContext()) {
validateJaxbContext(filteredClassesToBeBound, beanResolver, beans);
}
}
diff --git a/extensions/jaxb/runtime/pom.xml b/extensions/jaxb/runtime/pom.xml
index 3c6144fcbb637..17ad6ef0aede2 100644
--- a/extensions/jaxb/runtime/pom.xml
+++ b/extensions/jaxb/runtime/pom.xml
@@ -71,9 +71,6 @@
${project.version}
-
- -AlegacyConfigRoot=true
-
diff --git a/extensions/jaxb/runtime/src/main/java/io/quarkus/jaxb/runtime/JaxbConfig.java b/extensions/jaxb/runtime/src/main/java/io/quarkus/jaxb/runtime/JaxbConfig.java
index 10aee70162135..dc456f329460b 100644
--- a/extensions/jaxb/runtime/src/main/java/io/quarkus/jaxb/runtime/JaxbConfig.java
+++ b/extensions/jaxb/runtime/src/main/java/io/quarkus/jaxb/runtime/JaxbConfig.java
@@ -3,24 +3,25 @@
import java.util.List;
import java.util.Optional;
-import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
-@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED, name = "jaxb")
-public class JaxbConfig {
+@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+@ConfigMapping(prefix = "quarkus.jaxb")
+public interface JaxbConfig {
/**
* If enabled, it will validate the default JAXB context at build time.
*/
- @ConfigItem(defaultValue = "false")
- public boolean validateJaxbContext;
+ @WithDefault("false")
+ boolean validateJaxbContext();
/**
* Exclude classes to automatically be bound to the default JAXB context.
* Values with suffix {@code .*}, i.e. {@code org.acme.*}, are considered packages and exclude all classes that are members
* of these packages
*/
- @ConfigItem
- public Optional> excludeClasses;
+ Optional> excludeClasses();
}
diff --git a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacketFactory_Substitutions.java b/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacketFactory_Substitutions.java
deleted file mode 100644
index d775c42f26c41..0000000000000
--- a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacketFactory_Substitutions.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.quarkus.jdbc.mariadb.runtime.graal;
-
-import org.mariadb.jdbc.Configuration;
-import org.mariadb.jdbc.HostAddress;
-import org.mariadb.jdbc.plugin.AuthenticationPlugin;
-import org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacketFactory;
-
-import com.oracle.svm.core.annotate.Substitute;
-import com.oracle.svm.core.annotate.TargetClass;
-
-/**
- * The SendPamAuthPacketFactory class is not supported in native mode.
- */
-@TargetClass(SendPamAuthPacketFactory.class)
-public final class SendPamAuthPacketFactory_Substitutions {
-
- @Substitute
- public AuthenticationPlugin initialize(String authenticationData, byte[] seed, Configuration conf,
- HostAddress hostAddress) {
- throw new UnsupportedOperationException("Authentication strategy 'dialog' is not supported in GraalVM");
- }
-
-}
diff --git a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Substitutions.java b/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Substitutions.java
index 8365c7b36f3bf..b03ac4cb81651 100644
--- a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Substitutions.java
+++ b/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Substitutions.java
@@ -1,15 +1,29 @@
package io.quarkus.jdbc.mariadb.runtime.graal;
-import org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacket;
+import java.io.IOException;
+import java.sql.SQLException;
-import com.oracle.svm.core.annotate.Delete;
+import org.mariadb.jdbc.Configuration;
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.client.Context;
+import org.mariadb.jdbc.client.ReadableByteBuf;
+import org.mariadb.jdbc.client.socket.Reader;
+import org.mariadb.jdbc.client.socket.Writer;
+
+import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
-/**
- * The SendPamAuthPacket class is not supported in native mode.
- */
-@Delete
-@TargetClass(SendPamAuthPacket.class)
+@TargetClass(className = "org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacket")
public final class SendPamAuthPacket_Substitutions {
+ @Substitute
+ public void initialize(String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) {
+ throw new UnsupportedOperationException("Authentication strategy 'dialog' is not supported in GraalVM");
+ }
+
+ @Substitute
+ public ReadableByteBuf process(Writer out, Reader in, Context context)
+ throws SQLException, IOException {
+ throw new UnsupportedOperationException("Authentication strategy 'dialog' is not supported in GraalVM");
+ }
}
diff --git a/extensions/jfr/deployment/src/main/java/io/quarkus/jfr/deployment/JfrProcessor.java b/extensions/jfr/deployment/src/main/java/io/quarkus/jfr/deployment/JfrProcessor.java
index d5494245b1874..caad3d89959e1 100644
--- a/extensions/jfr/deployment/src/main/java/io/quarkus/jfr/deployment/JfrProcessor.java
+++ b/extensions/jfr/deployment/src/main/java/io/quarkus/jfr/deployment/JfrProcessor.java
@@ -12,11 +12,13 @@
import io.quarkus.jfr.runtime.OTelIdProducer;
import io.quarkus.jfr.runtime.QuarkusIdProducer;
import io.quarkus.jfr.runtime.config.JfrRuntimeConfig;
-import io.quarkus.jfr.runtime.http.rest.ClassicServerRecorderProducer;
-import io.quarkus.jfr.runtime.http.rest.JfrClassicServerFilter;
-import io.quarkus.jfr.runtime.http.rest.JfrReactiveServerFilter;
-import io.quarkus.jfr.runtime.http.rest.ReactiveServerRecorderProducer;
+import io.quarkus.jfr.runtime.http.rest.classic.ClassicServerFilter;
+import io.quarkus.jfr.runtime.http.rest.classic.ClassicServerRecorderProducer;
+import io.quarkus.jfr.runtime.http.rest.reactive.ReactiveServerFilters;
+import io.quarkus.jfr.runtime.http.rest.reactive.ReactiveServerRecorderProducer;
+import io.quarkus.jfr.runtime.http.rest.reactive.ServerStartRecordingHandler;
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
+import io.quarkus.resteasy.reactive.server.spi.GlobalHandlerCustomizerBuildItem;
import io.quarkus.resteasy.reactive.spi.CustomContainerRequestFilterBuildItem;
@BuildSteps
@@ -52,7 +54,8 @@ void registerRequestIdProducer(Capabilities capabilities,
@BuildStep
void registerRestIntegration(Capabilities capabilities,
BuildProducer filterBeans,
- BuildProducer additionalBeans) {
+ BuildProducer additionalBeans,
+ BuildProducer globalHandlerCustomizerProducer) {
if (capabilities.isPresent(Capability.RESTEASY_REACTIVE)) {
@@ -61,7 +64,10 @@ void registerRestIntegration(Capabilities capabilities,
.build());
filterBeans
- .produce(new CustomContainerRequestFilterBuildItem(JfrReactiveServerFilter.class.getName()));
+ .produce(new CustomContainerRequestFilterBuildItem(ReactiveServerFilters.class.getName()));
+
+ globalHandlerCustomizerProducer
+ .produce(new GlobalHandlerCustomizerBuildItem(new ServerStartRecordingHandler.Customizer()));
}
}
@@ -76,7 +82,7 @@ void registerResteasyClassicIntegration(Capabilities capabilities,
.build());
resteasyJaxrsProviderBuildItemBuildProducer
- .produce(new ResteasyJaxrsProviderBuildItem(JfrClassicServerFilter.class.getName()));
+ .produce(new ResteasyJaxrsProviderBuildItem(ClassicServerFilter.class.getName()));
}
}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrReactiveServerFilter.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrReactiveServerFilter.java
deleted file mode 100644
index 46f14bdead66e..0000000000000
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrReactiveServerFilter.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.quarkus.jfr.runtime.http.rest;
-
-import jakarta.inject.Inject;
-import jakarta.ws.rs.container.ContainerResponseContext;
-import jakarta.ws.rs.core.Response;
-
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.reactive.server.ServerRequestFilter;
-import org.jboss.resteasy.reactive.server.ServerResponseFilter;
-
-public class JfrReactiveServerFilter {
-
- private static final Logger LOG = Logger.getLogger(JfrReactiveServerFilter.class);
-
- @Inject
- Recorder recorder;
-
- @ServerRequestFilter
- public void requestFilter() {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Enter Jfr Reactive Request Filter");
- }
- recorder.recordStartEvent();
- recorder.startPeriodEvent();
- }
-
- @ServerResponseFilter
- public void responseFilter(ContainerResponseContext responseContext) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Enter Jfr Reactive Response Filter");
- }
- if (isRecordable(responseContext)) {
- recorder.endPeriodEvent();
- recorder.recordEndEvent();
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Recording REST event was skipped");
- }
- }
- }
-
- private boolean isRecordable(ContainerResponseContext responseContext) {
- return responseContext.getStatus() != Response.Status.NOT_FOUND.getStatusCode();
- }
-}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ReactiveServerRecorderProducer.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ReactiveServerRecorderProducer.java
deleted file mode 100644
index 393e50c84848e..0000000000000
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ReactiveServerRecorderProducer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package io.quarkus.jfr.runtime.http.rest;
-
-import jakarta.enterprise.context.Dependent;
-import jakarta.enterprise.context.RequestScoped;
-import jakarta.enterprise.inject.Produces;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.core.Context;
-
-import org.jboss.resteasy.reactive.server.SimpleResourceInfo;
-
-import io.quarkus.jfr.runtime.IdProducer;
-import io.vertx.core.http.HttpServerRequest;
-
-@Dependent
-public class ReactiveServerRecorderProducer {
-
- @Context
- HttpServerRequest vertxRequest;
-
- @Context
- SimpleResourceInfo resourceInfo;
-
- @Inject
- IdProducer idProducer;
-
- @Produces
- @RequestScoped
- public Recorder create() {
- String httpMethod = vertxRequest.method().name();
- String uri = vertxRequest.path();
- Class> resourceClass = resourceInfo.getResourceClass();
- String resourceClassName = (resourceClass == null) ? null : resourceClass.getName();
- String resourceMethodName = resourceInfo.getMethodName();
- String client = vertxRequest.remoteAddress().toString();
-
- return new ServerRecorder(httpMethod, uri, resourceClassName, resourceMethodName, client, idProducer);
- }
-}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/Recorder.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/Recorder.java
deleted file mode 100644
index fc8535b773d67..0000000000000
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/Recorder.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.quarkus.jfr.runtime.http.rest;
-
-public interface Recorder {
-
- void recordStartEvent();
-
- void recordEndEvent();
-
- void startPeriodEvent();
-
- void endPeriodEvent();
-}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrClassicServerFilter.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerFilter.java
similarity index 76%
rename from extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrClassicServerFilter.java
rename to extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerFilter.java
index e019c5dfb6710..2e67e40201571 100644
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/JfrClassicServerFilter.java
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerFilter.java
@@ -1,4 +1,4 @@
-package io.quarkus.jfr.runtime.http.rest;
+package io.quarkus.jfr.runtime.http.rest.classic;
import java.io.IOException;
@@ -14,16 +14,16 @@
import io.quarkus.arc.Arc;
@Provider
-public class JfrClassicServerFilter implements ContainerRequestFilter, ContainerResponseFilter {
+public class ClassicServerFilter implements ContainerRequestFilter, ContainerResponseFilter {
- private static final Logger LOG = Logger.getLogger(JfrClassicServerFilter.class);
+ private static final Logger LOG = Logger.getLogger(ClassicServerFilter.class);
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Enter Jfr Classic Request Filter");
}
- Recorder recorder = Arc.container().instance(Recorder.class).get();
+ ClassicServerRecorder recorder = Arc.container().instance(ClassicServerRecorder.class).get();
recorder.recordStartEvent();
recorder.startPeriodEvent();
}
@@ -36,7 +36,7 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
}
if (isRecordable(responseContext)) {
- Recorder recorder = Arc.container().instance(Recorder.class).get();
+ ClassicServerRecorder recorder = Arc.container().instance(ClassicServerRecorder.class).get();
recorder.endPeriodEvent();
recorder.recordEndEvent();
} else {
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ServerRecorder.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorder.java
similarity index 81%
rename from extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ServerRecorder.java
rename to extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorder.java
index 3f2dc0428fdc6..fed42a45de64e 100644
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ServerRecorder.java
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorder.java
@@ -1,9 +1,12 @@
-package io.quarkus.jfr.runtime.http.rest;
+package io.quarkus.jfr.runtime.http.rest.classic;
import io.quarkus.jfr.runtime.IdProducer;
import io.quarkus.jfr.runtime.http.AbstractHttpEvent;
+import io.quarkus.jfr.runtime.http.rest.RestEndEvent;
+import io.quarkus.jfr.runtime.http.rest.RestPeriodEvent;
+import io.quarkus.jfr.runtime.http.rest.RestStartEvent;
-public class ServerRecorder implements Recorder {
+public class ClassicServerRecorder {
private final String httpMethod;
private final String uri;
@@ -13,7 +16,8 @@ public class ServerRecorder implements Recorder {
private final IdProducer idProducer;
private RestPeriodEvent durationEvent;
- public ServerRecorder(String httpMethod, String uri, String resourceClass, String resourceMethod, String client,
+ public ClassicServerRecorder(String httpMethod, String uri, String resourceClass, String resourceMethod,
+ String client,
IdProducer idProducer) {
this.httpMethod = httpMethod;
this.uri = uri;
@@ -23,7 +27,6 @@ public ServerRecorder(String httpMethod, String uri, String resourceClass, Strin
this.idProducer = idProducer;
}
- @Override
public void recordStartEvent() {
RestStartEvent startEvent = new RestStartEvent();
@@ -34,7 +37,6 @@ public void recordStartEvent() {
}
}
- @Override
public void recordEndEvent() {
RestEndEvent endEvent = new RestEndEvent();
@@ -45,13 +47,11 @@ public void recordEndEvent() {
}
}
- @Override
public void startPeriodEvent() {
durationEvent = new RestPeriodEvent();
durationEvent.begin();
}
- @Override
public void endPeriodEvent() {
durationEvent.end();
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ClassicServerRecorderProducer.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorderProducer.java
similarity index 83%
rename from extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ClassicServerRecorderProducer.java
rename to extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorderProducer.java
index 9ee161e302ade..355df43b5571e 100644
--- a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/ClassicServerRecorderProducer.java
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/classic/ClassicServerRecorderProducer.java
@@ -1,4 +1,4 @@
-package io.quarkus.jfr.runtime.http.rest;
+package io.quarkus.jfr.runtime.http.rest.classic;
import java.lang.reflect.Method;
@@ -25,7 +25,7 @@ public class ClassicServerRecorderProducer {
@Produces
@RequestScoped
- public Recorder create() {
+ public ClassicServerRecorder create() {
String httpMethod = vertxRequest.method().name();
String uri = vertxRequest.path();
Class> resourceClass = resourceInfo.getResourceClass();
@@ -34,6 +34,6 @@ public Recorder create() {
String resourceMethodName = (resourceMethod == null) ? null : resourceMethod.getName();
String client = vertxRequest.remoteAddress().toString();
- return new ServerRecorder(httpMethod, uri, resourceClassName, resourceMethodName, client, idProducer);
+ return new ClassicServerRecorder(httpMethod, uri, resourceClassName, resourceMethodName, client, idProducer);
}
}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerFilters.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerFilters.java
new file mode 100644
index 0000000000000..b7d1566dfb9be
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerFilters.java
@@ -0,0 +1,50 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.server.ServerRequestFilter;
+import org.jboss.resteasy.reactive.server.ServerResponseFilter;
+import org.jboss.resteasy.reactive.server.SimpleResourceInfo;
+
+public class ReactiveServerFilters {
+
+ private static final Logger LOG = Logger.getLogger(ReactiveServerFilters.class);
+
+ private final ReactiveServerRecorder recorder;
+
+ public ReactiveServerFilters(ReactiveServerRecorder recorder) {
+ this.recorder = recorder;
+ }
+
+ /**
+ * Executed if request processing proceeded correctly.
+ * We now have to update the start event with the resource class and method data and also commit the event.
+ */
+ @ServerRequestFilter
+ public void requestFilter(SimpleResourceInfo resourceInfo) {
+ Class> resourceClass = resourceInfo.getResourceClass();
+ if (resourceClass != null) { // should always be the case
+ String resourceClassName = resourceClass.getName();
+ String resourceMethodName = resourceInfo.getMethodName();
+ recorder
+ .updateResourceInfo(new ResourceInfo(resourceClassName, resourceMethodName))
+ .commitStartEventIfNecessary();
+ }
+
+ }
+
+ /**
+ * This will execute regardless of a processing failure or not.
+ * If there was a failure, we need to check if the start event was not commited
+ * (which happens when request was not matched to any resource method) and if so, commit it.
+ */
+ @ServerResponseFilter
+ public void responseFilter() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Enter Jfr Reactive Response Filter");
+ }
+ recorder
+ .recordEndEvent()
+ .endPeriodEvent();
+ }
+
+}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorder.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorder.java
new file mode 100644
index 0000000000000..151736f520d0d
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorder.java
@@ -0,0 +1,98 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+import io.quarkus.jfr.runtime.IdProducer;
+import io.quarkus.jfr.runtime.http.AbstractHttpEvent;
+import io.quarkus.jfr.runtime.http.rest.RestEndEvent;
+import io.quarkus.jfr.runtime.http.rest.RestPeriodEvent;
+import io.quarkus.jfr.runtime.http.rest.RestStartEvent;
+
+class ReactiveServerRecorder {
+
+ private final RequestInfo requestInfo;
+ private final IdProducer idProducer;
+
+ private volatile ResourceInfo resourceInfo;
+
+ private volatile RestStartEvent startEvent;
+ // TODO: we can perhaps get rid of this volatile if access patterns to this and startEvent allow it
+ private volatile boolean startEventHandled;
+
+ private volatile RestPeriodEvent durationEvent;
+
+ public ReactiveServerRecorder(RequestInfo requestInfo, IdProducer idProducer) {
+ this.requestInfo = requestInfo;
+ this.idProducer = idProducer;
+ }
+
+ public ReactiveServerRecorder createStartEvent() {
+ startEvent = new RestStartEvent();
+ return this;
+ }
+
+ public ReactiveServerRecorder createAndStartPeriodEvent() {
+ durationEvent = new RestPeriodEvent();
+ durationEvent.begin();
+ return this;
+ }
+
+ public ReactiveServerRecorder updateResourceInfo(ResourceInfo resourceInfo) {
+ this.resourceInfo = resourceInfo;
+ return this;
+ }
+
+ public ReactiveServerRecorder commitStartEventIfNecessary() {
+ startEventHandled = true;
+ var se = startEvent;
+ if (se.shouldCommit()) {
+ setHttpInfo(startEvent);
+ se.commit();
+ }
+ return this;
+ }
+
+ /**
+ * Because this can be called when a start event has not been completely handled
+ * (this happens when request processing failed because a Resource method could not be identified),
+ * we need to handle that event as well.
+ */
+ public ReactiveServerRecorder recordEndEvent() {
+ if (!startEventHandled) {
+ commitStartEventIfNecessary();
+ }
+
+ RestEndEvent endEvent = new RestEndEvent();
+ if (endEvent.shouldCommit()) {
+ setHttpInfo(endEvent);
+ endEvent.commit();
+ }
+
+ return this;
+ }
+
+ public ReactiveServerRecorder endPeriodEvent() {
+ if (durationEvent != null) {
+ durationEvent.end();
+ if (durationEvent.shouldCommit()) {
+ setHttpInfo(durationEvent);
+ durationEvent.commit();
+ }
+ } else {
+ // this shouldn't happen, but if it does due to an error on our side, the request processing shouldn't be botched because of it
+ }
+
+ return this;
+ }
+
+ private void setHttpInfo(AbstractHttpEvent event) {
+ event.setTraceId(idProducer.getTraceId());
+ event.setSpanId(idProducer.getSpanId());
+ event.setHttpMethod(requestInfo.httpMethod());
+ event.setUri(requestInfo.uri());
+ event.setClient(requestInfo.remoteAddress());
+ var ri = resourceInfo;
+ if (resourceInfo != null) {
+ event.setResourceClass(ri.resourceClass());
+ event.setResourceMethod(ri.resourceMethod());
+ }
+ }
+}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorderProducer.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorderProducer.java
new file mode 100644
index 0000000000000..62cd81ce11bc5
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ReactiveServerRecorderProducer.java
@@ -0,0 +1,18 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+import jakarta.enterprise.context.RequestScoped;
+
+import io.quarkus.jfr.runtime.IdProducer;
+import io.vertx.core.http.HttpServerRequest;
+
+public class ReactiveServerRecorderProducer {
+
+ @RequestScoped
+ public ReactiveServerRecorder create(IdProducer idProducer, HttpServerRequest vertxRequest) {
+ String httpMethod = vertxRequest.method().name();
+ String uri = vertxRequest.path();
+ String client = vertxRequest.remoteAddress().toString();
+
+ return new ReactiveServerRecorder(new RequestInfo(httpMethod, uri, client), idProducer);
+ }
+}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/RequestInfo.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/RequestInfo.java
new file mode 100644
index 0000000000000..41a9baa19add4
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/RequestInfo.java
@@ -0,0 +1,4 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+record RequestInfo(String httpMethod, String uri, String remoteAddress) {
+}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ResourceInfo.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ResourceInfo.java
new file mode 100644
index 0000000000000..219b842e679c7
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ResourceInfo.java
@@ -0,0 +1,4 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+record ResourceInfo(String resourceClass, String resourceMethod) {
+}
diff --git a/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ServerStartRecordingHandler.java b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ServerStartRecordingHandler.java
new file mode 100644
index 0000000000000..74ec6fd7f61c0
--- /dev/null
+++ b/extensions/jfr/runtime/src/main/java/io/quarkus/jfr/runtime/http/rest/reactive/ServerStartRecordingHandler.java
@@ -0,0 +1,48 @@
+package io.quarkus.jfr.runtime.http.rest.reactive;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.reactive.common.model.ResourceClass;
+import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
+import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
+import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
+import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.jfr.runtime.http.rest.RestStartEvent;
+
+/**
+ * Kicks off the creation of a {@link RestStartEvent}.
+ * This is done very early as to be able to capture events such as 405, 406, etc.
+ */
+public class ServerStartRecordingHandler implements ServerRestHandler {
+
+ private static final ServerStartRecordingHandler INSTANCE = new ServerStartRecordingHandler();
+
+ private static final Logger LOG = Logger.getLogger(ServerStartRecordingHandler.class);
+
+ @Override
+ public void handle(ResteasyReactiveRequestContext requestContext) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Enter Jfr Reactive Request Filter");
+ }
+ requestContext.requireCDIRequestScope();
+ ReactiveServerRecorder recorder = Arc.container().instance(ReactiveServerRecorder.class).get();
+ recorder
+ .createStartEvent()
+ .createAndStartPeriodEvent();
+ }
+
+ public static class Customizer implements HandlerChainCustomizer {
+ @Override
+ public List handlers(Phase phase, ResourceClass resourceClass,
+ ServerResourceMethod serverResourceMethod) {
+ if (phase == Phase.AFTER_PRE_MATCH) {
+ return Collections.singletonList(INSTANCE);
+ }
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
index 53a0f45a83e39..f6a3c35aa8f3d 100644
--- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
+++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
@@ -228,7 +228,7 @@ private RunningDevService startKafka(DockerStatusBuildItem dockerStatusBuildItem
switch (config.provider) {
case REDPANDA:
RedpandaKafkaContainer redpanda = new RedpandaKafkaContainer(
- DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("vectorized/redpanda"),
+ DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("redpandadata/redpanda"),
config.fixedExposedPort,
launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT ? config.serviceName : null,
useSharedNetwork, config.redpanda);
diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaDevServicesBuildTimeConfig.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaDevServicesBuildTimeConfig.java
index d3bbc818fc103..4207faf0f521b 100644
--- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaDevServicesBuildTimeConfig.java
+++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaDevServicesBuildTimeConfig.java
@@ -34,7 +34,7 @@ public class KafkaDevServicesBuildTimeConfig {
* Redpanda, Strimzi and kafka-native container providers are supported. Default is redpanda.
*
* For Redpanda:
- * See https://docs.redpanda.com/current/get-started/quick-start/ and https://hub.docker.com/r/vectorized/redpanda
+ * See https://docs.redpanda.com/current/get-started/quick-start/ and https://hub.docker.com/r/redpandadata/redpanda
*
* For Strimzi:
* See https://github.com/strimzi/test-container and https://quay.io/repository/strimzi-test-container/test-container
@@ -48,7 +48,7 @@ public class KafkaDevServicesBuildTimeConfig {
public Provider provider = Provider.REDPANDA;
public enum Provider {
- REDPANDA("docker.io/vectorized/redpanda:v24.1.2"),
+ REDPANDA("docker.io/redpandadata/redpanda:v24.1.2"),
STRIMZI("quay.io/strimzi-test-container/test-container:latest-kafka-3.7.0"),
KAFKA_NATIVE("quay.io/ogunalp/kafka-native:latest");
diff --git a/extensions/kafka-client/deployment/src/main/resources/dev-ui/qwc-kafka-add-topic.js b/extensions/kafka-client/deployment/src/main/resources/dev-ui/qwc-kafka-add-topic.js
index ad8ca2104ee67..c26c0d9cb26ea 100644
--- a/extensions/kafka-client/deployment/src/main/resources/dev-ui/qwc-kafka-add-topic.js
+++ b/extensions/kafka-client/deployment/src/main/resources/dev-ui/qwc-kafka-add-topic.js
@@ -59,6 +59,13 @@ export class QwcKafkaAddTopic extends LitElement {
min="0"
max="99">
+ this._configsChanged(e)}">
+
${this._renderButtons()}`;
}
@@ -70,10 +77,11 @@ export class QwcKafkaAddTopic extends LitElement {
}
_reset(){
- this._newTopic = new Object();
+ this._newTopic = {};
this._newTopic.name = '';
this._newTopic.partitions = 1;
this._newTopic.replications = 1;
+ this._newTopic.configs = undefined;
}
_cancel(){
@@ -89,11 +97,11 @@ export class QwcKafkaAddTopic extends LitElement {
_submit(){
if(this._newTopic.name.trim() !== ''){
-
this.jsonRpc.createTopic({
topicName: this._newTopic.name,
partitions: parseInt(this._newTopic.partitions),
- replications: parseInt(this._newTopic.replications)
+ replications: parseInt(this._newTopic.replications),
+ configs: this._newTopic.configs
}).then(jsonRpcResponse => {
this._reset();
const success = new CustomEvent("kafka-topic-added-success", {
@@ -119,6 +127,17 @@ export class QwcKafkaAddTopic extends LitElement {
_replicationsChanged(e){
this._newTopic.replications = e.detail.value;
}
+
+ _configsChanged(e){
+ this._newTopic.configs = Object.fromEntries(e.detail.value.split(',')
+ .reduce((configs, item) => {
+ const split = item.trim().split('=');
+ if (split.length > 1) {
+ configs.set(split[0], split[1]);
+ }
+ return configs;
+ }, new Map()));
+ }
}
customElements.define('qwc-kafka-add-topic', QwcKafkaAddTopic);
\ No newline at end of file
diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java
index 0ced0504abcda..8f8486630e18c 100644
--- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java
+++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaAdminClient.java
@@ -70,23 +70,23 @@ public Collection getConsumerGroups() throws Interrupt
.values();
}
- public boolean deleteTopic(String name) {
+ public boolean deleteTopic(final String name) {
Collection topics = new ArrayList<>();
topics.add(name);
DeleteTopicsResult dtr = client.deleteTopics(topics);
return dtr.topicNameValues() != null;
}
- public boolean createTopic(KafkaCreateTopicRequest kafkaCreateTopicRq) {
+ public boolean createTopic(final KafkaCreateTopicRequest kafkaCreateTopicRq) {
var partitions = Optional.ofNullable(kafkaCreateTopicRq.getPartitions()).orElse(1);
var replications = Optional.ofNullable(kafkaCreateTopicRq.getReplications()).orElse((short) 1);
var newTopic = new NewTopic(kafkaCreateTopicRq.getTopicName(), partitions, replications);
-
+ newTopic.configs(Optional.ofNullable(kafkaCreateTopicRq.getConfigs()).orElse(Map.of()));
CreateTopicsResult ctr = client.createTopics(List.of(newTopic));
return ctr.values() != null;
}
- public ListConsumerGroupOffsetsResult listConsumerGroupOffsets(String groupId) {
+ public ListConsumerGroupOffsetsResult listConsumerGroupOffsets(final String groupId) {
return client.listConsumerGroupOffsets(groupId);
}
@@ -96,7 +96,7 @@ public Collection getAclInfo() throws InterruptedException, Executio
return client.describeAcls(filter, options).values().get();
}
- public Map describeTopics(Collection topicNames)
+ public Map describeTopics(final Collection topicNames)
throws InterruptedException, ExecutionException {
return client.describeTopics(topicNames)
.allTopicNames()
diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/KafkaJsonRPCService.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/KafkaJsonRPCService.java
index c9d37cbb001e4..b5fe4ebdd3fcf 100644
--- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/KafkaJsonRPCService.java
+++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/KafkaJsonRPCService.java
@@ -30,10 +30,12 @@ public List getTopics() throws InterruptedException, ExecutionExcept
return kafkaUiUtils.getTopics();
}
- public List createTopic(String topicName, int partitions, int replications)
+ public List createTopic(final String topicName, final int partitions, final int replications,
+ Map configs)
throws InterruptedException, ExecutionException {
- KafkaCreateTopicRequest createTopicRequest = new KafkaCreateTopicRequest(topicName, partitions, (short) replications);
+ KafkaCreateTopicRequest createTopicRequest = new KafkaCreateTopicRequest(topicName, partitions, (short) replications,
+ configs);
boolean created = kafkaAdminClient.createTopic(createTopicRequest);
if (created) {
return kafkaUiUtils.getTopics();
@@ -41,7 +43,7 @@ public List createTopic(String topicName, int partitions, int replic
throw new RuntimeException("Topic [" + topicName + "] not created");
}
- public List deleteTopic(String topicName) throws InterruptedException, ExecutionException {
+ public List deleteTopic(final String topicName) throws InterruptedException, ExecutionException {
boolean deleted = kafkaAdminClient.deleteTopic(topicName);
if (deleted) {
return kafkaUiUtils.getTopics();
@@ -49,7 +51,7 @@ public List deleteTopic(String topicName) throws InterruptedExceptio
throw new RuntimeException("Topic [" + topicName + "] not deleted");
}
- public KafkaMessagePage topicMessages(String topicName) throws ExecutionException, InterruptedException {
+ public KafkaMessagePage topicMessages(final String topicName) throws ExecutionException, InterruptedException {
List partitions = getPartitions(topicName);
KafkaOffsetRequest offsetRequest = new KafkaOffsetRequest(topicName, partitions, Order.NEW_FIRST);
Map offset = kafkaUiUtils.getOffset(offsetRequest);
@@ -71,7 +73,7 @@ public KafkaMessagePage createMessage(String topicName, Integer partition, Strin
return topicMessages(topicName);
}
- public List getPartitions(String topicName) throws ExecutionException, InterruptedException {
+ public List getPartitions(final String topicName) throws ExecutionException, InterruptedException {
return new ArrayList<>(kafkaUiUtils.partitions(topicName));
}
diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/model/request/KafkaCreateTopicRequest.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/model/request/KafkaCreateTopicRequest.java
index b099bb06ca3e9..883a6ba1ecaa4 100644
--- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/model/request/KafkaCreateTopicRequest.java
+++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/devui/model/request/KafkaCreateTopicRequest.java
@@ -1,17 +1,22 @@
package io.quarkus.kafka.client.runtime.devui.model.request;
+import java.util.Map;
+
public class KafkaCreateTopicRequest {
private String topicName;
private Integer partitions;
private Short replications;
+ private Map configs;
public KafkaCreateTopicRequest() {
}
- public KafkaCreateTopicRequest(String topicName, Integer partitions, Short replications) {
+ public KafkaCreateTopicRequest(final String topicName, final Integer partitions, final Short replications,
+ final Map configs) {
this.topicName = topicName;
this.partitions = partitions;
this.replications = replications;
+ this.configs = configs;
}
public String getTopicName() {
@@ -25,4 +30,9 @@ public Integer getPartitions() {
public Short getReplications() {
return replications;
}
+
+ public Map getConfigs() {
+ return configs;
+ }
+
}
diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java
index aff88dfbd80b2..8a95b61750fe2 100644
--- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java
+++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java
@@ -62,7 +62,9 @@ void registerKotlinReflection(final BuildProducer refl
reflectiveClass.produce(ReflectiveClassBuildItem.builder("kotlin.KotlinVersion$Companion[]").constructors(false)
.build());
reflectiveClass.produce(
- ReflectiveClassBuildItem.builder("kotlin.collections.EmptyList", "kotlin.collections.EmptyMap").build());
+ ReflectiveClassBuildItem
+ .builder("kotlin.collections.EmptyList", "kotlin.collections.EmptyMap", "kotlin.collections.EmptySet")
+ .build());
nativeResourcePatterns.produce(builder().includePatterns(
"META-INF/.*.kotlin_module$",
diff --git a/extensions/kubernetes/openshift/deployment/src/test/java/io/quarkus/openshift/deployment/config/OpenShiftConfigFallbackTest.java b/extensions/kubernetes/openshift/deployment/src/test/java/io/quarkus/openshift/deployment/config/OpenShiftConfigFallbackTest.java
index f67261d858adf..b0c8aa6082a49 100644
--- a/extensions/kubernetes/openshift/deployment/src/test/java/io/quarkus/openshift/deployment/config/OpenShiftConfigFallbackTest.java
+++ b/extensions/kubernetes/openshift/deployment/src/test/java/io/quarkus/openshift/deployment/config/OpenShiftConfigFallbackTest.java
@@ -25,7 +25,6 @@ public class OpenShiftConfigFallbackTest {
static final QuarkusProdModeTest TEST = new QuarkusProdModeTest()
.setApplicationName("config")
.setApplicationVersion("0.1-SNAPSHOT")
- .overrideConfigKey("quarkus.kubernetes.replicas", "10")
.overrideConfigKey("quarkus.openshift.version", "999-SNAPSHOT")
.overrideConfigKey("quarkus.openshift.labels.app", "openshift")
.overrideConfigKey("quarkus.openshift.route.expose", "true")
@@ -51,10 +50,6 @@ void configFallback() throws Exception {
YamlConfigSource kubernetes = new YamlConfigSource(kubernetesDir.resolve("kubernetes.yml").toUri().toURL());
YamlConfigSource openshift = new YamlConfigSource(kubernetesDir.resolve("openshift.yml").toUri().toURL());
- // spec.replicas is only for Kubernetes and Openshift, no fallback
- assertEquals("10", kubernetes.getValue("spec.replicas"));
- assertEquals("1", openshift.getValue("spec.replicas"));
-
// In both, each should retain the value
assertEquals("0.1-SNAPSHOT", kubernetes.getValue("spec.template.metadata.labels.\"app.kubernetes.io/version\""));
assertEquals("999-SNAPSHOT", openshift.getValue("spec.template.metadata.labels.\"app.kubernetes.io/version\""));
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigBuilderCustomizer.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigBuilderCustomizer.java
index bc7f984e8b74e..1e8f7a2ad0577 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigBuilderCustomizer.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigBuilderCustomizer.java
@@ -2,12 +2,10 @@
import static io.smallrye.config.ConfigMappingInterface.getProperties;
import static io.smallrye.config.ConfigMappingLoader.getConfigMapping;
-import static io.smallrye.config.ProfileConfigSourceInterceptor.convertProfile;
+import static io.smallrye.config.ConfigValue.CONFIG_SOURCE_COMPARATOR;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Function;
@@ -17,7 +15,6 @@
import io.smallrye.config.ConfigSourceInterceptorFactory;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.FallbackConfigSourceInterceptor;
-import io.smallrye.config.NameIterator;
import io.smallrye.config.Priorities;
import io.smallrye.config.PropertyName;
import io.smallrye.config.RelocateConfigSourceInterceptor;
@@ -25,19 +22,21 @@
import io.smallrye.config.SmallRyeConfigBuilderCustomizer;
public class KubernetesConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer {
+ private static final Set IGNORE_OPENSHIFT_NAMES = ignoreOpenshiftNames();
+ private static final Set IGNORE_KNATIVE_NAMES = ignoreKnativeNames();
+
@Override
public void configBuilder(final SmallRyeConfigBuilder builder) {
- Set ignoreNames = ignoreNames();
-
builder.withInterceptorFactories(new ConfigSourceInterceptorFactory() {
@Override
public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) {
return new Fallbacks(new Function() {
@Override
public String apply(final String name) {
- if (name.startsWith("quarkus.openshift.") && !ignoreNames.contains(new PropertyName(name))) {
+ if (name.startsWith("quarkus.openshift.") && !IGNORE_OPENSHIFT_NAMES.contains(new PropertyName(name))) {
return "quarkus.kubernetes." + name.substring(18);
- } else if (name.startsWith("quarkus.knative.") && !ignoreNames.contains(new PropertyName(name))) {
+ } else if (name.startsWith("quarkus.knative.")
+ && !IGNORE_KNATIVE_NAMES.contains(new PropertyName(name))) {
return "quarkus.kubernetes." + name.substring(16);
}
return name;
@@ -64,7 +63,8 @@ public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorConte
return new RelocateConfigSourceInterceptor(new Function() {
@Override
public String apply(final String name) {
- if (name.startsWith("quarkus.kubernetes.") && !ignoreNames.contains(new PropertyName(name))) {
+ if (name.startsWith("quarkus.kubernetes.")
+ && !IGNORE_OPENSHIFT_NAMES.contains(new PropertyName(name))) {
return "quarkus.openshift." + name.substring(19);
}
return name;
@@ -84,7 +84,7 @@ public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorConte
return new RelocateConfigSourceInterceptor(new Function() {
@Override
public String apply(final String name) {
- if (name.startsWith("quarkus.kubernetes.") && !ignoreNames.contains(new PropertyName(name))) {
+ if (name.startsWith("quarkus.kubernetes.") && !IGNORE_KNATIVE_NAMES.contains(new PropertyName(name))) {
return "quarkus.knative." + name.substring(19);
}
return name;
@@ -134,73 +134,71 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final
}
}
- // TODO - This will become public in a new version of SmallRye Config - can be removed later
- private static final Comparator CONFIG_SOURCE_COMPARATOR = new Comparator() {
- @Override
- public int compare(ConfigValue original, ConfigValue candidate) {
- int result = Integer.compare(original.getConfigSourceOrdinal(), candidate.getConfigSourceOrdinal());
- if (result != 0) {
- return result;
- }
- result = Integer.compare(original.getConfigSourcePosition(), candidate.getConfigSourcePosition()) * -1;
- if (result != 0) {
- return result;
- }
- // If both properties are profiled, prioritize the one with the most specific profile.
- if (original.getName().charAt(0) == '%' && candidate.getName().charAt(0) == '%') {
- List originalProfiles = convertProfile(
- new NameIterator(original.getName()).getNextSegment().substring(1));
- List candidateProfiles = convertProfile(
- new NameIterator(candidate.getName()).getNextSegment().substring(1));
- return Integer.compare(originalProfiles.size(), candidateProfiles.size()) * -1;
- }
- return result;
- }
- };
-
/**
- * Collect the properties names that are not shared between kubernetes, openshift and
- * knative to ignore when performing the fallback functions.
+ * Collect the properties names that are not shared between kubernetes and openshift
+ * to ignore when performing the fallback functions.
*
* @return a Set of properties names to ignore
*/
- private static Set ignoreNames() {
- Set kubernetes = getProperties(getConfigMapping(KubernetesConfig.class))
- .get(KubernetesConfig.class).get("").keySet();
- Set openshift = getProperties(getConfigMapping(OpenShiftConfig.class))
- .get(OpenShiftConfig.class).get("").keySet();
- Set knative = getProperties(getConfigMapping(KnativeConfig.class))
- .get(KnativeConfig.class).get("").keySet();
+ private static Set ignoreOpenshiftNames() {
+ Set kubernetes = getProperties(getConfigMapping(KubernetesConfig.class)).get(KubernetesConfig.class).get("")
+ .keySet();
+ Set openshift = getProperties(getConfigMapping(OpenShiftConfig.class)).get(OpenShiftConfig.class).get("")
+ .keySet();
Set ignored = new HashSet<>();
for (String name : kubernetes) {
- if (!openshift.contains(name) || !knative.contains(name)) {
+ if (!openshift.contains(name)) {
ignored.add(new PropertyName("quarkus.kubernetes." + name));
ignored.add(new PropertyName("quarkus.openshift." + name));
- ignored.add(new PropertyName("quarkus.knative." + name));
}
}
+
for (String name : openshift) {
- if (!kubernetes.contains(name) || !knative.contains(name)) {
+ if (!kubernetes.contains(name)) {
+ ignored.add(new PropertyName("quarkus.kubernetes." + name));
+ ignored.add(new PropertyName("quarkus.openshift." + name));
+ }
+ }
+
+ // These are shared, but must work independently
+ ignored.add(new PropertyName("quarkus.kubernetes.deploy"));
+ ignored.add(new PropertyName("quarkus.openshift.deploy"));
+ ignored.add(new PropertyName("quarkus.kubernetes.deploy-strategy"));
+ ignored.add(new PropertyName("quarkus.openshift.deploy-strategy"));
+ return ignored;
+ }
+
+ /**
+ * Collect the properties names that are not shared between kubernetes and knative to
+ * ignore when performing the fallback functions.
+ *
+ * @return a Set of properties names to ignore
+ */
+ private static Set ignoreKnativeNames() {
+ Set kubernetes = getProperties(getConfigMapping(KubernetesConfig.class)).get(KubernetesConfig.class).get("")
+ .keySet();
+ Set knative = getProperties(getConfigMapping(KnativeConfig.class)).get(KnativeConfig.class).get("").keySet();
+
+ Set ignored = new HashSet<>();
+ for (String name : kubernetes) {
+ if (!knative.contains(name)) {
ignored.add(new PropertyName("quarkus.kubernetes." + name));
ignored.add(new PropertyName("quarkus.openshift." + name));
- ignored.add(new PropertyName("quarkus.knative." + name));
}
}
+
for (String name : knative) {
- if (!kubernetes.contains(name) || !openshift.contains(name)) {
+ if (!kubernetes.contains(name)) {
ignored.add(new PropertyName("quarkus.kubernetes." + name));
ignored.add(new PropertyName("quarkus.openshift." + name));
- ignored.add(new PropertyName("quarkus.knative." + name));
}
}
// These are shared, but must work independently
ignored.add(new PropertyName("quarkus.kubernetes.deploy"));
- ignored.add(new PropertyName("quarkus.openshift.deploy"));
ignored.add(new PropertyName("quarkus.knative.deploy"));
ignored.add(new PropertyName("quarkus.kubernetes.deploy-strategy"));
- ignored.add(new PropertyName("quarkus.openshift.deploy-strategy"));
ignored.add(new PropertyName("quarkus.knative.deploy-strategy"));
return ignored;
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigFallbackTest.java b/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigFallbackTest.java
index 3e3a42c8e9e2a..35387e5433ef3 100644
--- a/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigFallbackTest.java
+++ b/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigFallbackTest.java
@@ -2,6 +2,7 @@
import static io.smallrye.config.PropertiesConfigSourceLoader.inClassPath;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
@@ -10,6 +11,7 @@
import org.junit.jupiter.api.Test;
import io.quarkus.runtime.configuration.DurationConverter;
+import io.smallrye.config.PropertiesConfigSource;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
@@ -46,4 +48,23 @@ void fallback() {
assertEquals(knative.labels().get(entry.getKey()), entry.getValue());
}
}
+
+ @Test
+ void sharedOnlyBetweenKubernetesAndOpenshift() {
+ SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .addDiscoveredCustomizers()
+ .withConverter(Duration.class, 100, new DurationConverter())
+ .withMappingIgnore("quarkus.**")
+ .withMapping(KubernetesConfig.class)
+ .withMapping(OpenShiftConfig.class)
+ .withMapping(KnativeConfig.class)
+ .withSources(new PropertiesConfigSource(Map.of("quarkus.kubernetes.init-task-defaults.enabled", "false"), ""))
+ .build();
+
+ KubernetesConfig kubernetes = config.getConfigMapping(KubernetesConfig.class);
+ OpenShiftConfig openShift = config.getConfigMapping(OpenShiftConfig.class);
+
+ assertFalse(kubernetes.initTaskDefaults().enabled());
+ assertFalse(openShift.initTaskDefaults().enabled());
+ }
}
diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java
new file mode 100644
index 0000000000000..c1fe6f1afcee3
--- /dev/null
+++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java
@@ -0,0 +1,97 @@
+package io.quarkus.micrometer.deployment.binder;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.search.Search;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.quarkus.rest.client.reactive.Url;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class RestClientUriParameterTest {
+
+ final static SimpleMeterRegistry registry = new SimpleMeterRegistry();
+
+ @RegisterExtension
+ static final QuarkusUnitTest TEST = new QuarkusUnitTest()
+ .withApplicationRoot(
+ jar -> jar.addClasses(Resource.class, Client.class))
+ .overrideConfigKey("quarkus.redis.devservices.enabled", "false")
+ .overrideConfigKey("quarkus.rest-client.\"client\".url", "http://does-not-exist.io");
+
+ @RestClient
+ Client client;
+
+ @ConfigProperty(name = "quarkus.http.test-port")
+ Integer testPort;
+
+ @BeforeAll
+ static void setRegistry() {
+ Metrics.addRegistry(registry);
+ }
+
+ @AfterAll()
+ static void removeRegistry() {
+ Metrics.removeRegistry(registry);
+ }
+
+ @Test
+ public void testOverride() {
+ String result = client.getById("http://localhost:" + testPort, "bar");
+ assertEquals("bar", result);
+
+ Timer clientTimer = registry.find("http.client.requests").timer();
+ assertNotNull(clientTimer);
+ assertEquals("/example/{id}", clientTimer.getId().getTag("uri"));
+ }
+
+ private Search getMeter(String name) {
+ return registry.find(name);
+ }
+
+ @Path("/example")
+ @RegisterRestClient(baseUri = "http://dummy")
+ public interface Client {
+
+ @GET
+ @Path("/{id}")
+ String getById(@Url String baseUri, @PathParam("id") String id);
+ }
+
+ @Path("/example")
+ public static class Resource {
+
+ @RestClient
+ Client client;
+
+ @GET
+ @Path("/{id}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String example() {
+ return "bar";
+ }
+
+ @GET
+ @Path("/call")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String call() {
+ return client.getById("http://localhost:8080", "1");
+ }
+ }
+}
diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java
index d153fdb65fc5b..8bca89242a0e7 100644
--- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java
+++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java
@@ -44,6 +44,14 @@ public class UriTagWithHttpRootTest {
@Inject
MeterRegistry registry;
+ @Test
+ public void testClient() throws InterruptedException {
+ when().get("/ping/one").then().statusCode(200);
+ Util.waitForMeters(registry.find("http.server.requests").timers(), 1);
+ Util.waitForMeters(registry.find("http.client.requests").timers(), 1);
+ Assertions.assertEquals(1, registry.find("http.client.requests").tag("uri", "/pong/{message}").timers().size());
+ }
+
@Test
public void testRequestUris() throws Exception {
RestAssured.basePath = "/";
diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml
index 8bd99d1840900..36b104c57bf4b 100644
--- a/extensions/mongodb-client/deployment/pom.xml
+++ b/extensions/mongodb-client/deployment/pom.xml
@@ -63,7 +63,12 @@
io.quarkus
- quarkus-smallrye-metrics-deployment
+ quarkus-micrometer-deployment
+ true
+
+
+ io.quarkus
+ quarkus-micrometer-registry-prometheus-deploymenttest
@@ -86,6 +91,11 @@
awaitilitytest
+
+ org.mockito
+ mockito-core
+ test
+ org.assertjassertj-core
diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
index 6413be3720314..68003f57ea613 100644
--- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
+++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
@@ -64,6 +64,7 @@
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.mongodb.MongoClientName;
+import io.quarkus.mongodb.metrics.MicrometerCommandListener;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.mongodb.runtime.MongoClientBeanUtil;
import io.quarkus.mongodb.runtime.MongoClientCustomizer;
@@ -124,6 +125,21 @@ AdditionalIndexedClassesBuildItem includeMongoCommandListener(MongoClientBuildTi
return new AdditionalIndexedClassesBuildItem();
}
+ @BuildStep
+ void includeMongoCommandMetricListener(
+ BuildProducer additionalIndexedClasses,
+ MongoClientBuildTimeConfig buildTimeConfig,
+ Optional metricsCapability) {
+ if (!buildTimeConfig.metricsEnabled) {
+ return;
+ }
+ boolean withMicrometer = metricsCapability.map(cap -> cap.metricsSupported(MetricsFactory.MICROMETER))
+ .orElse(false);
+ if (withMicrometer) {
+ additionalIndexedClasses.produce(new AdditionalIndexedClassesBuildItem(MicrometerCommandListener.class.getName()));
+ }
+ }
+
@BuildStep
public void registerDnsProvider(BuildProducer nativeProducer) {
nativeProducer.produce(new NativeImageResourceBuildItem("META-INF/services/" + DnsClientProvider.class.getName()));
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java
index c8c1d01716668..10c1cae82bf3a 100644
--- a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java
@@ -4,58 +4,47 @@
import jakarta.inject.Inject;
-import org.eclipse.microprofile.metrics.Metric;
-import org.eclipse.microprofile.metrics.MetricID;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.Tag;
-import org.eclipse.microprofile.metrics.annotation.RegistryType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.mongodb.client.MongoClient;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
import io.quarkus.arc.Arc;
-import io.quarkus.mongodb.metrics.ConnectionPoolGauge;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;
/** Variation of {@link io.quarkus.mongodb.MongoMetricsTest} to verify lazy client initialization. */
-public class MongoLazyTest extends MongoTestBase {
+class MongoLazyTest extends MongoTestBase {
@Inject
- @RegistryType(type = MetricRegistry.Type.VENDOR)
- MetricRegistry registry;
+ MeterRegistry meterRegistry;
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
- .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
+ .withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class))
.withConfigurationResource("application-metrics-mongo.properties");
@Test
void testLazyClientCreation() {
// Clients are created lazily, this metric should not be present yet
- assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull();
- assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull();
+ assertThat(getMetric("mongodb.driver.pool.size")).isNull();
+ assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();
+ assertThat(getMetric("mongodb.driver.commands")).isNull();
// doing this here instead of in another method in order to avoid messing with the initialization stats
assertThat(Arc.container().instance(MongoClient.class).get()).isNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull();
}
- private Long getGaugeValueOrNull(String metricName, Tag[] tags) {
- MetricID metricID = new MetricID(metricName, tags);
- Metric metric = registry.getMetrics().get(metricID);
-
- if (metric == null) {
- return null;
- }
- return ((ConnectionPoolGauge) metric).getValue();
+ private Double getMetric(String name) {
+ Meter metric = meterRegistry.getMeters()
+ .stream()
+ .filter(mtr -> mtr.getId().getName().contains(name))
+ .findFirst()
+ .orElse(null);
+ return metric == null ? null : metric.measure().iterator().next().getValue();
}
- private Tag[] getTags() {
- return new Tag[] {
- new Tag("host", "127.0.0.1"),
- new Tag("port", "27018"),
- };
- }
}
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java
index dab2253817afe..cc54cb1b61d8a 100644
--- a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java
@@ -1,38 +1,32 @@
package io.quarkus.mongodb;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import jakarta.inject.Inject;
-import org.eclipse.microprofile.metrics.Metric;
-import org.eclipse.microprofile.metrics.MetricID;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.Tag;
-import org.eclipse.microprofile.metrics.annotation.RegistryType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import com.mongodb.client.MongoClient;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
import io.quarkus.arc.Arc;
-import io.quarkus.mongodb.metrics.ConnectionPoolGauge;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;
-public class MongoMetricsTest extends MongoTestBase {
+class MongoMetricsTest extends MongoTestBase {
@Inject
MongoClient client;
@Inject
- @RegistryType(type = MetricRegistry.Type.VENDOR)
- MetricRegistry registry;
+ MeterRegistry meterRegistry;
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
- .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class))
+ .withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class))
.withConfigurationResource("application-metrics-mongo.properties");
@AfterEach
@@ -45,38 +39,32 @@ void cleanup() {
@Test
void testMetricsInitialization() {
// Clients are created lazily, this metric should not be present yet
- assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull();
- assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull();
+ assertThat(getMetric("mongodb.driver.pool.size")).isNull();
+ assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();
// Just need to execute something so that a connection is opened
String name = client.listDatabaseNames().first();
- assertEquals(1L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags()));
- assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags()));
+ assertThat(getMetric("mongodb.driver.pool.size")).isOne();
+ assertThat(getMetric("mongodb.driver.commands")).isOne();
+ assertThat(getMetric("mongodb.driver.pool.checkedout")).isZero();
client.close();
- assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags()));
- assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags()));
+ assertThat(getMetric("mongodb.driver.pool.size")).isNull();
+ assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull();
// doing this here instead of in another method in order to avoid messing with the initialization stats
assertThat(Arc.container().instance(MongoClient.class).get()).isNotNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull();
}
- private Long getGaugeValueOrNull(String metricName, Tag[] tags) {
- MetricID metricID = new MetricID(metricName, tags);
- Metric metric = registry.getMetrics().get(metricID);
-
- if (metric == null) {
- return null;
- }
- return ((ConnectionPoolGauge) metric).getValue();
+ private Double getMetric(String metricName) {
+ Meter metric = meterRegistry.getMeters()
+ .stream()
+ .filter(mtr -> mtr.getId().getName().contains(metricName))
+ .findFirst()
+ .orElse(null);
+ return metric == null ? null : metric.measure().iterator().next().getValue();
}
- private Tag[] getTags() {
- return new Tag[] {
- new Tag("host", "127.0.0.1"),
- new Tag("port", "27018"),
- };
- }
}
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java
new file mode 100644
index 0000000000000..ad437dc2dc882
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java
@@ -0,0 +1,59 @@
+package io.quarkus.mongodb.deployment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import java.util.Optional;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.mockito.ArgumentCaptor;
+
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
+import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
+import io.quarkus.runtime.metrics.MetricsFactory;
+
+class MongoClientProcessorTest {
+ private final MongoClientProcessor buildStep = new MongoClientProcessor();
+
+ @SuppressWarnings("unchecked")
+ @ParameterizedTest
+ @CsvSource({
+ "true, true, true", // Metrics enabled and Micrometer supported
+ "true, false, false", // Metrics enabled but Micrometer not supported
+ "false, true, false", // Metrics disabled and Micrometer supported
+ "false, false, false" // Metrics disabled and Micrometer not supported
+ })
+ void testIncludeMongoCommandMetricListener(boolean metricsEnabled, boolean micrometerSupported, boolean expectedResult) {
+ MongoClientBuildTimeConfig config = config(metricsEnabled);
+ Optional capability = capability(metricsEnabled, micrometerSupported);
+
+ BuildProducer buildProducer = mock(BuildProducer.class);
+ buildStep.includeMongoCommandMetricListener(buildProducer, config, capability);
+
+ if (expectedResult) {
+ var captor = ArgumentCaptor.forClass(AdditionalIndexedClassesBuildItem.class);
+ verify(buildProducer, times(1)).produce(captor.capture());
+ assertThat(captor.getAllValues().get(0).getClassesToIndex())
+ .containsExactly("io.quarkus.mongodb.metrics.MicrometerCommandListener");
+ } else {
+ verify(buildProducer, never()).produce(any(AdditionalIndexedClassesBuildItem.class));
+ }
+ }
+
+ private static Optional capability(boolean metricsEnabled, boolean micrometerSupported) {
+ MetricsCapabilityBuildItem capability = metricsEnabled
+ ? new MetricsCapabilityBuildItem(factory -> MetricsFactory.MICROMETER.equals(factory) && micrometerSupported)
+ : null;
+ return Optional.ofNullable(capability);
+ }
+
+ private static MongoClientBuildTimeConfig config(boolean metricsEnabled) {
+ MongoClientBuildTimeConfig buildTimeConfig = new MongoClientBuildTimeConfig();
+ buildTimeConfig.metricsEnabled = metricsEnabled;
+ return buildTimeConfig;
+ }
+
+}
diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java
new file mode 100644
index 0000000000000..4ee642491a9c7
--- /dev/null
+++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java
@@ -0,0 +1,14 @@
+package io.quarkus.mongodb.metrics;
+
+import jakarta.inject.Inject;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener;
+
+public class MicrometerCommandListener extends MongoMetricsCommandListener {
+ @Inject
+ public MicrometerCommandListener(MeterRegistry registry) {
+ super(registry);
+ }
+
+}
diff --git a/extensions/oidc-client-registration/deployment/src/main/java/io/quarkus/oidc/client/registration/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java b/extensions/oidc-client-registration/deployment/src/main/java/io/quarkus/oidc/client/registration/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
index 5ba594617a5b6..bff9cd44584b0 100644
--- a/extensions/oidc-client-registration/deployment/src/main/java/io/quarkus/oidc/client/registration/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
+++ b/extensions/oidc-client-registration/deployment/src/main/java/io/quarkus/oidc/client/registration/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
@@ -1,7 +1,5 @@
package io.quarkus.oidc.client.registration.deployment.devservices.keycloak;
-import static io.quarkus.devservices.keycloak.KeycloakDevServicesRequiredBuildItem.OIDC_AUTH_SERVER_URL_CONFIG_KEY;
-
import java.util.List;
import java.util.Map;
@@ -55,8 +53,7 @@ public void customizeDefaultRealm(RealmRepresentation realmRepresentation) {
}
};
- return KeycloakDevServicesRequiredBuildItem.of(devServicesConfigurator,
- OIDC_CLIENT_REG_AUTH_SERVER_URL_CONFIG_KEY, OIDC_AUTH_SERVER_URL_CONFIG_KEY);
+ return KeycloakDevServicesRequiredBuildItem.of(devServicesConfigurator, OIDC_CLIENT_REG_AUTH_SERVER_URL_CONFIG_KEY);
}
@BuildStep(onlyIf = IsDevelopment.class)
diff --git a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
index 099afd16de833..5ca43f77ad4bd 100644
--- a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
+++ b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/devservices/keycloak/KeycloakDevServiceRequiredBuildStep.java
@@ -1,7 +1,5 @@
package io.quarkus.oidc.client.deployment.devservices.keycloak;
-import static io.quarkus.devservices.keycloak.KeycloakDevServicesRequiredBuildItem.OIDC_AUTH_SERVER_URL_CONFIG_KEY;
-
import java.util.HashMap;
import io.quarkus.deployment.IsDevelopment;
@@ -35,7 +33,7 @@ KeycloakDevServicesRequiredBuildItem requireKeycloakDevService(KeycloakDevServic
configProperties.put(OIDC_CLIENT_SECRET_CONFIG_KEY, ctx.oidcClientSecret());
}
return configProperties;
- }, OIDC_CLIENT_AUTH_SERVER_URL_CONFIG_KEY, OIDC_CLIENT_TOKEN_PATH_CONFIG_KEY, OIDC_AUTH_SERVER_URL_CONFIG_KEY);
+ }, OIDC_CLIENT_AUTH_SERVER_URL_CONFIG_KEY, OIDC_CLIENT_TOKEN_PATH_CONFIG_KEY);
}
@BuildStep(onlyIf = IsDevelopment.class)
diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/NamedOidcClientInjectionTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/NamedOidcClientInjectionTestCase.java
index e06687718effd..a164f65178c57 100644
--- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/NamedOidcClientInjectionTestCase.java
+++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/NamedOidcClientInjectionTestCase.java
@@ -9,7 +9,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import io.quarkus.oidc.runtime.OidcUtils;
+import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.QuarkusTestResource;
import io.restassured.RestAssured;
@@ -48,7 +48,7 @@ private void validateTokens(String token1, String token2) {
}
private String preferredUserOf(String token) {
- return OidcUtils.decodeJwtContent(token).getString("preferred_username");
+ return OidcCommonUtils.decodeJwtContent(token).getString("preferred_username");
}
private String doTestGetTokenByNamedClient(String clientId) {
diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceStartupTest.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceStartupTest.java
new file mode 100644
index 0000000000000..bbb6243429eda
--- /dev/null
+++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceStartupTest.java
@@ -0,0 +1,33 @@
+package io.quarkus.oidc.client;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+/**
+ * Test Keycloak Dev Service is not started when known social provider is configured
+ * in Quarkus OIDC extension.
+ */
+public class OidcClientKeycloakDevServiceStartupTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest test = new QuarkusUnitTest()
+ .withApplicationRoot(jar -> jar
+ .addAsResource(new StringAsset("""
+ quarkus.oidc.provider=slack
+ quarkus.oidc.client-id=irrelevant-client-id
+ """), "application.properties"))
+ .setLogRecordPredicate(logRecord -> logRecord != null && logRecord.getMessage() != null
+ && logRecord.getMessage().contains("Dev Services for Keycloak started"))
+ .assertLogRecords(logRecords -> assertTrue(logRecords.isEmpty()));
+
+ @Test
+ public void testDevServiceNotStarted() {
+ // needs to be here so that log asserter runs after all tests
+ }
+
+}
diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java
index 8a2e94cb42caa..16895e0e833e2 100644
--- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java
+++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java
@@ -13,7 +13,7 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.oidc.client.runtime.OidcClientsConfig;
-import io.quarkus.oidc.runtime.OidcUtils;
+import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
@@ -67,7 +67,7 @@ private void validateTokens(String token1, String token2) {
}
private String upn(String token) {
- return OidcUtils.decodeJwtContent(token).getString("upn");
+ return OidcCommonUtils.decodeJwtContent(token).getString("upn");
}
private String doTestGetTokenByNamedClient(String clientId) {
diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
index 3b820603f7d3d..04f0bc32c11cc 100644
--- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
+++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
@@ -22,11 +22,13 @@
import io.quarkus.oidc.common.OidcRequestFilter;
import io.quarkus.oidc.common.OidcRequestFilter.OidcRequestContext;
import io.quarkus.oidc.common.OidcResponseFilter;
+import io.quarkus.oidc.common.runtime.ClientAssertionProvider;
import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.Jwt.Source;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.groups.UniOnItem;
+import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;
@@ -55,12 +57,13 @@ public class OidcClientImpl implements OidcClient {
private final OidcClientConfig oidcConfig;
private final Map> requestFilters;
private final Map> responseFilters;
+ private final ClientAssertionProvider clientAssertionProvider;
private volatile boolean closed;
- public OidcClientImpl(WebClient client, String tokenRequestUri, String tokenRevokeUri, String grantType,
+ OidcClientImpl(WebClient client, String tokenRequestUri, String tokenRevokeUri, String grantType,
MultiMap tokenGrantParams, MultiMap commonRefreshGrantParams, OidcClientConfig oidcClientConfig,
Map> requestFilters,
- Map> responseFilters) {
+ Map> responseFilters, Vertx vertx) {
this.client = client;
this.tokenRequestUri = tokenRequestUri;
this.tokenRevokeUri = tokenRevokeUri;
@@ -73,6 +76,16 @@ public OidcClientImpl(WebClient client, String tokenRequestUri, String tokenRevo
this.clientSecretBasicAuthScheme = OidcCommonUtils.initClientSecretBasicAuth(oidcClientConfig);
this.jwtBearerAuthentication = oidcClientConfig.credentials().jwt().source() == Source.BEARER;
this.clientJwtKey = jwtBearerAuthentication ? null : OidcCommonUtils.initClientJwtKey(oidcClientConfig, false);
+ if (jwtBearerAuthentication && oidcClientConfig.credentials().jwt().tokenPath().isPresent()) {
+ this.clientAssertionProvider = new ClientAssertionProvider(vertx,
+ oidcClientConfig.credentials().jwt().tokenPath().get());
+ if (this.clientAssertionProvider.getClientAssertion() == null) {
+ throw new OidcClientException("Cannot find a valid JWT bearer token at path: "
+ + oidcClientConfig.credentials().jwt().tokenPath().get());
+ }
+ } else {
+ this.clientAssertionProvider = null;
+ }
}
@Override
@@ -177,7 +190,14 @@ private UniOnItem> postRequest(
if (clientSecretBasicAuthScheme != null) {
request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme);
} else if (jwtBearerAuthentication) {
- if (!additionalGrantParameters.containsKey(OidcConstants.CLIENT_ASSERTION)) {
+ String clientAssertion = additionalGrantParameters.get(OidcConstants.CLIENT_ASSERTION);
+ if (clientAssertion == null && clientAssertionProvider != null) {
+ clientAssertion = clientAssertionProvider.getClientAssertion();
+ if (clientAssertion != null) {
+ body.add(OidcConstants.CLIENT_ASSERTION, clientAssertion);
+ }
+ }
+ if (clientAssertion == null) {
String errorMessage = String.format(
"%s OidcClient can not complete the %s grant request because a JWT bearer client_assertion is missing",
oidcConfig.id().get(), (refresh ? OidcConstants.REFRESH_TOKEN_GRANT : grantType));
@@ -319,6 +339,9 @@ private static MultiMap copyMultiMap(MultiMap oldMap) {
public void close() throws IOException {
if (!closed) {
client.close();
+ if (clientAssertionProvider != null) {
+ clientAssertionProvider.close();
+ }
closed = true;
}
}
diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java
index 5b9c1e6f64faf..86603aeba0015 100644
--- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java
+++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java
@@ -218,11 +218,8 @@ public OidcClient apply(OidcConfigurationMetadata metadata, Throwable t) {
setGrantClientParams(oidcConfig, commonRefreshGrantParams, OidcConstants.REFRESH_TOKEN_GRANT);
return new OidcClientImpl(client, metadata.tokenRequestUri, metadata.tokenRevokeUri, grantType,
- tokenGrantParams,
- commonRefreshGrantParams,
- oidcConfig,
- oidcRequestFilters,
- oidcResponseFilters);
+ tokenGrantParams, commonRefreshGrantParams, oidcConfig, oidcRequestFilters,
+ oidcResponseFilters, vertx.get());
}
});
diff --git a/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigBuilderTest.java b/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigBuilderTest.java
index e32ffb2873c44..5de44331d95f8 100644
--- a/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigBuilderTest.java
+++ b/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigBuilderTest.java
@@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@@ -84,6 +85,7 @@ public void testDefaultValues() {
assertTrue(jwt.signatureAlgorithm().isEmpty());
assertEquals(10, jwt.lifespan());
assertFalse(jwt.assertion());
+ assertFalse(jwt.tokenPath().isPresent());
// OidcCommonConfig methods
assertTrue(config.authServerUrl().isEmpty());
@@ -154,6 +156,7 @@ public void testSetEveryProperty() {
.end()
.jwt()
.source(Source.BEARER)
+ .tokenPath(Path.of("janitor"))
.secretProvider()
.keyringName("jwt-keyring-name-yep")
.key("jwt-key-yep")
@@ -249,6 +252,7 @@ public void testSetEveryProperty() {
assertNotNull(jwt);
assertEquals(Source.BEARER, jwt.source());
assertEquals("jwt-secret-yep", jwt.secret().orElse(null));
+ assertEquals("janitor", jwt.tokenPath().map(Path::toString).orElse(null));
provider = jwt.secretProvider();
assertNotNull(provider);
assertEquals("jwt-keyring-name-yep", provider.keyringName().orElse(null));
@@ -460,6 +464,7 @@ public void testCopyOidcClientCommonConfigProperties() {
.end()
.jwt()
.source(Source.BEARER)
+ .tokenPath(Path.of("robot"))
.secretProvider()
.keyringName("jwt-keyring-name-yep")
.key("jwt-key-yep")
@@ -507,6 +512,7 @@ public void testCopyOidcClientCommonConfigProperties() {
assertNotNull(jwt);
assertEquals(Source.BEARER, jwt.source());
assertEquals("jwt-secret-yep", jwt.secret().orElse(null));
+ assertEquals("robot", jwt.tokenPath().map(Path::toString).orElse(null));
provider = jwt.secretProvider();
assertNotNull(provider);
assertEquals("jwt-keyring-name-yep", provider.keyringName().orElse(null));
diff --git a/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigImpl.java b/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigImpl.java
index b786c83e4237a..895e1478a1053 100644
--- a/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigImpl.java
+++ b/extensions/oidc-client/runtime/src/test/java/io/quarkus/oidc/client/OidcClientConfigImpl.java
@@ -89,7 +89,8 @@ enum ConfigMappingMethods {
CREDENTIALS_JWT_LIFESPAN,
CREDENTIALS_JWT_ASSERTION,
CREDENTIALS_JWT_AUDIENCE,
- CREDENTIALS_JWT_TOKEN_ID
+ CREDENTIALS_JWT_TOKEN_ID,
+ JWT_BEARER_TOKEN_PATH
}
final Map invocationsRecorder = new EnumMap<>(ConfigMappingMethods.class);
@@ -182,6 +183,12 @@ public Source source() {
return Source.BEARER;
}
+ @Override
+ public Optional tokenPath() {
+ invocationsRecorder.put(ConfigMappingMethods.JWT_BEARER_TOKEN_PATH, true);
+ return Optional.empty();
+ }
+
@Override
public Optional secret() {
invocationsRecorder.put(ConfigMappingMethods.CREDENTIALS_JWT_SECRET, true);
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java
index eda5b00cd66d3..45c0918cc7bef 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/OidcRequestContextProperties.java
@@ -1,6 +1,7 @@
package io.quarkus.oidc.common;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
public class OidcRequestContextProperties {
@@ -16,7 +17,7 @@ public OidcRequestContextProperties() {
}
public OidcRequestContextProperties(Map properties) {
- this.properties = properties;
+ this.properties = new HashMap<>(properties);
}
/**
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/ClientAssertionProvider.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/ClientAssertionProvider.java
new file mode 100644
index 0000000000000..f5b8cd0a29d63
--- /dev/null
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/ClientAssertionProvider.java
@@ -0,0 +1,109 @@
+package io.quarkus.oidc.common.runtime;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.jboss.logging.Logger;
+
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+
+public final class ClientAssertionProvider implements Closeable {
+
+ private record ClientAssertion(String bearerToken, long expiresAt, long timerId) {
+ private boolean isInvalid() {
+ final long nowSecs = System.currentTimeMillis() / 1000;
+ return nowSecs > expiresAt;
+ }
+ }
+
+ private static final Logger LOG = Logger.getLogger(ClientAssertionProvider.class);
+ private final Vertx vertx;
+ private final Path bearerTokenPath;
+ private volatile ClientAssertion clientAssertion;
+
+ public ClientAssertionProvider(Vertx vertx, Path bearerTokenPath) {
+ this.vertx = vertx;
+ this.bearerTokenPath = bearerTokenPath;
+ this.clientAssertion = loadFromFileSystem();
+ }
+
+ public String getClientAssertion() {
+ ClientAssertion clientAssertion = this.clientAssertion;
+ if (isInvalid(clientAssertion)) {
+ clientAssertion = loadClientAssertion();
+ }
+ return clientAssertion == null ? null : clientAssertion.bearerToken;
+ }
+
+ @Override
+ public void close() {
+ cancelRefresh();
+ clientAssertion = null;
+ }
+
+ private synchronized ClientAssertion loadClientAssertion() {
+ if (isInvalid(clientAssertion)) {
+ cancelRefresh();
+ clientAssertion = loadFromFileSystem();
+ }
+ return clientAssertion;
+ }
+
+ private long scheduleRefresh(long expiresAt) {
+ // in K8 and OCP, tokens are proactively rotated at 80 % of their TTL
+ long delay = (long) (expiresAt * 0.85);
+ return vertx.setTimer(delay, new Handler() {
+ @Override
+ public void handle(Long ignored) {
+ ClientAssertionProvider.this.clientAssertion = loadFromFileSystem();
+ }
+ });
+ }
+
+ private void cancelRefresh() {
+ if (clientAssertion != null) {
+ vertx.cancelTimer(clientAssertion.timerId);
+ }
+ }
+
+ private ClientAssertion loadFromFileSystem() {
+ if (Files.exists(bearerTokenPath)) {
+ try {
+ String bearerToken = Files.readString(bearerTokenPath).trim();
+ Long expiresAt = getExpiresAtFromExpClaim(bearerToken);
+ if (expiresAt != null) {
+ return new ClientAssertion(bearerToken, expiresAt, scheduleRefresh(expiresAt));
+ } else {
+ LOG.error("Bearer token or its expiry claim is invalid");
+ }
+ } catch (IOException e) {
+ LOG.error("Failed to read file with a bearer token at path: " + bearerTokenPath, e);
+ }
+ } else {
+ LOG.warn("Cannot find a file with a bearer token at path: " + bearerTokenPath);
+ }
+ return null;
+ }
+
+ private static boolean isInvalid(ClientAssertion clientAssertion) {
+ return clientAssertion == null || clientAssertion.isInvalid();
+ }
+
+ private static Long getExpiresAtFromExpClaim(String bearerToken) {
+ JsonObject claims = OidcCommonUtils.decodeJwtContent(bearerToken);
+ if (claims == null || !claims.containsKey(Claims.exp.name())) {
+ return null;
+ }
+ try {
+ return claims.getLong(Claims.exp.name());
+ } catch (IllegalArgumentException ex) {
+ LOG.debug("Bearer token expiry claim can not be converted to Long");
+ return null;
+ }
+ }
+}
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java
index c7fa4e77f4882..07b7e6b61afc5 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcClientCommonConfig.java
@@ -1,13 +1,10 @@
package io.quarkus.oidc.common.runtime;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-/**
- * @deprecated use the {@link io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig} interface instead
- */
-@Deprecated(since = "3.18")
public abstract class OidcClientCommonConfig extends OidcCommonConfig
implements io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig {
@@ -283,6 +280,11 @@ public io.quarkus.oidc.common.runtime.config.OidcClientCommonConfig.Credentials.
.valueOf(source.toString());
}
+ @Override
+ public Optional tokenPath() {
+ return tokenPath;
+ }
+
@Override
public Optional secret() {
return secret;
@@ -363,6 +365,8 @@ public boolean assertion() {
return assertion;
}
+ private Optional tokenPath = Optional.empty();
+
public static enum Source {
// JWT token is generated by the OIDC provider client to support
// `client_secret_jwt` and `private_key_jwt` authentication methods
@@ -578,6 +582,7 @@ private void addConfigMappingValues(
signatureAlgorithm = mapping.signatureAlgorithm();
lifespan = mapping.lifespan();
assertion = mapping.assertion();
+ tokenPath = mapping.tokenPath();
}
}
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
index 978c116d80827..705f23329a0d2 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java
@@ -5,10 +5,6 @@
import java.util.Optional;
import java.util.OptionalInt;
-/**
- * @deprecated use the {@link io.quarkus.oidc.common.runtime.config.OidcCommonConfig} interface instead
- */
-@Deprecated(since = "3.18")
public abstract class OidcCommonConfig implements io.quarkus.oidc.common.runtime.config.OidcCommonConfig {
public OidcCommonConfig() {
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
index 3be6c684f063d..224ad6af5ab78 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
@@ -24,6 +24,7 @@
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
+import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -121,6 +122,14 @@ public static void verifyCommonConfiguration(OidcClientCommonConfig oidcConfig,
"Use only '%1$scredentials.secret' or '%1$scredentials.client-secret' or '%1$scredentials.jwt.secret' property",
configPrefix));
}
+ Credentials.Jwt jwt = creds.jwt();
+ if (jwt.source() == Credentials.Jwt.Source.BEARER) {
+ if (isServerConfig && jwt.tokenPath().isEmpty()) {
+ throw new ConfigurationException("Bearer token path must be set when the JWT source is a bearer token");
+ }
+ } else if (jwt.tokenPath().isPresent()) {
+ throw new ConfigurationException("Bearer token path can only be set when the JWT source is a bearer token");
+ }
}
public static String prependSlash(String path) {
@@ -743,4 +752,41 @@ public Uni extends HttpResponse> apply(Void unused) {
return request.send();
}
}
+
+ public static JsonObject decodeJwtContent(String jwt) {
+ String encodedContent = getJwtContentPart(jwt);
+ if (encodedContent == null) {
+ return null;
+ }
+ return decodeAsJsonObject(encodedContent);
+ }
+
+ public static String getJwtContentPart(String jwt) {
+ StringTokenizer tokens = new StringTokenizer(jwt, ".");
+ // part 1: skip the token headers
+ tokens.nextToken();
+ if (!tokens.hasMoreTokens()) {
+ return null;
+ }
+ // part 2: token content
+ String encodedContent = tokens.nextToken();
+
+ // let's check only 1 more signature part is available
+ if (tokens.countTokens() != 1) {
+ return null;
+ }
+ return encodedContent;
+ }
+
+ public static String base64UrlDecode(String encodedContent) {
+ return new String(Base64.getUrlDecoder().decode(encodedContent), StandardCharsets.UTF_8);
+ }
+
+ public static JsonObject decodeAsJsonObject(String encodedContent) {
+ try {
+ return new JsonObject(base64UrlDecode(encodedContent));
+ } catch (IllegalArgumentException ex) {
+ return null;
+ }
+ }
}
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfig.java
index 07318e9f4c18a..58b02bdf81ec0 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfig.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfig.java
@@ -1,5 +1,6 @@
package io.quarkus.oidc.common.runtime.config;
+import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
@@ -132,11 +133,14 @@ enum Method {
interface Jwt {
enum Source {
- // JWT token is generated by the OIDC provider client to support
- // `client_secret_jwt` and `private_key_jwt` authentication methods
+ /**
+ * JWT token is generated by the OIDC provider client to support
+ * `client_secret_jwt` and `private_key_jwt` authentication methods.
+ */
CLIENT,
- // JWT bearer token as used as a client assertion: https://www.rfc-editor.org/rfc/rfc7523#section-2.2
- // This option is only supported by the OIDC client extension.
+ /**
+ * JWT bearer token is used as a client assertion: https://www.rfc-editor.org/rfc/rfc7523#section-2.2.
+ */
BEARER
}
@@ -146,6 +150,12 @@ enum Source {
@WithDefault("client")
Source source();
+ /**
+ * Path to a file with a JWT bearer token that should be used as a client assertion.
+ * This path can only be set when JWT source ({@link #source()}) is set to {@link Source#BEARER}.
+ */
+ Optional tokenPath();
+
/**
* If provided, indicates that JWT is signed using a secret key.
* It is mutually exclusive with {@link #key}, {@link #keyFile} and {@link #keyStore} properties.
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfigBuilder.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfigBuilder.java
index 3e3112d0bd858..643fa456cae2e 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfigBuilder.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/config/OidcClientCommonConfigBuilder.java
@@ -1,5 +1,6 @@
package io.quarkus.oidc.common.runtime.config;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -472,7 +473,8 @@ private record JwtImpl(Source source, Optional secret, Provider secretPr
Optional keyFile, Optional keyStoreFile, Optional keyStorePassword,
Optional keyId, Optional keyPassword, Optional audience, Optional tokenKeyId,
Optional issuer, Optional subject, Map claims,
- Optional signatureAlgorithm, int lifespan, boolean assertion) implements Jwt {
+ Optional signatureAlgorithm, int lifespan, boolean assertion,
+ Optional tokenPath) implements Jwt {
}
@@ -492,6 +494,7 @@ private record JwtImpl(Source source, Optional secret, Provider secretPr
private Optional issuer;
private Optional subject;
private Optional signatureAlgorithm;
+ private Optional