From 14535b1406ae25b216b618974f321335ca58e175 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Dec 2024 14:21:38 +0100 Subject: [PATCH 01/22] Revert "Bump org.mariadb.jdbc:mariadb-java-client from 3.4.1 to 3.5.0" This reverts commit bc38e9348cfc2496d2a43d9fb28e7e100276c014. (cherry picked from commit 1b2e53b9d3ecdf0993e8595c43b318ae449ce067) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index ae106d00f59c0..9de128459dfab 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -119,7 +119,7 @@ 2.3.230 42.7.4 - 3.5.0 + 3.4.1 8.3.0 12.8.1.jre11 1.6.7 From fadd689c797904249f19ddbb00d9db917baafdee Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 10 Dec 2024 19:38:30 +0000 Subject: [PATCH 02/22] Priority of REST Client Config changed to Quarkus FQN, config key, simple name (cherry picked from commit 79e2beb1ca15356b34fb10734b4b701e435f8c3c) --- .../AbstractRestClientConfigBuilder.java | 182 +++++++++++++----- .../config/RestClientConfigTest.java | 78 ++++++++ .../key/SharedOneConfigKeyRestClient.java | 7 + .../key/SharedThreeConfigKeyRestClient.java | 7 + .../key/SharedTwoConfigKeyRestClient.java | 7 + .../simple/one/SimpleNameRestClient.java | 7 + .../simple/two/SimpleNameRestClient.java | 7 + .../src/test/resources/application.properties | 9 + 8 files changed, 255 insertions(+), 49 deletions(-) create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedOneConfigKeyRestClient.java create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedThreeConfigKeyRestClient.java create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedTwoConfigKeyRestClient.java create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/one/SimpleNameRestClient.java create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/two/SimpleNameRestClient.java diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java index a4e2e0f7bcdbc..7ed5887381a76 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java @@ -2,11 +2,14 @@ import static io.smallrye.config.ConfigValue.CONFIG_SOURCE_COMPARATOR; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.OptionalInt; import java.util.function.Function; +import java.util.function.Supplier; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -17,7 +20,6 @@ import io.smallrye.config.ConfigValue; import io.smallrye.config.FallbackConfigSourceInterceptor; import io.smallrye.config.Priorities; -import io.smallrye.config.RelocateConfigSourceInterceptor; import io.smallrye.config.SmallRyeConfigBuilder; /** @@ -70,49 +72,66 @@ public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) Map quarkusFallbacks = new HashMap<>(); Map microProfileFallbacks = new HashMap<>(); - // relocates [All Combinations] -> quarkus.rest-client."FQN".* - Map relocates = new HashMap<>(); + Map> relocates = new HashMap<>(); + for (RegisteredRestClient restClient : restClients) { if (runtime) { RestClientsConfig.RestClientKeysProvider.KEYS.add(restClient.getFullName()); } - // FQN -> Simple Name String quotedFullName = "\"" + restClient.getFullName() + "\""; - quarkusFallbacks.put(quotedFullName, restClient.getSimpleName()); - relocates.put(restClient.getSimpleName(), quotedFullName); - // Simple Name -> Quoted Simple Name String quotedSimpleName = "\"" + restClient.getSimpleName() + "\""; - quarkusFallbacks.put(restClient.getSimpleName(), quotedSimpleName); - relocates.put(quotedSimpleName, quotedFullName); - String configKey = restClient.getConfigKey(); + + // relocates [All Combinations] -> quarkus.rest-client."FQN".* + List fullNameRelocates = new ArrayList<>(); + relocates.put(quotedFullName, fullNameRelocates); + if (configKey != null && !restClient.isConfigKeyEqualsNames()) { String quotedConfigKey = "\"" + configKey + "\""; if (!quotedConfigKey.equals(quotedFullName) && !quotedConfigKey.equals(quotedSimpleName)) { if (restClient.isConfigKeyComposed()) { - // Quoted Simple Name -> Quoted Config Key - quarkusFallbacks.put(quotedSimpleName, quotedConfigKey); - relocates.put(quotedConfigKey, quotedFullName); + // FQN -> Quoted Config Key -> Quoted Simple Name -> Simple Name + quarkusFallbacks.put(quotedFullName, quotedConfigKey); + quarkusFallbacks.put(quotedConfigKey, restClient.getSimpleName()); + quarkusFallbacks.put(restClient.getSimpleName(), quotedSimpleName); + fullNameRelocates.add(quotedConfigKey); + fullNameRelocates.add(restClient.getSimpleName()); + fullNameRelocates.add(quotedSimpleName); } else { - // Quoted Simple Name -> Config Key - quarkusFallbacks.put(quotedSimpleName, configKey); - relocates.put(configKey, quotedFullName); - // Config Key -> Quoted Config Key + // FQN -> Config Key -> Quoted Config Key -> Quoted Simple Name -> Simple Name + quarkusFallbacks.put(quotedFullName, configKey); quarkusFallbacks.put(configKey, quotedConfigKey); - relocates.put(quotedConfigKey, quotedFullName); + quarkusFallbacks.put(quotedConfigKey, restClient.getSimpleName()); + quarkusFallbacks.put(restClient.getSimpleName(), quotedSimpleName); + fullNameRelocates.add(configKey); + fullNameRelocates.add(quotedConfigKey); + fullNameRelocates.add(restClient.getSimpleName()); + fullNameRelocates.add(quotedSimpleName); } + } else { + // FQN -> Quoted Simple Name -> Simple Name + quarkusFallbacks.put(quotedFullName, restClient.getSimpleName()); + quarkusFallbacks.put(restClient.getSimpleName(), quotedSimpleName); + fullNameRelocates.add(restClient.getSimpleName()); + fullNameRelocates.add(quotedSimpleName); } + } else { + // FQN -> Quoted Simple Name -> Simple Name + quarkusFallbacks.put(quotedFullName, restClient.getSimpleName()); + quarkusFallbacks.put(restClient.getSimpleName(), quotedSimpleName); + fullNameRelocates.add(restClient.getSimpleName()); + fullNameRelocates.add(quotedSimpleName); } // FQN -> FQN/mp-rest String mpRestFullName = restClient.getFullName() + "/mp-rest/"; microProfileFallbacks.put(quotedFullName, mpRestFullName); - relocates.put(mpRestFullName, quotedFullName); + fullNameRelocates.add(mpRestFullName); if (configKey != null && !configKey.equals(restClient.getFullName())) { String mpConfigKey = configKey + "/mp-rest/"; microProfileFallbacks.put(mpRestFullName, mpConfigKey); - relocates.put(mpConfigKey, quotedFullName); + fullNameRelocates.add(mpConfigKey); } } @@ -145,12 +164,7 @@ public OptionalInt getPriority() { builder.withInterceptorFactories(new ConfigSourceInterceptorFactory() { @Override public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) { - return new RelocateConfigSourceInterceptor(new Relocates(relocates)) { - @Override - public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { - return context.proceed(name); - } - }; + return new Relocates(relocates); } }); return builder; @@ -298,35 +312,105 @@ public String apply(final String name) { /** * Relocates every possible name (Quarkus and MP) to - * quarkus.rest-client."[FQN of the REST Interface]".* + * quarkus.rest-client."[FQN of the REST Interface]".*. The same configKey can relocate + * to many quarkus.rest-client."[FQN of the REST Interface]".*. */ - private record Relocates(Map names) implements Function { + private static class Relocates implements ConfigSourceInterceptor { + private final Map> relocates; + + Relocates(final Map> relocates) { + this.relocates = new HashMap<>(); + // Inverts the Map to make it easier to search + for (Map.Entry> entry : relocates.entrySet()) { + for (String from : entry.getValue()) { + this.relocates.putIfAbsent(from, new ArrayList<>()); + this.relocates.get(from).add(entry.getKey()); + } + } + } + @Override - public String apply(final String name) { - int indexOfRestClient = indexOfRestClient(name); - if (indexOfRestClient != -1) { - for (Map.Entry entry : names.entrySet()) { - String original = entry.getKey(); - String target = entry.getValue(); - int endOfConfigKey = indexOfRestClient + original.length(); - if (name.regionMatches(indexOfRestClient, original, 0, original.length())) { - if (name.length() > endOfConfigKey && name.charAt(endOfConfigKey) == '.') { - return REST_CLIENT_PREFIX + target + name.substring(endOfConfigKey); + public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { + return context.proceed(name); + } + + @Override + public Iterator iterateNames(final ConfigSourceInterceptorContext context) { + List relocatedNames = new ArrayList<>(relocates.size()); + List>> iterators = new ArrayList<>(); + iterators.add(new Supplier>() { + @Override + public Iterator get() { + Iterator names = context.iterateNames(); + + return new Iterator<>() { + @Override + public boolean hasNext() { + return names.hasNext(); } - } + + @Override + public String next() { + String name = names.next(); + int indexOfRestClient = indexOfRestClient(name); + if (indexOfRestClient != -1) { + for (Map.Entry> entry : relocates.entrySet()) { + String original = entry.getKey(); + int endOfConfigKey = indexOfRestClient + original.length(); + if (name.regionMatches(indexOfRestClient, original, 0, original.length())) { + if (name.length() > endOfConfigKey && name.charAt(endOfConfigKey) == '.') { + for (String relocatedName : entry.getValue()) { + relocatedNames.add( + REST_CLIENT_PREFIX + relocatedName + name.substring(endOfConfigKey)); + } + return name; + } + } + } + } + int slash = name.indexOf("/"); + if (slash != -1) { + if (name.regionMatches(slash + 1, "mp-rest/", 0, 8)) { + String property = name.substring(slash + 9); + if (MICROPROFILE_NAMES.containsKey(property)) { + relocatedNames.add(REST_CLIENT_PREFIX + "\"" + name.substring(0, slash) + "\"." + + MICROPROFILE_NAMES.getOrDefault(property, property)); + } + return name; + } + } + return name; + } + }; } - } - int slash = name.indexOf("/"); - if (slash != -1) { - if (name.regionMatches(slash + 1, "mp-rest/", 0, 8)) { - String property = name.substring(slash + 9); - if (MICROPROFILE_NAMES.containsKey(property)) { - return REST_CLIENT_PREFIX + "\"" + name.substring(0, slash) + "\"." - + MICROPROFILE_NAMES.getOrDefault(property, property); + }); + iterators.add(new Supplier>() { + @Override + public Iterator get() { + return relocatedNames.iterator(); + } + }); + Iterator>> iterator = iterators.iterator(); + return new Iterator<>() { + Iterator names = iterator.next().get(); + + @Override + public boolean hasNext() { + if (names.hasNext()) { + return true; + } else { + if (iterator.hasNext()) { + names = iterator.next().get(); + } + return names.hasNext(); } } - } - return name; + + @Override + public String next() { + return names.next(); + } + }; } } diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java index 2a8c2621726c5..7b73ed5ae3942 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java @@ -13,6 +13,9 @@ import org.junit.jupiter.api.Test; import io.quarkus.restclient.config.RestClientsConfig.RestClientConfig; +import io.quarkus.restclient.config.key.SharedOneConfigKeyRestClient; +import io.quarkus.restclient.config.key.SharedThreeConfigKeyRestClient; +import io.quarkus.restclient.config.key.SharedTwoConfigKeyRestClient; import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; @@ -233,6 +236,81 @@ public List getRestClients() { assertThat(restClientConfig.uri().get()).isEqualTo("http://localhost:8082"); } + @Test + void restClientDuplicateSimpleName() { + SmallRyeConfig config = ConfigUtils.emptyConfigBuilder() + .withMapping(RestClientsConfig.class) + .withCustomizers(new SmallRyeConfigBuilderCustomizer() { + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + new AbstractRestClientConfigBuilder() { + @Override + public List getRestClients() { + return List.of( + new RegisteredRestClient( + io.quarkus.restclient.config.simple.one.SimpleNameRestClient.class, "one"), + new RegisteredRestClient( + io.quarkus.restclient.config.simple.two.SimpleNameRestClient.class, "two")); + } + }.configBuilder(builder); + } + }) + .build(); + assertNotNull(config); + + RestClientsConfig restClientsConfig = config.getConfigMapping(RestClientsConfig.class); + assertEquals(2, restClientsConfig.clients().size()); + assertThat(restClientsConfig.getClient(io.quarkus.restclient.config.simple.one.SimpleNameRestClient.class).uri().get()) + .isEqualTo("http://localhost:8081"); + assertThat(restClientsConfig.getClient(io.quarkus.restclient.config.simple.two.SimpleNameRestClient.class).uri().get()) + .isEqualTo("http://localhost:8082"); + } + + @Test + void restClientSharedConfigKey() { + SmallRyeConfig config = ConfigUtils.emptyConfigBuilder() + .withMapping(RestClientsConfig.class) + .withCustomizers(new SmallRyeConfigBuilderCustomizer() { + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + new AbstractRestClientConfigBuilder() { + @Override + public List getRestClients() { + return List.of( + new RegisteredRestClient(SharedOneConfigKeyRestClient.class, "shared"), + new RegisteredRestClient(SharedTwoConfigKeyRestClient.class, "shared"), + new RegisteredRestClient(SharedThreeConfigKeyRestClient.class, "shared")); + } + }.configBuilder(builder); + } + }) + .build(); + assertNotNull(config); + + RestClientsConfig restClientsConfig = config.getConfigMapping(RestClientsConfig.class); + assertEquals(3, restClientsConfig.clients().size()); + + RestClientConfig restClientConfigOne = restClientsConfig.getClient(SharedOneConfigKeyRestClient.class); + assertThat(restClientConfigOne.uri().get()).isEqualTo("http://localhost:8081"); + assertEquals(2, restClientConfigOne.headers().size()); + assertEquals("one", restClientConfigOne.headers().get("two")); + assertEquals("two", restClientConfigOne.headers().get("one")); + + RestClientConfig restClientConfigTwo = restClientsConfig + .getClient(SharedTwoConfigKeyRestClient.class); + assertThat(restClientConfigTwo.uri().get()).isEqualTo("http://localhost:8082"); + assertEquals(2, restClientConfigTwo.headers().size()); + assertEquals("one", restClientConfigTwo.headers().get("two")); + assertEquals("two", restClientConfigTwo.headers().get("one")); + + RestClientConfig restClientConfigThree = restClientsConfig + .getClient(SharedThreeConfigKeyRestClient.class); + assertThat(restClientConfigThree.uri().get()).isEqualTo("http://localhost:8083"); + assertEquals(2, restClientConfigThree.headers().size()); + assertEquals("one", restClientConfigThree.headers().get("two")); + assertEquals("two", restClientConfigThree.headers().get("one")); + } + @Test void buildTimeConfig() { SmallRyeConfig config = ConfigUtils.emptyConfigBuilder() diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedOneConfigKeyRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedOneConfigKeyRestClient.java new file mode 100644 index 0000000000000..53350e34da0b5 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedOneConfigKeyRestClient.java @@ -0,0 +1,7 @@ +package io.quarkus.restclient.config.key; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "shared") +public interface SharedOneConfigKeyRestClient { +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedThreeConfigKeyRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedThreeConfigKeyRestClient.java new file mode 100644 index 0000000000000..cc87e958a0b23 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedThreeConfigKeyRestClient.java @@ -0,0 +1,7 @@ +package io.quarkus.restclient.config.key; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "shared") +public interface SharedThreeConfigKeyRestClient { +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedTwoConfigKeyRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedTwoConfigKeyRestClient.java new file mode 100644 index 0000000000000..ec04debb91ab7 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/key/SharedTwoConfigKeyRestClient.java @@ -0,0 +1,7 @@ +package io.quarkus.restclient.config.key; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "shared") +public interface SharedTwoConfigKeyRestClient { +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/one/SimpleNameRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/one/SimpleNameRestClient.java new file mode 100644 index 0000000000000..695b709324a0c --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/one/SimpleNameRestClient.java @@ -0,0 +1,7 @@ +package io.quarkus.restclient.config.simple.one; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "one") +public interface SimpleNameRestClient { +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/two/SimpleNameRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/two/SimpleNameRestClient.java new file mode 100644 index 0000000000000..586746a2a82c5 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/simple/two/SimpleNameRestClient.java @@ -0,0 +1,7 @@ +package io.quarkus.restclient.config.simple.two; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "two") +public interface SimpleNameRestClient { +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/resources/application.properties b/extensions/resteasy-classic/rest-client-config/runtime/src/test/resources/application.properties index fb083bea6f36b..8e4560a7ddb65 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/test/resources/application.properties +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/resources/application.properties @@ -54,3 +54,12 @@ mp.key/mp-rest/proxyAddress=mp.key mp.key/mp-rest/queryParamStyle=COMMA_SEPARATED MPConfigKeyRestClient/mp-rest/uri=http://localhost:8082 + +quarkus.rest-client.one.uri=http://localhost:8081 +quarkus.rest-client.two.uri=http://localhost:8082 + +quarkus.rest-client."io.quarkus.restclient.config.key.SharedOneConfigKeyRestClient".uri=http://localhost:8081 +quarkus.rest-client."io.quarkus.restclient.config.key.SharedTwoConfigKeyRestClient".uri=http://localhost:8082 +io.quarkus.restclient.config.key.SharedThreeConfigKeyRestClient/mp-rest/uri=http://localhost:8083 +quarkus.rest-client.shared.headers.two=one +quarkus.rest-client.shared.headers.one=two From 53c8759b3d42ec19fae5d9cb639d861f298367d4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 10 Dec 2024 08:50:37 +0200 Subject: [PATCH 03/22] Properly create REST Client template path when @Url is used Fixes: #44974 (cherry picked from commit e4a3279c2a2e6e8934648dfbb843141e9afef834) --- .../JaxrsClientReactiveProcessor.java | 32 +++++++++++++++---- .../reactive/client/impl/WebTargetImpl.java | 11 +++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 2f4ac2e80302f..bf1a4a4099909 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -955,8 +955,7 @@ A more full example of generated client (with sub-resource) can is at the bottom classContext.constructor.getThis(), baseTarget)); if (observabilityIntegrationNeeded) { - String templatePath = MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath()) - .replaceAll("/"); + String templatePath = templatePath(restClientInterface, method); classContext.constructor.invokeVirtualMethod( MethodDescriptor.ofMethod(WebTargetImpl.class, "setPreClientSendHandler", void.class, ClientRestHandler.class), @@ -1012,11 +1011,25 @@ A more full example of generated client (with sub-resource) can is at the bottom + jandexMethod.name()); } - ResultHandle newInputTarget = methodParamNotNull.invokeVirtualMethod( - MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, - java.net.URI.class), - methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), - newUri); + ResultHandle newInputTarget; + if (observabilityIntegrationNeeded) { + // we need to apply the ClientObservabilityHandler to the inputTarget field without altering it + newInputTarget = methodParamNotNull.invokeVirtualMethod( + MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, + java.net.URI.class, ClientRestHandler.class), + methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), + newUri, + methodParamNotNull.newInstance( + MethodDescriptor.ofConstructor(ClientObservabilityHandler.class, String.class), + methodParamNotNull.load(templatePath(restClientInterface, method)))); + } else { + // just read the inputTarget field and call withNewUri on it + newInputTarget = methodParamNotNull.invokeVirtualMethod( + MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, + java.net.URI.class), + methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), + newUri); + } ResultHandle newBaseTarget = methodParamNotNull.invokeVirtualMethod( baseTargetProducer.getMethodDescriptor(), methodParamNotNull.getThis(), newInputTarget); @@ -1247,6 +1260,11 @@ A more full example of generated client (with sub-resource) can is at the bottom } + private String templatePath(RestClientInterface restClientInterface, ResourceMethod method) { + return MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath()) + .replaceAll("/"); + } + /** * The @Encoded annotation is only supported in path/query/matrix/form params. */ diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java index 5ef736be1dd04..b96030232bf3a 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java @@ -259,6 +259,11 @@ public WebTargetImpl withNewUri(URI uri) { return newInstance(client, UriBuilder.fromUri(uri), configuration); } + @SuppressWarnings("unused") // this is used in the REST Client to support @BaseUrl and observability is enabled + public WebTargetImpl withNewUri(URI uri, ClientRestHandler preClientSendHandler) { + return newInstance(client, UriBuilder.fromUri(uri), configuration, preClientSendHandler); + } + @SuppressWarnings("unused") public WebTargetImpl queryParams(MultivaluedMap parameters) throws IllegalArgumentException, NullPointerException { @@ -297,6 +302,12 @@ public WebTargetImpl queryParamNoTemplate(String name, Object... values) throws protected WebTargetImpl newInstance(HttpClient client, UriBuilder uriBuilder, ConfigurationImpl configuration) { + return newInstance(client, uriBuilder, configuration, preClientSendHandler); + } + + protected WebTargetImpl newInstance(HttpClient client, UriBuilder uriBuilder, + ConfigurationImpl configuration, + ClientRestHandler preClientSendHandler) { WebTargetImpl result = new WebTargetImpl(restClient, client, uriBuilder, configuration, handlerChain.setPreClientSendHandler(preClientSendHandler), requestContext); From 71c8aba216ffc4f1c378d0e7d3eaca0af3409561 Mon Sep 17 00:00:00 2001 From: brunobat Date: Mon, 9 Dec 2024 16:06:37 +0000 Subject: [PATCH 04/22] Add test for case where RestClient uses @Url and observability features (cherry picked from commit 07a16033d2621cc4c733b0fc4c518834b70863a2) --- .../binder/RestClientUriParameterTest.java | 96 +++++++++++++++++++ .../binder/UriTagWithHttpRootTest.java | 8 ++ 2 files changed, 104 insertions(+) create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java 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..5bfc407260dd3 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java @@ -0,0 +1,96 @@ +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.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 = "/"; From c40b005d436ebbb217468700aea443685708e25d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 11 Dec 2024 13:28:59 +0200 Subject: [PATCH 05/22] Make sure Redis dev-service doesn't start in test that doesn't need it (cherry picked from commit 37c50ee34bf634bd0dc51be574051caad6e963c0) --- .../micrometer/deployment/binder/RestClientUriParameterTest.java | 1 + 1 file changed, 1 insertion(+) 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 index 5bfc407260dd3..c1fe6f1afcee3 100644 --- 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 @@ -32,6 +32,7 @@ public class RestClientUriParameterTest { 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 From 7af78162da41591399c39e2b109dbb4cffbd1d6d Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Tue, 10 Dec 2024 08:50:11 +0100 Subject: [PATCH 06/22] Upgrade to quarkus-http 5.3.4 (cherry picked from commit adf11bd89e2aec1adbeccb5cf7c64653dd95366a) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 9de128459dfab..32bfa1030cc4c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -32,7 +32,7 @@ 1.42.1 2.8.0-alpha 1.27.0-alpha - 5.3.3 + 5.3.4 1.13.7 2.2.2 0.22.0 From 1b4272437d1e0c1d8b0fd3a9818e04783fdbf4bc Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 11 Dec 2024 14:25:32 +0000 Subject: [PATCH 07/22] Update OIDC MTLS test to use generated certificates (cherry picked from commit f8b87362a5158b081b130266bdf31a24953a66d7) --- build-parent/pom.xml | 5 ++++ integration-tests/oidc-mtls/pom.xml | 28 +++++++++++++++++- .../src/main/resources/application.properties | 10 +++---- .../src/main/resources/server-keystore.jks | Bin 2423 -> 0 bytes .../src/main/resources/server-truststore.jks | Bin 925 -> 0 bytes .../java/io/quarkus/it/oidc/OidcMtlsTest.java | 14 ++++----- .../src/test/resources/client-keystore.jks | Bin 2214 -> 0 bytes .../src/test/resources/client-truststore.jks | Bin 2423 -> 0 bytes 8 files changed, 44 insertions(+), 13 deletions(-) delete mode 100644 integration-tests/oidc-mtls/src/main/resources/server-keystore.jks delete mode 100644 integration-tests/oidc-mtls/src/main/resources/server-truststore.jks delete mode 100644 integration-tests/oidc-mtls/src/test/resources/client-keystore.jks delete mode 100644 integration-tests/oidc-mtls/src/test/resources/client-truststore.jks diff --git a/build-parent/pom.xml b/build-parent/pom.xml index bd780c85a57ff..19df02c9047c1 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -660,6 +660,11 @@ + + io.smallrye.certs + smallrye-certificate-generator-maven-plugin + ${smallrye-certificate-generator.version} + diff --git a/integration-tests/oidc-mtls/pom.xml b/integration-tests/oidc-mtls/pom.xml index 2edad5c91ad20..719118f28efca 100644 --- a/integration-tests/oidc-mtls/pom.xml +++ b/integration-tests/oidc-mtls/pom.xml @@ -27,7 +27,6 @@ io.quarkus quarkus-tls-registry - io.quarkus quarkus-junit5 @@ -88,6 +87,33 @@ + io.smallrye.certs + smallrye-certificate-generator-maven-plugin + + + generate-test-resources + + generate + + + + + + + oidc + + PEM + PKCS12 + + password + backend-service + 2 + true + + + + + maven-surefire-plugin true diff --git a/integration-tests/oidc-mtls/src/main/resources/application.properties b/integration-tests/oidc-mtls/src/main/resources/application.properties index 69d52fd93aa24..939e259a700ac 100644 --- a/integration-tests/oidc-mtls/src/main/resources/application.properties +++ b/integration-tests/oidc-mtls/src/main/resources/application.properties @@ -1,11 +1,11 @@ quarkus.http.tls-configuration-name=oidc-mtls -quarkus.tls.oidc-mtls.key-store.jks.path=server-keystore.jks -quarkus.tls.oidc-mtls.key-store.jks.password=secret -quarkus.tls.oidc-mtls.trust-store.jks.path=server-truststore.jks -quarkus.tls.oidc-mtls.trust-store.jks.password=password +quarkus.tls.oidc-mtls.key-store.p12.path=target/certificates/oidc-keystore.p12 +quarkus.tls.oidc-mtls.key-store.p12.password=password +quarkus.tls.oidc-mtls.trust-store.p12.path=target/certificates/oidc-server-truststore.p12 +quarkus.tls.oidc-mtls.trust-store.p12.password=password quarkus.http.auth.inclusive=true quarkus.http.ssl.client-auth=REQUIRED quarkus.http.insecure-requests=DISABLED -quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks +quarkus.native.additional-build-args=-H:IncludeResources=target/certificates/.*\\.p12 diff --git a/integration-tests/oidc-mtls/src/main/resources/server-keystore.jks b/integration-tests/oidc-mtls/src/main/resources/server-keystore.jks deleted file mode 100644 index da33e8e7a16683d421c7a541bf0013521efb605e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2423 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 aH~3Ca%|*I@e88To>+D>~J`lt~rv3-)J!2gJ diff --git a/integration-tests/oidc-mtls/src/main/resources/server-truststore.jks b/integration-tests/oidc-mtls/src/main/resources/server-truststore.jks deleted file mode 100644 index 8ec8e126507b61e0e602b71d9f67b3d7e3c7cae3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 925 zcmezO_TO6u1_mY|W(3o$xs}0Ue&dE&8D>0B0 z=QXr6G&C?YG&M3ZGLI7HHL^4`0AdK2=Jq!+Dj|EBk(GhDiIJbdpox)-sfm%1VUF3s zo}9*=J}DCyv6d_>miqhX+0BER7vzr=weLB0SI~-EN4Tx;+oAX3sZu+AreC;W+rDov z-#5*V51%D8KX(20xaE+MMC5UqlCZ}&*8kY~jW=TIFRK(mcF!wJ^TT7)vd&p~1Uy)$ z`pW;@Uk~HZlKvCjci1|P$j&@8Uwi5_z2paTO5a=hS@rqWe0&n}OLbn_nKBuLad&4PoF%w@U!d89 z+LCrv&mDH_wWWUYJ`36D%^-U7h-9RXpo^<=ZFc0HX`&2UWMlL9lv%uVR}CpmJg{;8 zx_~1U?x&fU85tNCD;mff$O0oymXAe@MTGhL(rFKW9uTVSSw3Zn;O1#zMYmj$0}+^R zfPu)!z}8XzuI^>`BBZaSb80!?+r7pc#`09np_Lozt4zJ}{d+W?|#aPK1E8H9tc&p84 z9$uW3|Ks-G6^q-888qdu-^`O)?fLIsY>e9J3v8=pk6rP))@~NVdhf7^xr%i0hj$v? zKacmOG&aO636*{EblS9x!(U?iHAU{$t+Riy#c;{YELD{*se2PT}_AU?Y+Xxvefqh07{ov?f?J) diff --git a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java index ce4b2cd482cad..458c37b26b1ea 100644 --- a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java +++ b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java @@ -27,7 +27,7 @@ @QuarkusTest public class OidcMtlsTest { - @TestHTTPResource(ssl = true) + @TestHTTPResource(tls = true) URL url; KeycloakTestClient keycloakClient = new KeycloakTestClient(); @@ -46,7 +46,7 @@ public void testGetIdentityNames() throws Exception { .indefinitely(); assertEquals(200, resp.statusCode()); String name = resp.bodyAsString(); - assertEquals("Identities: CN=client, alice", name); + assertEquals("Identities: CN=backend-service, alice", name); // HTTP 401, invalid token resp = webClient.get("/service/name") @@ -63,18 +63,18 @@ private WebClientOptions createWebClientOptions() throws Exception { WebClientOptions webClientOptions = new WebClientOptions().setDefaultHost(url.getHost()) .setDefaultPort(url.getPort()).setSsl(true).setVerifyHost(false); - byte[] keyStoreData = getFileContent(Paths.get("client-keystore.jks")); + byte[] keyStoreData = getFileContent(Paths.get("target/certificates/oidc-client-keystore.p12")); KeyStoreOptions keyStoreOptions = new KeyStoreOptions() .setPassword("password") .setValue(Buffer.buffer(keyStoreData)) - .setType("JKS"); + .setType("PKCS12"); webClientOptions.setKeyCertOptions(keyStoreOptions); - byte[] trustStoreData = getFileContent(Paths.get("client-truststore.jks")); + byte[] trustStoreData = getFileContent(Paths.get("target/certificates/oidc-client-truststore.p12")); KeyStoreOptions trustStoreOptions = new KeyStoreOptions() - .setPassword("secret") + .setPassword("password") .setValue(Buffer.buffer(trustStoreData)) - .setType("JKS"); + .setType("PKCS12"); webClientOptions.setTrustOptions(trustStoreOptions); return webClientOptions; diff --git a/integration-tests/oidc-mtls/src/test/resources/client-keystore.jks b/integration-tests/oidc-mtls/src/test/resources/client-keystore.jks deleted file mode 100644 index cf6d6ba454864d18322799afac37f520673193d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmcJQ`8O2&9>-_54#_?lreR2^8EGsf5m^giEKx+VWCk-tSu%>TFQG7!wawCFxDt1) z$&$558ao-vR`!gZd7gXkInO_Ee|Z1!I_G`9pY!>AzUTefU)o;+001DafPV|-e$)Fp zk-|kHUfYHU06+m)Dr65U1mjnM0U^MnAQ2!C3V=`{Y~pr7{iGM2;kEYzjSPZ7q9r%T zzQd$tc?+tj?ncxmVd`4L6BK!)Z-fwDm^`G}mK(Y2EM+!Pj`lgrsrJg@^?ZUUMf+9S zb$3jkBB_2WaIFmZdHlc<=hRDRlY5pevhC%>O-`2)l-y__$OB;n#8 zcs3**>MX{B3)};)HziYcmcvIR-dyS*V9sJum6L+Wy4gF3)lb{X?fxo-w|k2-JV*5M zW_@%NWtS@}s+4%_R$w)X$TxE4DA}-?{J?t`)drk|JW1vyxJdkF_Z}^{sLN8$dazje zb(9ug=(48*>>So`T-N)DLF-T}=5uE!{m69F0;RA-@rBpVq*(Et7lm8%ax@^KmH~=F)MVeABtWz zj_2v0zkl=<+;jI9t60&nFG$dAf-Ut$nvIWw8A}9kWGufYqegF4THNT(?%V#CefCz) zl*CL~jAo*5ZQSx)bd1gx`Yk2myuym#g%(>#WXmec0hJfOd6l}d=4!W-F7;Kc78_qJ zrYg-OUD={O*Gm?PYER)M*jqeb48_|4eFtq&-JimlkVRLn^8(%Ph zi3_(Cddo6Ut(!|VVJIc+wF@ZJiXVQY>@AF}Z@yTpB>(Z+cIfvja+>{VW8){3qHLW~L@8P<5~XEsR8n5V_&H46$yc*D zh=%X6Zj}i?ND&KumSL87kY8z{CNp~|b+92uI6vvG;CP15tQF3j5FD2x)`daP1L7aN zM*A#I>}4Cst8~th7b1j++S{u3>o=h@^ICcik!t~G#4S?rjZ24M9@}v<5>|a3nX)v* zNoq4I^xgCL$Ia)5nU@M1p$^lCrEdXc(dKJHCr1};b6H#I{O@HBsKsD;tz&Ezfnf^l zv5A+0ack_=h{X7zEXz00L;fVal)fUn5S5ZnRYr$XX=;aZc?K1KLAO`T@v?NDXBo6B zE!|IVW$vJCs_YM2BPrsnsg=904M(5IJD?dv-!nq+T1VAcVx#fp@1gW>#tSsudEWs_ z0#W;_)G2eu6Ly{f>07n*g9DDv&4bg_f)|dOF3FMbn5qEmD-GJ^!5wuW9wtd+ zD1)OT!~7tvl0hicD$@!jNQB{b(*+T!TgO5H?&%lz=K-e&cV6j*G}ufPSl65OSe>oU z2n7eGXS)@ie73kdD675O*7@efl>pAYH#;?9q+fGdFM7_{lJ6AtoBZ)uHE>A zpNaDEOFXD%Fiu!4U9uPBsE zaid(v!Lb5=F^?$3-J24MJHQQBF7k`=1O&MS`Ua8zXAs~Tt_MOc5@uTKS{|JgG49)PJpVeX*-@`I>AAb^fMS1ikLboS|1Gqsyf*dK zHG0NqbLL))x|HKP846)$q;k7%6Yh|?tK%WUWBL)4Z|~yi)2m}*HCcgKwCv@WoUo&$ zbR@7~v^HdROTJ2G)-m@GvvJokxO4`(f4f>E=6ZxA`D7TV|b!^>P3<~j#oFoJ!QMEGL70`0V7a6h(UJ2 zB3q+&A>(#yIdA#s`}xBz(vO~<*LQ{A(adfwx<=am!g*E{O9m**efmt4d1<;o?&7T2 z-4A<-Z2xFB9E*2`j<w_4$0h-#_0-5;OwH0YZ|XDiG)?vL%_t$HB!x zB|#;@B&g`2eH=;R4EtBa=?x}vdL7#ChvN+4{ofUghXY6@fqx=NU=~sd!t;Oo@VPh` z{u^~hzxbJY7jese*aWc5)4oDTXGhGCMrd-AXW!cv zCg}`o^yK?#Xs29}+FQ4Jm72&uWtOZ&dicTsSGB!=c>+ur9lJb~mp}ASUUF&Es=lGy z+VQ?ibX2yEzsF8X6Zme24pl{6%VXKQV@zm6$W}X9%e!Y}7Ao-zkFojqZmCuGx!G#+ z9!$dh1#8p5t*xJnP%Dfd|j`>1KWp9eXqsx`4fNfvHdxMue#CqA+0pS z%H?XwL|58}{6wuY`6XexN0)7aOEIBC7W&0bva*xaX^s@j1Mc$EgYv3HYG}qR)@6E1 z9sZ=SWA=>?q}=1`nahTN@LxU$n`AfXlpXoB^Rjo`V7E^ibz^(hZw+7YLr)uf57!rP zXEZrD=#np7@i1u<8!HNR31(w$3~WbYo@RP^7ze}Y8s68R7)!Gr|A@zofo!j(f9-9& zt|f(cHh3+yrGt@E^XRKld?hY7m}l?16&b(El2&PZ#fh!;)JUNiUdrA!NL_ou0O7Qm zgtJvdlL_<>~?>DZfB(?++8+g+I%|eO<^9l29*{eo3XyO z0|G@+8yLwsv&v-USqPsm7HhV!TfBja}Xv0t->)dRA`PdfFb=Wid=7u4vzZ zf}0|Z8}YB*Td+KV4#-OIVMeFbTpKj1Pd*Byu3R-9!nT{UzPFYxJBz?$on49Vdp$yz zFj4y0BD+?uHpLvTVk<1~LFbr=0RPWZ3VOwgLcQ~aYG#F>KKCcoXZp5}_m)=tDWjBF z+qb!;{;MT!SuxxG84wxzpg?$WNigCO>%0W7-L1-;sFTycn;Ao|=bFRsh}?V(tiS!O z*ZL@nccj_iF^Hk$a!I6Xj?W312EL?Cc zu2^BrIfvd^+8b?0fpXxq6}C6kBIskuZ+-kLbTh+vb9XHS#WviSR z4hn@Nf%5+@aY3jg(9J_D9moMVT%>;zj(?FB@?X*lr#Zil4v2*GGjs&cw`M)3E#v$D zB5e{0WO++h>s!Yu;ZX1LV`)T!T~+p!H@S?mT8>feY)0+I<-W;zK(ywI9u08<&|n0? z7&J3ugoZ-g*`QZs#j&&U%h=az#QiH)dUA4K9u27TyL6u4yeR|E)og#v7wuC9oESMv zS3a%a$6OplbI3VhMbE|1*+QyPSEoMO{RmX{-&%GI87#E|8+|a=Ulhe--k>T%vJDL9Jgz18_Nr4p@ z+TdjVWhL*Xo?-85<3=wbX~2Q^?75kbu92zqXrFDbSU>F|{sn9I_x%?e!bfG+7OL+Q zrt^p(lZ$`PMqa#bOVmjGQtfAJN5YmeXch_GEGC1f$e*U_NwwQH;^X@2@ZtV5uHlug z=G#Z^g#?hRjS)A-U-mS~X& zD&}5ZyUgS078SQqm+1QjGj_T6b~pnV?&m*?cy(PN_q50_vfsF9c`4?N=?YxxjzuZ^ z!q*k0&hdG!NW@6gG3=s)Zfmi42$bv7KELgQpoFx**mel<=kj&h5jG*nG-y|cIre(` z=TnMyM_kwYg~a$qn!dXf7$dk=8NCk((7JJ^i=?zSnsQ+1G}-^+xVm&((Yv!SE^LT( zO1q~)BB8rhPP1qUKA208zYD9nTbJx`2iSqrERpIm;O~iJaJS`w=RHRG5vbrwfY^ZA zrOJvWA;gOu+x1|th3U(-ed}OPGm!(6v{n!eZGtJ?voNh8n(7Rn2^S3;Jtb&ZKG7zb zdiqMen)hrq#{Rkan2Q)dGA(Vs;mJ24zs8Y-NAp&&NCYXCk^RW)H;sYMQ;%*1>F7Ox z&7lkW)?gh;(|lW)AifN&+Qf~lDalth4Sk}qu36l;j;HH=0;C%j(6^t(QiGuN6E_6K z7mV2rZNIOz-Rbt@mMlbQ_91)lJzqK@5%7bPUrHE(;nW|&dRPd$kZv0`uy3qXser?_ zxaQN;d6-u<2W(o921pqs6vC;@4+4sD0>E(IxBCh|Sez3?#v7b1OUA>|ocRe1|6w#- aH~3Ca%|*I@e88To>+D>~J`lt~rv3-)J!2gJ From 9b69d4187a6dabe1ea90f8c3c06b7e647a6c9f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Thu, 12 Dec 2024 14:10:30 +0100 Subject: [PATCH 08/22] fix(oidc,security): OIDC must auth before mTLS when disabled inclusive auth (cherry picked from commit 671ed8819883e360d69fdb7bfe45c1901fbb1fc1) --- .../security-authentication-mechanisms.adoc | 6 +- extensions/oidc/deployment/pom.xml | 5 + .../OidcMtlsDisabledInclusiveAuthTest.java | 107 ++++++++++++++++++ .../security/InclusiveAuthValidationTest.java | 2 +- .../vertx/http/runtime/AuthConfig.java | 2 + .../runtime/security/HttpAuthenticator.java | 2 +- .../security/MtlsAuthenticationMechanism.java | 13 ++- 7 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OidcMtlsDisabledInclusiveAuthTest.java diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc index 95a32393ed97b..f5cc1a250b9e0 100644 --- a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc +++ b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc @@ -602,7 +602,11 @@ quarkus.http.auth.inclusive=true If the authentication is inclusive then `SecurityIdentity` created by the first authentication mechanism can be injected into the application code. For example, if both <> and basic authentication mechanism authentications are required, -the <> authentication mechanism will create `SecurityIdentity` first. +the <> mechanism will create `SecurityIdentity` first. + +NOTE: The <> mechanism has the highest priority when inclusive authentication is enabled, to ensure +that an injected `SecurityIdentity` always represents <> and can be used to get access to `SecurityIdentity` +identities provided by other authentication mechanisms. Additional `SecurityIdentity` instances can be accessed as a `quarkus.security.identities` attribute on the first `SecurityIdentity`, however, accessing these extra identities directly may not be necessary, for example, diff --git a/extensions/oidc/deployment/pom.xml b/extensions/oidc/deployment/pom.xml index 8691bf75f1911..8e8796c16474f 100644 --- a/extensions/oidc/deployment/pom.xml +++ b/extensions/oidc/deployment/pom.xml @@ -93,6 +93,11 @@ quarkus-elytron-security-properties-file-deployment test + + io.smallrye.certs + smallrye-certificate-generator-junit5 + test + diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OidcMtlsDisabledInclusiveAuthTest.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OidcMtlsDisabledInclusiveAuthTest.java new file mode 100644 index 0000000000000..94b913a6b3c5a --- /dev/null +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/OidcMtlsDisabledInclusiveAuthTest.java @@ -0,0 +1,107 @@ +package io.quarkus.oidc.test; + +import static org.hamcrest.Matchers.is; + +import java.io.File; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.oidc.BearerTokenAuthentication; +import io.quarkus.security.Authenticated; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; +import io.quarkus.vertx.http.runtime.security.annotation.MTLSAuthentication; +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; +import io.smallrye.certs.Format; +import io.smallrye.certs.junit5.Certificate; +import io.smallrye.certs.junit5.Certificates; + +/** + * This test ensures OIDC runs before mTLS authentication mechanism when inclusive authentication is not enabled. + */ +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "mtls-test", password = "secret", formats = { + Format.PKCS12, Format.PEM }, client = true)) +public class OidcMtlsDisabledInclusiveAuthTest { + + private static final String BASE_URL = "https://localhost:8443/mtls-bearer/"; + private static final String CONFIGURATION = """ + quarkus.tls.key-store.pem.0.cert=server.crt + quarkus.tls.key-store.pem.0.key=server.key + quarkus.tls.trust-store.pem.certs=ca.crt + quarkus.http.ssl.client-auth=REQUIRED + quarkus.http.insecure-requests=disabled + quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus + quarkus.oidc.client-id=quarkus-service-app + quarkus.oidc.credentials.secret=secret + quarkus.http.auth.proactive=false + """; + + @RegisterExtension + static final QuarkusDevModeTest config = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(MtlsBearerResource.class) + .addAsResource(new StringAsset(CONFIGURATION), "application.properties") + .addAsResource(new File("target/certs/mtls-test.key"), "server.key") + .addAsResource(new File("target/certs/mtls-test.crt"), "server.crt") + .addAsResource(new File("target/certs/mtls-test-server-ca.crt"), "ca.crt")); + + @Test + public void testOidcHasHighestPriority() { + givenWithCerts().get(BASE_URL + "only-mtls").then().statusCode(200).body(is("CN=localhost")); + givenWithCerts().auth().oauth2(getAccessToken()).get(BASE_URL + "only-bearer").then().statusCode(200).body(is("alice")); + // this needs to be OIDC because when inclusive auth is disabled, OIDC has higher priority + givenWithCerts().auth().oauth2(getAccessToken()).get(BASE_URL + "both").then().statusCode(200).body(is("alice")); + // OIDC must run first and thus authentication fails over invalid credentials + givenWithCerts().auth().oauth2("invalid-token").get(BASE_URL + "both").then().statusCode(401); + // mTLS authentication mechanism still runs when OIDC doesn't produce the identity + givenWithCerts().get(BASE_URL + "both").then().statusCode(200).body(is("CN=localhost")); + } + + private static RequestSpecification givenWithCerts() { + return RestAssured.given() + .keyStore("target/certs/mtls-test-client-keystore.p12", "secret") + .trustStore("target/certs/mtls-test-client-truststore.p12", "secret"); + } + + private static String getAccessToken() { + return KeycloakTestResourceLifecycleManager.getAccessToken("alice"); + } + + @Path("mtls-bearer") + public static class MtlsBearerResource { + + @Inject + SecurityIdentity securityIdentity; + + @GET + @Authenticated + @Path("both") + public String both() { + return securityIdentity.getPrincipal().getName(); + } + + @GET + @MTLSAuthentication + @Path("only-mtls") + public String onlyMTLS() { + return securityIdentity.getPrincipal().getName(); + } + + @GET + @BearerTokenAuthentication + @Path("only-bearer") + public String onlyBearer() { + return securityIdentity.getPrincipal().getName(); + } + } +} diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/InclusiveAuthValidationTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/InclusiveAuthValidationTest.java index 83285ca98d6ef..186b7a4b6dfc3 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/InclusiveAuthValidationTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/InclusiveAuthValidationTest.java @@ -89,7 +89,7 @@ public Set> getCredentialTypes() { @Override public int getPriority() { - return MtlsAuthenticationMechanism.PRIORITY + 1; + return MtlsAuthenticationMechanism.INCLUSIVE_AUTHENTICATION_PRIORITY + 1; } } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java index 08107d606038f..d251a34e732b7 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java @@ -43,6 +43,8 @@ public class AuthConfig { * authentication, for example, OIDC bearer token authentication, must succeed. * In such cases, `SecurityIdentity` created by the first mechanism, mTLS, can be injected, identities created * by other mechanisms will be available on `SecurityIdentity`. + * The mTLS mechanism is always the first mechanism, because its priority is elevated when inclusive authentication + * is enabled. * The identities can be retrieved using utility method as in the example below: * *
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java
index 0dfbc8f392975..da171acff63df 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java
@@ -160,7 +160,7 @@ public int compare(HttpAuthenticationMechanism mech1, HttpAuthenticationMechanis
                                     the highest priority. Please lower priority of the '%s' authentication mechanism under '%s'.
                                     """.formatted(MtlsAuthenticationMechanism.class.getName(),
                                     topMechanism.getClass().getName(),
-                                    MtlsAuthenticationMechanism.PRIORITY));
+                                    MtlsAuthenticationMechanism.INCLUSIVE_AUTHENTICATION_PRIORITY));
                 }
             }
         }
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java
index fa7d77c449dec..e6316f0e1bc0e 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java
@@ -17,6 +17,8 @@
  */
 package io.quarkus.vertx.http.runtime.security;
 
+import static io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism.DEFAULT_PRIORITY;
+
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Collections;
@@ -25,6 +27,8 @@
 
 import javax.net.ssl.SSLPeerUnverifiedException;
 
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.quarkus.security.credential.CertificateCredential;
 import io.quarkus.security.identity.IdentityProviderManager;
@@ -39,10 +43,15 @@
  * The authentication handler responsible for mTLS client authentication
  */
 public class MtlsAuthenticationMechanism implements HttpAuthenticationMechanism {
-    public static final int PRIORITY = 3000;
+    public static final int INCLUSIVE_AUTHENTICATION_PRIORITY = 3000;
     private static final String ROLES_MAPPER_ATTRIBUTE = "roles_mapper";
+    private final boolean inclusiveAuthentication;
     private Function> certificateToRoles = null;
 
+    MtlsAuthenticationMechanism(@ConfigProperty(name = "quarkus.http.auth.inclusive") boolean inclusiveAuthentication) {
+        this.inclusiveAuthentication = inclusiveAuthentication;
+    }
+
     @Override
     public Uni authenticate(RoutingContext context,
             IdentityProviderManager identityProviderManager) {
@@ -86,7 +95,7 @@ public Uni getCredentialTransport(RoutingContext contex
 
     @Override
     public int getPriority() {
-        return PRIORITY;
+        return inclusiveAuthentication ? INCLUSIVE_AUTHENTICATION_PRIORITY : DEFAULT_PRIORITY;
     }
 
     void setCertificateToRolesMapper(Function> certificateToRoles) {

From 1ce8edc3b54284fa01e9b7cbbc5532f26d56de3c Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Thu, 12 Dec 2024 20:45:16 +0200
Subject: [PATCH 09/22] Ensure that jakarta json types can be deserialized in
 native mode

Fixes: #45084
(cherry picked from commit 0879ce431e1cbdb599cf852c4fc142ea98dba22c)
---
 .../jsonp/ServerJsonArrayHandler.java         | 16 ++++++++++++-
 .../jsonp/ServerJsonObjectHandler.java        | 15 +++++++++++-
 .../jsonp/ServerJsonStructureHandler.java     | 15 +++++++++++-
 .../jsonp/ServerJsonValueHandler.java         | 16 ++++++++++++-
 .../java/io/quarkus/it/qute/JsonResource.java | 24 +++++++++++++++++++
 .../java/io/quarkus/it/qute/QuteTestCase.java | 12 ++++++++++
 6 files changed, 94 insertions(+), 4 deletions(-)
 create mode 100644 integration-tests/qute/src/main/java/io/quarkus/it/qute/JsonResource.java

diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonArrayHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonArrayHandler.java
index 8ea6a5ec31d3f..750adf79c8678 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonArrayHandler.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonArrayHandler.java
@@ -1,6 +1,7 @@
 package org.jboss.resteasy.reactive.server.providers.serialisers.jsonp;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Type;
 
 import jakarta.json.JsonArray;
@@ -11,10 +12,12 @@
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonArrayHandler;
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonpUtil;
 import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
 import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
 import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
 
-public class ServerJsonArrayHandler extends JsonArrayHandler implements ServerMessageBodyWriter {
+public class ServerJsonArrayHandler extends JsonArrayHandler
+        implements ServerMessageBodyWriter, ServerMessageBodyReader {
 
     @Override
     public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, MediaType mediaType) {
@@ -30,4 +33,15 @@ public void writeResponse(JsonArray o, Type genericType, ServerRequestContext co
         context.serverResponse().end(out.toByteArray());
     }
 
+    @Override
+    public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+            MediaType mediaType) {
+        return JsonArray.class.isAssignableFrom(type);
+    }
+
+    @Override
+    public JsonArray readFrom(Class type, Type genericType, MediaType mediaType,
+            ServerRequestContext context) throws WebApplicationException, IOException {
+        return JsonpUtil.reader(context.getInputStream(), mediaType).readArray();
+    }
 }
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonObjectHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonObjectHandler.java
index fcaa1d9091bc2..ba5dc772cdbd9 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonObjectHandler.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonObjectHandler.java
@@ -11,10 +11,12 @@
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonObjectHandler;
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonpUtil;
 import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
 import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
 import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
 
-public class ServerJsonObjectHandler extends JsonObjectHandler implements ServerMessageBodyWriter {
+public class ServerJsonObjectHandler extends JsonObjectHandler
+        implements ServerMessageBodyWriter, ServerMessageBodyReader {
 
     @Override
     public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, MediaType mediaType) {
@@ -30,4 +32,15 @@ public void writeResponse(JsonObject o, Type genericType, ServerRequestContext c
         context.serverResponse().end(out.toByteArray());
     }
 
+    @Override
+    public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+            MediaType mediaType) {
+        return JsonObject.class.isAssignableFrom(type);
+    }
+
+    @Override
+    public JsonObject readFrom(Class type, Type genericType, MediaType mediaType,
+            ServerRequestContext context) throws WebApplicationException {
+        return JsonpUtil.reader(context.getInputStream(), mediaType).readObject();
+    }
 }
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonStructureHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonStructureHandler.java
index ef055f5905fe4..ac9c7e6de030c 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonStructureHandler.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonStructureHandler.java
@@ -1,6 +1,7 @@
 package org.jboss.resteasy.reactive.server.providers.serialisers.jsonp;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Type;
 
 import jakarta.json.JsonObject;
@@ -12,11 +13,12 @@
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonStructureHandler;
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonpUtil;
 import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
 import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
 import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
 
 public class ServerJsonStructureHandler extends JsonStructureHandler
-        implements ServerMessageBodyWriter {
+        implements ServerMessageBodyWriter, ServerMessageBodyReader {
 
     @Override
     public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, MediaType mediaType) {
@@ -32,4 +34,15 @@ public void writeResponse(JsonStructure o, Type genericType, ServerRequestContex
         context.serverResponse().end(out.toByteArray());
     }
 
+    @Override
+    public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+            MediaType mediaType) {
+        return JsonStructure.class.isAssignableFrom(type) && !JsonObject.class.isAssignableFrom(type);
+    }
+
+    @Override
+    public JsonStructure readFrom(Class type, Type genericType, MediaType mediaType,
+            ServerRequestContext context) throws WebApplicationException, IOException {
+        return JsonpUtil.reader(context.getInputStream(), mediaType).read();
+    }
 }
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonValueHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonValueHandler.java
index b75d0d68b8961..e74804914e9c3 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonValueHandler.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/jsonp/ServerJsonValueHandler.java
@@ -1,6 +1,7 @@
 package org.jboss.resteasy.reactive.server.providers.serialisers.jsonp;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Type;
 
 import jakarta.json.JsonValue;
@@ -11,10 +12,12 @@
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonValueHandler;
 import org.jboss.resteasy.reactive.common.providers.serialisers.jsonp.JsonpUtil;
 import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
+import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader;
 import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
 import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
 
-public class ServerJsonValueHandler extends JsonValueHandler implements ServerMessageBodyWriter {
+public class ServerJsonValueHandler extends JsonValueHandler
+        implements ServerMessageBodyWriter, ServerMessageBodyReader {
 
     @Override
     public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, MediaType mediaType) {
@@ -30,4 +33,15 @@ public void writeResponse(JsonValue o, Type genericType, ServerRequestContext co
         context.serverResponse().end(out.toByteArray());
     }
 
+    @Override
+    public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod,
+            MediaType mediaType) {
+        return JsonValue.class.isAssignableFrom(type);
+    }
+
+    @Override
+    public JsonValue readFrom(Class type, Type genericType, MediaType mediaType,
+            ServerRequestContext context) throws WebApplicationException, IOException {
+        return JsonpUtil.reader(context.getInputStream(), mediaType).readValue();
+    }
 }
diff --git a/integration-tests/qute/src/main/java/io/quarkus/it/qute/JsonResource.java b/integration-tests/qute/src/main/java/io/quarkus/it/qute/JsonResource.java
new file mode 100644
index 0000000000000..9699c5a7d1fc9
--- /dev/null
+++ b/integration-tests/qute/src/main/java/io/quarkus/it/qute/JsonResource.java
@@ -0,0 +1,24 @@
+package io.quarkus.it.qute;
+
+import jakarta.inject.Inject;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateInstance;
+
+@Path("json")
+public class JsonResource {
+
+    @Inject
+    Template hello;
+
+    @POST
+    @Produces(MediaType.TEXT_HTML)
+    public TemplateInstance get(JsonObject request) {
+        return hello.data("name", request.get("name"));
+    }
+}
diff --git a/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java b/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java
index 81c791ea56e18..5124c67bd95b7 100644
--- a/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java
+++ b/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java
@@ -28,6 +28,18 @@ public void testTemplates() throws InterruptedException {
                 .body(containsString("Hello Ciri!"));
         RestAssured.when().get("/beer").then().body(containsString("Beer Pilsner, completed: true, done: true"));
         RestAssured.when().get("/defaultmethod").then().body(containsString("Hello MK"));
+        RestAssured
+                .given()
+                .contentType("application/json")
+                .body("""
+                        {
+                          "name": "foo"
+                        }
+                        """)
+                .when().post("/json")
+                .then()
+                .statusCode(200)
+                .body(containsString("foo"));
     }
 
     @Test

From fbcb7673e99c42916ace96826c6682a6a2808b10 Mon Sep 17 00:00:00 2001
From: Sergey Beryozkin 
Date: Thu, 12 Dec 2024 14:07:44 +0000
Subject: [PATCH 10/22] Generate certificates in the OIDC integration test

(cherry picked from commit 80d1a2d1553d236a754893048df2e2978f33fc79)
---
 integration-tests/oidc-mtls/pom.xml           |  52 +++++++--------
 integration-tests/oidc/pom.xml                |  27 ++++++++
 .../src/main/resources/application.properties |  15 +++--
 .../src/main/resources/client-keystore.p12    | Bin 2712 -> 0 bytes
 .../src/main/resources/client-truststore.p12  | Bin 1254 -> 0 bytes
 .../src/main/resources/server-keystore.p12    | Bin 2712 -> 0 bytes
 .../src/main/resources/server-truststore.p12  | Bin 1238 -> 0 bytes
 .../oidc/src/main/resources/upconfig.json     |  60 ------------------
 .../AbstractBearerTokenAuthorizationTest.java |   4 +-
 ...KeycloakXTestResourceLifecycleManager.java |   4 +-
 .../it/keycloak/WebsocketOidcTestCase.java    |   4 +-
 .../keycloak/client/KeycloakTestClient.java   |   5 +-
 12 files changed, 74 insertions(+), 97 deletions(-)
 delete mode 100644 integration-tests/oidc/src/main/resources/client-keystore.p12
 delete mode 100644 integration-tests/oidc/src/main/resources/client-truststore.p12
 delete mode 100644 integration-tests/oidc/src/main/resources/server-keystore.p12
 delete mode 100644 integration-tests/oidc/src/main/resources/server-truststore.p12
 delete mode 100644 integration-tests/oidc/src/main/resources/upconfig.json

diff --git a/integration-tests/oidc-mtls/pom.xml b/integration-tests/oidc-mtls/pom.xml
index 719118f28efca..7b6b331e319be 100644
--- a/integration-tests/oidc-mtls/pom.xml
+++ b/integration-tests/oidc-mtls/pom.xml
@@ -87,32 +87,32 @@
     
         
             
-    io.smallrye.certs
-    smallrye-certificate-generator-maven-plugin
-    
-        
-            generate-test-resources
-            
-                generate
-            
-        
-    
-    
-        
-            
-                oidc 
-                  
-                    PEM
-                    PKCS12
-                
-                password 
-                backend-service 
-                2 
-                true 
-            
-        
-    
-
+	        io.smallrye.certs
+	        smallrye-certificate-generator-maven-plugin
+	        
+		    
+		        generate-test-resources
+		        
+		            generate
+		        
+		    
+	        
+	        
+		    
+		        
+		            oidc 
+		              
+		                PEM
+		                PKCS12
+		            
+		            password 
+		            backend-service 
+		            2 
+		            true 
+		        
+		    
+	        
+	    
             
                 maven-surefire-plugin
                 
diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml
index ff0b6fdd56069..e63f4b8707ff2 100644
--- a/integration-tests/oidc/pom.xml
+++ b/integration-tests/oidc/pom.xml
@@ -132,6 +132,33 @@
                     
                 
             
+            
+	        io.smallrye.certs
+	        smallrye-certificate-generator-maven-plugin
+	        
+		    
+		        generate-test-resources
+		        
+		            generate
+		        
+		    
+	        
+	        
+		    
+		        
+		            oidc 
+		              
+		                PEM
+		                PKCS12
+		            
+		            password 
+		            backend-service 
+		            2 
+		            true 
+		        
+		    
+	        
+	    
         
     
 
diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties
index d8b1ec529ad7c..e4552113a34b4 100644
--- a/integration-tests/oidc/src/main/resources/application.properties
+++ b/integration-tests/oidc/src/main/resources/application.properties
@@ -1,23 +1,24 @@
 quarkus.keycloak.devservices.create-realm=false
 quarkus.keycloak.devservices.start-command=start --https-client-auth=required --hostname-strict=false --https-key-store-file=/etc/server-keystore.p12 --https-trust-store-file=/etc/server-truststore.p12 --https-trust-store-password=password --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json
-quarkus.keycloak.devservices.resource-aliases.keystore=server-keystore.p12
-quarkus.keycloak.devservices.resource-aliases.truststore=server-truststore.p12
+quarkus.keycloak.devservices.resource-aliases.keystore=target/certificates/oidc-keystore.p12
+quarkus.keycloak.devservices.resource-aliases.truststore=target/certificates/oidc-server-truststore.p12
 quarkus.keycloak.devservices.resource-mappings.keystore=/etc/server-keystore.p12
 quarkus.keycloak.devservices.resource-mappings.truststore=/etc/server-truststore.p12
 
 quarkus.oidc.token.principal-claim=email
 
-quarkus.oidc.tls.verification=required
-quarkus.oidc.tls.trust-store-file=client-truststore.p12
+quarkus.oidc.tls.verification=certificate-validation
+quarkus.oidc.tls.trust-store-file=target/certificates/oidc-client-truststore.p12
 quarkus.oidc.tls.trust-store-password=password
-quarkus.oidc.tls.key-store-file=client-keystore.p12
+quarkus.oidc.tls.key-store-file=target/certificates/oidc-client-keystore.p12
 quarkus.oidc.tls.key-store-password=password
 
 %tls-registry.quarkus.oidc.tls.tls-configuration-name=oidc-tls
-%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=client-keystore.p12
+%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=target/certificates/oidc-client-keystore.p12
 %tls-registry.quarkus.tls.oidc-tls.key-store.jks.password=password
-%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=client-truststore.p12
+%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=target/certificates/oidc-client-truststore.p12
 %tls-registry.quarkus.tls.oidc-tls.trust-store.jks.password=password
+%tls-registry.quarkus.tls.oidc-tls.hostname-verification-algorithm=NONE
 %tls-registry.quarkus.oidc.tls.verification=
 %tls-registry.quarkus.oidc.tls.trust-store-file=
 %tls-registry.quarkus.oidc.tls.trust-store-password=
diff --git a/integration-tests/oidc/src/main/resources/client-keystore.p12 b/integration-tests/oidc/src/main/resources/client-keystore.p12
deleted file mode 100644
index 11df9af88cd7347c52c3133b1bb4c06dad923695..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2712
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

diff --git a/integration-tests/oidc/src/main/resources/client-truststore.p12 b/integration-tests/oidc/src/main/resources/client-truststore.p12
deleted file mode 100644
index 8a9cefe2f5506a9e6d069666946da65f351d4567..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1254
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

diff --git a/integration-tests/oidc/src/main/resources/server-truststore.p12 b/integration-tests/oidc/src/main/resources/server-truststore.p12
deleted file mode 100644
index d006d5d2dd43e1f3fb281ef9b0f70b62366d55d5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1238
zcmV;{1S$J4f&|h60Ru3C1bhYwDuzgg_YDCD0ic2eZ3Kb@X)uBWWiWySVFn2*hDe6@
z4FLxRpn?QaFoFb50s#Opf&@nf2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q02uTHfVto}cs>t6EcWV+4SL1JHTWWGObIPYjLO($cJE!p6hQ
zyi+RQq~7$Nt0^uplY-(Qg*6&o;t56~ILD{z`oq)>kGQ3aU4F?EVOGuP(4I7CGNb9H
zTH2%*k*_ys=fAZ@84#4HBY(?RIWiCfX0q#fiz*Anw@O0D*vt?B{pey7)3H8giqX*t
zoT3ziOQh{2IPUSW&+Uuz@+3-;(^QJI$S#}t`=Q8Bq(7B{WFq;I#E+cwSBc3cnMtgM
z?#Orx%n<2uc)~A>{`G;Hm5z2WwDhRNUv1I~NLz7O1$QD-+SDA{u#V^x2`|$mq8`?Z
z0Vhkh8FpP0!X8ob)ZDYc1Yxe|{`ls#`zpl<9AIVdJYRhDlb3lzS=`jOFK4-f#;#u3
zjo|d+yzb#twx(gwwUccyB)!L<8gqo2X~a72Q{sQ796U~JBN*s1L&~w~hCBzj?F{!`
zSf@7eSq=g#DIM2uufUIge<0RokWUH8USq;@uG4{?P}0dJQckA99BxrachA&KuYkzx<`~)CrtP
z@{uVebFGAQVA7=e+T)#Jf&8gJFYOvL=w7`OQw=)}`)_7!%waE?$18Pf*VwKa`D^s;
zz>m%2vdIz=g|l$x;9-eiLMoV|h)rGHr@F+9QhO;@xw+Zb@ge@w>mYRN#yqaMJ5S?e
zyXyfY!7D;EfU#NAbonIFa{)S@=!;7THxuf^N5D>={aVO4Cw%D8-N>fNn6c*%CifI$g8ony^L
za~KHBLb*MoAjFT3^Yh{U(Qm25G?a~UeD3INpyWw3B})@0^e7e0Ic2^c)g%Tfb;yurIc`ywk#<4b3FNC9O71OfpC00bafVWNS|wp1wX
zyU_TEQ5xQ0(1XIrK!>`w9r$zbKD_q?6k{F#kOL?OfWpAH=<&{v*odT2lL7)K5Nth0
A8vp start() {
diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
index 3a75d88294dc4..3c3323e40562d 100644
--- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
+++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java
@@ -27,7 +27,9 @@ public class WebsocketOidcTestCase {
     @TestHTTPResource("secured-hello")
     URI wsUri;
 
-    KeycloakTestClient client = new KeycloakTestClient(new Tls());
+    KeycloakTestClient client = new KeycloakTestClient(
+            new Tls("target/certificates/oidc-client-keystore.p12",
+                    "target/certificates/oidc-client-truststore.p12"));
 
     @Test
     public void websocketTest() throws Exception {
diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java
index 55d2b70561e22..f510c3dac6ec3 100644
--- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java
+++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java
@@ -455,6 +455,9 @@ public record Tls(String keystore, String keystorePassword,
         public Tls() {
             this("client-keystore.p12", "password", "client-truststore.p12", "password");
         }
-    };
 
+        public Tls(String keystore, String truststore) {
+            this(keystore, "password", truststore, "password");
+        }
+    };
 }

From d111801d29ddf9431fb33c4edcca0b775be1030a Mon Sep 17 00:00:00 2001
From: Ankush Saini 
Date: Fri, 13 Dec 2024 12:54:40 +0530
Subject: [PATCH 11/22] logstash conf file fix

(cherry picked from commit 99a12d951930c717321ca610120af3445048c0a2)
---
 docs/src/main/asciidoc/centralized-log-management.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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
   }
 }
 

From 1f679b2ea4c5729d993ae5343e0e516e7ecde31e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 13 Dec 2024 22:43:14 +0000
Subject: [PATCH 12/22] Bump org.hibernate.validator:hibernate-validator

Bumps [org.hibernate.validator:hibernate-validator](https://github.com/hibernate/hibernate-validator) from 8.0.1.Final to 8.0.2.Final.
- [Changelog](https://github.com/hibernate/hibernate-validator/blob/8.0.2.Final/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-validator/compare/8.0.1.Final...8.0.2.Final)

---
updated-dependencies:
- dependency-name: org.hibernate.validator:hibernate-validator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
(cherry picked from commit 6e039fb10a913c6e1288aea44244ba1f11c93f99)
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index e9fc13e768a25..f0bf21040d696 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,7 @@
         1.14.18 
         7.0.3.Final 
         2.4.2.Final 
-        8.0.1.Final
+        8.0.2.Final
         7.2.2.Final
 
         

From 629188f1bbc7412c6ece6b3eabf685db12962ecf Mon Sep 17 00:00:00 2001
From: Foivos Zakkak 
Date: Mon, 9 Dec 2024 15:15:10 +0200
Subject: [PATCH 13/22] Update documentation for handling proxies in
 native-mode

* Merge "Managing Proxy Classes" sections.
* Remove outdated mention to `-H:DynamicProxyConfigurationResources`

Quarkus no longer uses `-H:DynamicProxyConfigurationResources` for
dynamic proxies. Instead it generates the corresponding necessary
metadata in `META-INF/native-image/proxy-config.json`

(cherry picked from commit c09ade9ede0decf314b0bfa0c1e57bc4569dac54)
---
 .../writing-native-applications-tips.adoc     | 34 +++++++++----------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/docs/src/main/asciidoc/writing-native-applications-tips.adoc b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
index b7ca08afcc0c3..e4e19d35f0d73 100644
--- a/docs/src/main/asciidoc/writing-native-applications-tips.adoc
+++ b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
@@ -320,21 +320,6 @@ and in the case of using the Maven configuration instead of `application.propert
 ----
 ====
 
-=== Managing Proxy Classes
-
-While writing native application you'll need to define proxy classes at image build time by specifying the list of interfaces that they implement.
-
-In such a situation, the error you might encounter is:
-
-[source]
-----
-com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles= and -H:DynamicProxyConfigurationResources= options.
-----
-
-Solving this issue requires creating a `proxy-config.json` file under the `src/main/resources/META-INF/native-image//` folder.
-This way the configuration will be automatically parsed by the native build, without additional configuration.
-For more information about the format of this file, see the link:https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/metadata/#dynamic-proxy-metadata-in-json[Dynamic Proxy Metadata in JSON] documentation.
-
 [[modularity-benefits]]
 === Modularity Benefits
 
@@ -635,7 +620,16 @@ For more information about the `--initialize-at-run-time` option, see the link:h
 
 === Managing Proxy Classes
 
-Very similarly, Quarkus allows extensions authors to register a `NativeImageProxyDefinitionBuildItem`. An example of doing so is:
+While writing native application you'll need to define proxy classes at image build time by specifying the list of interfaces that they implement.
+
+In such a situation, the error you might encounter is:
+
+[source]
+----
+com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles= and -H:DynamicProxyConfigurationResources= options.
+----
+
+Quarkus allows extensions authors to register a `NativeImageProxyDefinitionBuildItem`. An example of doing so is:
 
 [source,java]
 ----
@@ -650,11 +644,15 @@ public class S3Processor {
 }
 ----
 
-Using such a construct means that a `-H:DynamicProxyConfigurationResources` option will automatically be added to the `native-image` command line.
+This will allow Quarkus to generate the necessary configuration for handling the proxy class.
+Alternatively, you may create a `proxy-config.json` file under the `src/main/resources/META-INF/native-image//` folder.
+For more information about the format of this file, see the https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/metadata/#dynamic-proxy-metadata-in-json[Dynamic Proxy Metadata in JSON] documentation.
 
 [NOTE]
 ====
-For more information about Proxy Classes, see the link:https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/guides/configure-dynamic-proxies/[GraalVM Configure Dynamic Proxies Manually] guide.
+In both cases the configuration will be automatically parsed by the native build, without additional configuration.
+
+For more information about using Proxy Classes in native executables, see https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/DynamicProxy/[Dynamic Proxy in Native Image] and https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/guides/configure-dynamic-proxies/[GraalVM Configure Dynamic Proxies Manually].
 ====
 
 === Logging with Native Image

From 72f3a7003ced5d92dcbbb5b1a54515ba0fd39e51 Mon Sep 17 00:00:00 2001
From: Sergey Beryozkin 
Date: Mon, 16 Dec 2024 13:33:23 +0000
Subject: [PATCH 14/22] Update OIDC bearer doc with a section about response
 filters

(cherry picked from commit e72472f28cee16b706849b1d97d4c2893a761023)
---
 ...rity-oidc-bearer-token-authentication.adoc | 43 +++++++++++++++++++
 ...ecurity-oidc-code-flow-authentication.adoc |  8 ++--
 2 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
index 621d512c15c12..bb2556047e43c 100644
--- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
+++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc
@@ -1345,6 +1345,49 @@ Authentication that requires a dynamic tenant will fail.
 You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, and log requests.
 For more information, see xref:security-oidc-code-flow-authentication#code-flow-oidc-request-filters[OIDC request filters].
 
+[[bearer-token-oidc-response-filters]]
+=== OIDC response filters
+
+You can filter responses from the OIDC providers by registering one or more `OidcResponseFilter` implementations, which can check the response status, headers and body in order to log them or perform other actions.
+
+You can have a single filter intercepting all the OIDC responses, or use an `@OidcEndpoint` annotation to apply this filter to the specific endpoint responses only. For example:
+
+[source,java]
+----
+package io.quarkus.it.keycloak;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkus.arc.Unremovable;
+import io.quarkus.logging.Log;
+import io.quarkus.oidc.common.OidcEndpoint;
+import io.quarkus.oidc.common.OidcEndpoint.Type;
+import io.quarkus.oidc.common.OidcResponseFilter;
+import io.quarkus.oidc.common.runtime.OidcConstants;
+import io.quarkus.oidc.runtime.OidcUtils;
+
+@ApplicationScoped
+@Unremovable
+@OidcEndpoint(value = Type.DISCOVERY) <1>
+public class DiscoveryEndpointResponseFilter implements OidcResponseFilter {
+
+    @Override
+    public void filter(OidcResponseContext rc) {
+        String contentType = rc.responseHeaders().get("Content-Type"); <2>
+        if (contentType.equals("application/json") {
+            String tenantId = rc.requestProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE); <3>
+            String metadata = rc.responseBody().toString(); <4>
+            Log.debugf("Tenant %s OIDC metadata: %s", tenantId, metadata);
+        }
+    }
+}
+
+----
+<1> Restrict this filter to requests targeting the OIDC discovery endpoint only.
+<2> Check the response `Content-Type` header.
+<3> Use `OidcRequestContextProperties` request properties to get the tenant id.
+<4> Get the response data as String.
+
 == References
 
 * xref:security-oidc-configuration-properties-reference.adoc[OIDC configuration properties]
diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
index 3dbd97e8eb429..5d48a7f3ebbfb 100644
--- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
+++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
@@ -392,9 +392,8 @@ package io.quarkus.it.keycloak;
 
 import jakarta.enterprise.context.ApplicationScoped;
 
-import org.jboss.logging.Logger;
-
 import io.quarkus.arc.Unremovable;
+import io.quarkus.logging.Log;
 import io.quarkus.oidc.common.OidcEndpoint;
 import io.quarkus.oidc.common.OidcEndpoint.Type;
 import io.quarkus.oidc.common.OidcResponseFilter;
@@ -405,8 +404,7 @@ import io.quarkus.oidc.runtime.OidcUtils;
 @Unremovable
 @OidcEndpoint(value = Type.TOKEN) <1>
 public class TokenEndpointResponseFilter implements OidcResponseFilter {
-    private static final Logger LOG = Logger.getLogger(TokenResponseFilter.class);
-
+    
     @Override
     public void filter(OidcResponseContext rc) {
         String contentType = rc.responseHeaders().get("Content-Type"); <2>
@@ -414,7 +412,7 @@ public class TokenEndpointResponseFilter implements OidcResponseFilter {
                 && OidcConstants.AUTHORIZATION_CODE.equals(rc.requestProperties().get(OidcConstants.GRANT_TYPE)) <3>
                 && "code-flow-user-info-cached-in-idtoken".equals(rc.requestProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE)) <3>
                 && rc.responseBody().toJsonObject().containsKey("id_token")) { <4>
-            LOG.debug("Authorization code completed for tenant 'code-flow-user-info-cached-in-idtoken'");
+            Log.debug("Authorization code completed for tenant 'code-flow-user-info-cached-in-idtoken'");
         }
     }
 }

From df84c0dc8a279920b7871a6c011099d3e377d704 Mon Sep 17 00:00:00 2001
From: George Gastaldi 
Date: Mon, 16 Dec 2024 18:36:29 -0300
Subject: [PATCH 15/22] Bump org.asynchttpclient:async-http-client from 2.12.3
 to 2.12.4

Bumps [org.asynchttpclient:async-http-client](https://github.com/AsyncHttpClient/async-http-client) from 2.12.3 to 2.12.4.
- [Release notes](https://github.com/AsyncHttpClient/async-http-client/releases)
- [Changelog](https://github.com/AsyncHttpClient/async-http-client/blob/main/CHANGES.md)
- [Commits](AsyncHttpClient/async-http-client@async-http-client-project-2.12.3...async-http-client-project-2.12.4)

---
updated-dependencies:
- dependency-name: org.asynchttpclient:async-http-client
  dependency-type: direct:production
...

(cherry picked from commit 731ef3a916f390915a46058012de5e58a7787902)
---
 bom/application/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 32bfa1030cc4c..4f099b47a75b5 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -214,7 +214,7 @@
         0.8.11
         1.1.0
         3.3.0
-        2.12.3
+        2.12.4
         
         0.16.0
         

From 82cda4381600f074a308090786f222998da08454 Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Mon, 16 Dec 2024 09:11:38 +0200
Subject: [PATCH 16/22] Break build cycle between OTel, logging and Flyway

Fixes: #45130
(cherry picked from commit 327341c79a719650ba5a5eea1f14512ed506b21b)
---
 .../main/java/io/quarkus/flyway/deployment/FlywayProcessor.java | 2 --
 1 file changed, 2 deletions(-)

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,

From dda0b149b4181db10de06e7657f1e37788ad0118 Mon Sep 17 00:00:00 2001
From: Auri Munoz 
Date: Fri, 13 Dec 2024 12:23:23 +0100
Subject: [PATCH 17/22] Register for reflection Pageable class for not missing
 paged/unpaged attributes.

Related to #41292

(cherry picked from commit 57c250d70157074941a0fa5fe7fc99ffc8b8d324)
---
 .../data/deployment/SpringDataJPAProcessor.java  |  1 +
 .../it/spring/data/jpa/BookRepository.java       |  9 +++++++++
 .../quarkus/it/spring/data/jpa/BookResource.java | 10 ++++++++++
 .../it/spring/data/jpa/BookResourceTest.java     | 16 ++++++++++++++++
 4 files changed, 36 insertions(+)

diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java
index 104d9167067b7..2b893982be4f0 100644
--- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java
+++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java
@@ -103,6 +103,7 @@ void registerReflection(BuildProducer producer) {
                 "org.springframework.data.domain.Page",
                 "org.springframework.data.domain.Slice",
                 "org.springframework.data.domain.PageImpl",
+                "org.springframework.data.domain.Pageable",
                 "org.springframework.data.domain.SliceImpl",
                 "org.springframework.data.domain.Sort",
                 "org.springframework.data.domain.Chunk",
diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookRepository.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookRepository.java
index e68d9c856ff73..e6802086398c1 100644
--- a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookRepository.java
+++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookRepository.java
@@ -3,6 +3,9 @@
 import java.util.List;
 import java.util.Optional;
 
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.Repository;
 import org.springframework.data.repository.query.Param;
@@ -45,6 +48,12 @@ public interface BookRepository extends Repository {
     @Query(value = "SELECT b.publicationYear FROM Book b where b.bid = :bid")
     Integer customFindPublicationYearObject(@Param("bid") Integer bid);
 
+    // Related to issue 41292
+    public default Page findPaged(Pageable pageable) {
+        List list = findAll();
+        return new PageImpl<>(list, pageable, list.size());
+    }
+
     interface BookCountByYear {
         int getPublicationYear();
 
diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookResource.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookResource.java
index 55e6342d340e3..2db6fcd4d5f73 100644
--- a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookResource.java
+++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/BookResource.java
@@ -8,8 +8,12 @@
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.Response;
 
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+
 @Path("/book")
 public class BookResource {
 
@@ -115,4 +119,10 @@ public Integer customFindPublicationYearObject(@PathParam("bid") Integer bid) {
         return bookRepository.customFindPublicationYearObject(bid);
     }
 
+    @GET
+    @Path("/paged")
+    public Page getPaged(@QueryParam("size") int size, @QueryParam("page") int page) {
+        return bookRepository.findPaged(PageRequest.of(page, size));
+    }
+
 }
diff --git a/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/BookResourceTest.java b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/BookResourceTest.java
index 3bd490e6594ee..318cd12659c13 100644
--- a/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/BookResourceTest.java
+++ b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/BookResourceTest.java
@@ -1,13 +1,17 @@
 package io.quarkus.it.spring.data.jpa;
 
+import static io.restassured.RestAssured.given;
 import static io.restassured.RestAssured.when;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.core.Is.is;
 
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.response.Response;
 
 @QuarkusTest
 public class BookResourceTest {
@@ -132,4 +136,16 @@ void testCustomFindPublicationYearObject() {
                 .statusCode(200)
                 .body(is("2011"));
     }
+
+    @Test
+    void testEnsureFieldPageableIsSerialized() {
+        Response response = given()
+                .accept("application/json")
+                .queryParam("size", "2")
+                .queryParam("page", "1")
+                .when().get("/book/paged");
+        Assertions.assertEquals(200, response.statusCode());
+        assertThat(response.body().jsonPath().getString("pageable")).contains("paged:true");
+        assertThat(response.body().jsonPath().getString("pageable")).contains("unpaged:false");
+    }
 }

From 44f09de33adc21e206c08807a02ef13758b2bcfb Mon Sep 17 00:00:00 2001
From: Foivos Zakkak 
Date: Tue, 17 Dec 2024 11:13:17 +0200
Subject: [PATCH 18/22] Improve documentation for handling proxies in
 native-mode

Follow up to https://github.com/quarkusio/quarkus/pull/45004

(cherry picked from commit 036cc7fc1c1bc18222ded80865e8ea886d100aa6)
---
 .../writing-native-applications-tips.adoc     | 33 ++++++++++++-------
 1 file changed, 21 insertions(+), 12 deletions(-)

diff --git a/docs/src/main/asciidoc/writing-native-applications-tips.adoc b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
index e4e19d35f0d73..32a6917cc22e0 100644
--- a/docs/src/main/asciidoc/writing-native-applications-tips.adoc
+++ b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
@@ -320,6 +320,23 @@ and in the case of using the Maven configuration instead of `application.propert
 ----
 ====
 
+[[managing-proxy-classes-app]]
+=== Managing Proxy Classes
+
+While writing native application you'll need to define proxy classes at image build time by specifying the list of interfaces that they implement.
+
+In such a situation, the error you might encounter is:
+
+[source]
+----
+com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles= and -H:DynamicProxyConfigurationResources= options.
+----
+
+To solve the issue you can create a `proxy-config.json` file under the `src/main/resources/META-INF/native-image//` folder.
+For more information about the format of the `proxy-config.json`, see the https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/metadata/#dynamic-proxy-metadata-in-json[Dynamic Proxy Metadata in JSON] documentation.
+
+Alternatively, you can create a quarkus extension and register the proxy classes as described in <>.
+
 [[modularity-benefits]]
 === Modularity Benefits
 
@@ -618,18 +635,10 @@ Using such a construct means that a `--initialize-at-run-time` option will autom
 For more information about the `--initialize-at-run-time` option, see the link:https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/optimizations-and-performance/ClassInitialization/[GraalVM Class Initialization in Native Image] guide.
 ====
 
+[[managing-proxy-classes-extension]]
 === Managing Proxy Classes
 
-While writing native application you'll need to define proxy classes at image build time by specifying the list of interfaces that they implement.
-
-In such a situation, the error you might encounter is:
-
-[source]
-----
-com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles= and -H:DynamicProxyConfigurationResources= options.
-----
-
-Quarkus allows extensions authors to register a `NativeImageProxyDefinitionBuildItem`. An example of doing so is:
+Similarly, Quarkus allows extensions authors to register a `NativeImageProxyDefinitionBuildItem`. An example of doing so is:
 
 [source,java]
 ----
@@ -645,8 +654,8 @@ public class S3Processor {
 ----
 
 This will allow Quarkus to generate the necessary configuration for handling the proxy class.
-Alternatively, you may create a `proxy-config.json` file under the `src/main/resources/META-INF/native-image//` folder.
-For more information about the format of this file, see the https://www.graalvm.org/{graalvm-docs-version}/reference-manual/native-image/metadata/#dynamic-proxy-metadata-in-json[Dynamic Proxy Metadata in JSON] documentation.
+
+Alternatively, you may create a `proxy-config.json` as described in <>.
 
 [NOTE]
 ====

From 6359779924a61265a9e9979b6a3909be2f04fec8 Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Tue, 17 Dec 2024 10:54:08 +0200
Subject: [PATCH 19/22] Fix local proxy handling in REST Client module

With the previous implementation, Cloudfare was
blocking requests coming from the local proxy

(cherry picked from commit 64c5045117044538c88d0c2e235d272dd18d7bfd)
---
 ...vServicesRestClientHttpProxyProcessor.java |  8 +++---
 ...oxyDevServicesRestClientProxyProvider.java | 27 ++++++++++---------
 2 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java
index 6e2eb61e3f12c..8672ee205a985 100644
--- a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java
+++ b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java
@@ -211,10 +211,12 @@ public void start(List restClientHttpProxyBuildIte
 
             var urlKeyName = String.format("quarkus.rest-client.\"%s\".override-uri", bi.getClassName());
             var urlKeyValue = String.format("http://%s:%d", createResult.host(), createResult.port());
-            if (baseUri.getPath() != null) {
-                if (!"/".equals(baseUri.getPath()) && !baseUri.getPath().isEmpty()) {
-                    urlKeyValue = urlKeyValue + "/" + baseUri.getPath();
+            String basePath = baseUri.getPath();
+            if ((basePath != null) && !basePath.isEmpty()) {
+                if (basePath.startsWith("/")) {
+                    basePath = basePath.substring(1);
                 }
+                urlKeyValue = urlKeyValue + "/" + basePath;
             }
 
             devServicePropertiesProducer.produce(
diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/VertxHttpProxyDevServicesRestClientProxyProvider.java b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/VertxHttpProxyDevServicesRestClientProxyProvider.java
index 4965a4d35c457..73d457ce4a3ca 100644
--- a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/VertxHttpProxyDevServicesRestClientProxyProvider.java
+++ b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/VertxHttpProxyDevServicesRestClientProxyProvider.java
@@ -15,7 +15,6 @@
 import io.quarkus.rest.client.reactive.spi.RestClientHttpProxyBuildItem;
 import io.quarkus.runtime.ResettableSystemProperties;
 import io.vertx.core.Future;
-import io.vertx.core.MultiMap;
 import io.vertx.core.Vertx;
 import io.vertx.core.VertxOptions;
 import io.vertx.core.file.FileSystemOptions;
@@ -23,6 +22,7 @@
 import io.vertx.core.http.HttpClientOptions;
 import io.vertx.core.http.HttpServer;
 import io.vertx.core.metrics.MetricsOptions;
+import io.vertx.core.net.HostAndPort;
 import io.vertx.httpproxy.HttpProxy;
 import io.vertx.httpproxy.ProxyContext;
 import io.vertx.httpproxy.ProxyInterceptor;
@@ -71,16 +71,18 @@ public CreateResult create(RestClientHttpProxyBuildItem buildItem) {
         }
         HttpClient proxyClient = vertx.get().createHttpClient(clientOptions);
         HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
-        proxy.origin(determineOriginPort(baseUri), baseUri.getHost())
-                .addInterceptor(new HostSettingInterceptor(baseUri.getHost()));
+        int targetPort = determineOriginPort(baseUri);
+        String targetHost = baseUri.getHost();
+        proxy.origin(targetPort, targetHost)
+                .addInterceptor(new AuthoritySettingInterceptor(targetPort, targetHost));
 
         HttpServer proxyServer = vertx.get().createHttpServer();
-        Integer port = findRandomPort();
-        proxyServer.requestHandler(proxy).listen(port);
+        Integer proxyPort = findRandomPort();
+        proxyServer.requestHandler(proxy).listen(proxyPort);
 
-        logStartup(buildItem.getClassName(), port);
+        logStartup(buildItem.getClassName(), proxyPort);
 
-        return new CreateResult("localhost", port, new HttpServerClosable(proxyServer));
+        return new CreateResult("localhost", proxyPort, new HttpServerClosable(proxyServer));
     }
 
     protected void logStartup(String className, Integer port) {
@@ -124,19 +126,18 @@ private Integer findRandomPort() {
      * This class sets the Host HTTP Header in order to avoid having services being blocked
      * for presenting a wrong value
      */
-    private static class HostSettingInterceptor implements ProxyInterceptor {
+    private static class AuthoritySettingInterceptor implements ProxyInterceptor {
 
-        private final String host;
+        private final HostAndPort authority;
 
-        private HostSettingInterceptor(String host) {
-            this.host = host;
+        private AuthoritySettingInterceptor(int targetPort, String host) {
+            this.authority = HostAndPort.authority(host, targetPort);
         }
 
         @Override
         public Future handleProxyRequest(ProxyContext context) {
             ProxyRequest request = context.request();
-            MultiMap headers = request.headers();
-            headers.set("Host", host);
+            request.setAuthority(authority);
 
             return context.sendRequest();
         }

From ac91de218f8915cb40d4f5dcc1f03a3b0f2b9bf5 Mon Sep 17 00:00:00 2001
From: Maciej Lisowski 
Date: Tue, 17 Dec 2024 14:36:29 +0100
Subject: [PATCH 20/22] Docs: Correct word form in Native Applications Tips

Signed-off-by: Maciej Lisowski 
(cherry picked from commit 732833d6b7f3b66fcb5965d999c07d7ea7a3ade7)
---
 docs/src/main/asciidoc/writing-native-applications-tips.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/src/main/asciidoc/writing-native-applications-tips.adoc b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
index 32a6917cc22e0..3a75185d2411b 100644
--- a/docs/src/main/asciidoc/writing-native-applications-tips.adoc
+++ b/docs/src/main/asciidoc/writing-native-applications-tips.adoc
@@ -197,7 +197,7 @@ public class MyReflectionConfiguration {
 }
 ----
 
-Note: By default the `@RegisterForReflection` annotation will also registered any potential nested classes for reflection. If you want to avoid this behavior, you can set the `ignoreNested` attribute to `true`.
+Note: By default the `@RegisterForReflection` annotation will also register any potential nested classes for reflection. If you want to avoid this behavior, you can set the `ignoreNested` attribute to `true`.
 
 ==== Using a configuration file
 

From b73587d7df9a5ad6b3e256cbb1b4e79d34caa573 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Barto=C5=A1?= 
Date: Tue, 17 Dec 2024 15:22:15 +0100
Subject: [PATCH 21/22] Unable to use custom handlers for HTTP OPTIONS method
 in subresources
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes #45173

Signed-off-by: Martin Bartoš 
(cherry picked from commit 349d540e8275e0cd0f1cb543387c9ebcf78ff973)
---
 .../resource/basic/ResourceLocatorTest.java   | 50 ++++++++++++++++++-
 .../basic/resource/CorsPreflightResource.java | 17 +++++++
 .../resource/ResourceLocatorBaseResource.java |  7 +++
 .../resource/ResourceLocatorSubresource.java  | 17 +++++++
 .../resource/ResourceLocatorSubresource2.java | 11 +++-
 .../handlers/ResourceLocatorHandler.java      | 42 ++++++++--------
 6 files changed, 121 insertions(+), 23 deletions(-)
 create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/CorsPreflightResource.java

diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java
index 2f610a82cce42..ba94a8dd38463 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/ResourceLocatorTest.java
@@ -1,10 +1,15 @@
 package io.quarkus.resteasy.reactive.server.test.resource.basic;
 
 import static io.restassured.RestAssured.given;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
 
+import java.util.Arrays;
 import java.util.function.Supplier;
 
+import jakarta.ws.rs.HttpMethod;
 import jakarta.ws.rs.client.Client;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.Entity;
@@ -22,6 +27,7 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.CorsPreflightResource;
 import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorAbstractAnnotationFreeResource;
 import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorAnnotationFreeSubResource;
 import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.ResourceLocatorBaseResource;
@@ -68,7 +74,7 @@ public JavaArchive get() {
                     JavaArchive war = ShrinkWrap.create(JavaArchive.class);
                     war.addClass(ResourceLocatorQueueReceiver.class).addClass(ResourceLocatorReceiver.class)
                             .addClass(ResourceLocatorRootInterface.class).addClass(ResourceLocatorSubInterface.class)
-                            .addClass(ResourceLocatorSubresource3Interface.class);
+                            .addClass(ResourceLocatorSubresource3Interface.class).addClass(CorsPreflightResource.class);
                     war.addClasses(PortProviderUtil.class, ResourceLocatorAbstractAnnotationFreeResource.class,
                             ResourceLocatorAnnotationFreeSubResource.class, ResourceLocatorBaseResource.class,
                             ResourceLocatorCollectionResource.class, ResourceLocatorDirectory.class,
@@ -114,6 +120,48 @@ public void testSubresource() throws Exception {
         }
     }
 
+    /**
+     * @tpTestDetails Return custom handler for HTTP OPTIONS method in subresource redirection. The
+     *                {@link CorsPreflightResource} instance should be returned
+     */
+    @Test
+    @DisplayName("Test custom HTTP OPTIONS handler in subresource")
+    public void testOptionsMethodInSubresource() {
+        try (Response response = client.target(generateURL("/sub3/something/resources/test-options-method")).request()
+                .options()) {
+            assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
+
+            var customHeader = response.getHeaderString(CorsPreflightResource.TEST_PREFLIGHT_HEADER);
+            assertThat(customHeader, notNullValue());
+            assertThat(customHeader, is("test"));
+
+            var allowHeader = response.getHeaderString("Allow");
+            assertThat(allowHeader, notNullValue());
+            assertThat(Arrays.asList(allowHeader.split(", ")),
+                    containsInAnyOrder(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS, HttpMethod.HEAD));
+        }
+    }
+
+    /**
+     * @tpTestDetails Custom handler for HTTP OPTIONS method in subresource.
+     */
+    @Test
+    @DisplayName("Test custom explicit HTTP OPTIONS handler in subresource")
+    public void testOptionsMethodExplicitInSubresource() {
+        try (Response response = client.target(generateURL("/sub3/something/resources/test-options-method-explicit")).request()
+                .options()) {
+            assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
+
+            var customHeader = response.getHeaderString(ResourceLocatorSubresource2.TEST_PREFLIGHT_HEADER);
+            assertThat(customHeader, notNullValue());
+            assertThat(customHeader, is("test"));
+
+            var allowHeader = response.getHeaderString("Allow");
+            assertThat(allowHeader, notNullValue());
+            assertThat(Arrays.asList(allowHeader.split(", ")), containsInAnyOrder(HttpMethod.GET));
+        }
+    }
+
     /**
      * @tpTestDetails Two matching methods, one a resource locator, the other a resource method.
      * @tpSince RESTEasy 3.0.20
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/CorsPreflightResource.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/CorsPreflightResource.java
new file mode 100644
index 0000000000000..d1b56b49d77fa
--- /dev/null
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/CorsPreflightResource.java
@@ -0,0 +1,17 @@
+package io.quarkus.resteasy.reactive.server.test.resource.basic.resource;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.OPTIONS;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+public class CorsPreflightResource {
+    public static final String TEST_PREFLIGHT_HEADER = "preflight-header-test";
+
+    @Path("{any:.*}")
+    @OPTIONS
+    public Response preflight() {
+        return Response.ok().allow(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS, HttpMethod.HEAD)
+                .header(TEST_PREFLIGHT_HEADER, "test").build();
+    }
+}
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorBaseResource.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorBaseResource.java
index 308a4a738be87..ab09e88f1643c 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorBaseResource.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorBaseResource.java
@@ -6,6 +6,7 @@
 import java.util.List;
 
 import jakarta.ws.rs.GET;
+import jakarta.ws.rs.OPTIONS;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
@@ -61,4 +62,10 @@ public ResourceLocatorSubresource getSubresource() {
         return new ResourceLocatorSubresource();
     }
 
+    @OPTIONS
+    @Path("{any:.*}")
+    public Object preflight() {
+        return "Here might be a custom handler for HTTP OPTIONS method";
+    }
+
 }
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource.java
index dfb0d717642ef..58261ac763c3b 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource.java
@@ -4,6 +4,7 @@
 
 import jakarta.ws.rs.BeanParam;
 import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.Context;
@@ -13,6 +14,8 @@
 import org.jboss.resteasy.reactive.RestPath;
 import org.junit.jupiter.api.Assertions;
 
+import io.vertx.core.http.HttpServerRequest;
+
 public class ResourceLocatorSubresource {
 
     private static final Logger LOG = Logger.getLogger(ResourceLocatorSubresource.class);
@@ -62,6 +65,20 @@ public String getValueFromBeanParam(@BeanParam Params params) {
         return params.param + " and " + params.value;
     }
 
+    @Path("/test-options-method-explicit")
+    public Object testOptionsMethodExplicit() {
+        return new ResourceLocatorSubresource2();
+    }
+
+    @Path("/test-options-method")
+    public Object testOptionsMethod(@Context HttpServerRequest request) {
+        if (request.method().name().equals(HttpMethod.OPTIONS)) {
+            return new CorsPreflightResource();
+        }
+
+        return "Should be used only with HTTP @OPTIONS method";
+    }
+
     public static class Params {
         @RestPath
         String param;
diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource2.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource2.java
index da745c63579ec..677ba369a33ec 100644
--- a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource2.java
+++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/ResourceLocatorSubresource2.java
@@ -1,16 +1,19 @@
 package io.quarkus.resteasy.reactive.server.test.resource.basic.resource;
 
 import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.OPTIONS;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.UriInfo;
 
 import org.jboss.logging.Logger;
 import org.junit.jupiter.api.Assertions;
 
 public class ResourceLocatorSubresource2 {
-
+    public static final String TEST_PREFLIGHT_HEADER = "test-preflight-header";
     private static final Logger LOG = Logger.getLogger(ResourceLocatorSubresource2.class);
 
     @GET
@@ -35,4 +38,10 @@ public String doGet(@PathParam("param") String param, @Context UriInfo uri) {
         Assertions.assertEquals("2", param);
         return this.getClass().getName() + "-" + param;
     }
+
+    @OPTIONS
+    @Path("{any:.*}")
+    public Response preflight() {
+        return Response.ok().allow(HttpMethod.GET).header(TEST_PREFLIGHT_HEADER, "test").build();
+    }
 }
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceLocatorHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceLocatorHandler.java
index 83215887b91af..9dafe4e57b544 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceLocatorHandler.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceLocatorHandler.java
@@ -63,29 +63,29 @@ public void onComplete(Throwable throwable) {
         RequestMapper mapper = target.get(requestContext.getMethod());
         boolean hadNullMethodMapper = false;
         if (mapper == null) {
-            String requestMethod = requestContext.getMethod();
-            if (requestMethod.equals(HttpMethod.HEAD)) {
-                mapper = target.get(HttpMethod.GET);
-            } else if (requestMethod.equals(HttpMethod.OPTIONS)) {
-                Set allowedMethods = new HashSet<>();
-                for (String method : target.keySet()) {
-                    if (method == null) {
-                        continue;
-                    }
-                    allowedMethods.add(method);
-                }
-                allowedMethods.add(HttpMethod.OPTIONS);
-                allowedMethods.add(HttpMethod.HEAD);
-                requestContext.abortWith(Response.ok().allow(allowedMethods).build());
-                return;
-            }
+            mapper = target.get(null); //another layer of resource locators maybe
+            // we set this without checking if we matched, but we only use it after
+            // we check for a null mapper, so by the time we use it, it must have meant that
+            // we had a matcher for a null method
+            hadNullMethodMapper = true;
 
             if (mapper == null) {
-                mapper = target.get(null); //another layer of resource locators maybe
-                // we set this without checking if we matched, but we only use it after
-                // we check for a null mapper, so by the time we use it, it must have meant that
-                // we had a matcher for a null method
-                hadNullMethodMapper = true;
+                String requestMethod = requestContext.getMethod();
+                if (requestMethod.equals(HttpMethod.HEAD)) {
+                    mapper = target.get(HttpMethod.GET);
+                } else if (requestMethod.equals(HttpMethod.OPTIONS)) {
+                    Set allowedMethods = new HashSet<>();
+                    for (String method : target.keySet()) {
+                        if (method == null) {
+                            continue;
+                        }
+                        allowedMethods.add(method);
+                    }
+                    allowedMethods.add(HttpMethod.OPTIONS);
+                    allowedMethods.add(HttpMethod.HEAD);
+                    requestContext.abortWith(Response.ok().allow(allowedMethods).build());
+                    return;
+                }
             }
         }
         if (mapper == null) {

From c6608bfc052b7bc709a132411cde4148e954a624 Mon Sep 17 00:00:00 2001
From: Guillaume Smet 
Date: Wed, 18 Dec 2024 17:37:49 +0100
Subject: [PATCH 22/22] Fix matrix computation for Ubuntu 24

(cherry picked from commit 38f44a759e77ab8599788b0faa40f2668ff1a1e4)
---
 .github/workflows/ci-actions-incremental.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

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