From 091967c1dba74d8a3e8bde533de5773ee5a12b7e Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Wed, 15 Nov 2023 20:13:17 +0100 Subject: [PATCH 001/212] Bump Smallrye RM from 4.10.1 to 4.10.2 With RabbitMQ Tracing fix --- 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 7e04f0438a780..1292752df2858 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -61,7 +61,7 @@ 1.0.13 3.0.1 3.7.2 - 4.10.1 + 4.10.2 2.4.0 2.1.2 2.1.1 From 5362a838cf4b1b6fc6bf554cde09973d2e881b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 27 Oct 2023 14:54:09 +0200 Subject: [PATCH 002/212] Allow using a random test port within Google Cloud Function tests Fixes #35476 (cherry picked from commit 12e0afcf5f710e900a6942cfe7f0fb9c5313b6e7) --- .../main/asciidoc/funqy-gcp-functions.adoc | 2 ++ docs/src/main/asciidoc/gcp-functions.adoc | 2 ++ .../test/HttpFunctionRandomPortTestCase.java | 31 +++++++++++++++++++ .../functions/test/CloudFunctionsInvoker.java | 6 +++- .../cloud/functions/test/SocketUtil.java | 29 +++++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 integration-tests/google-cloud-functions/src/test/java/io/quarkus/gcp/function/test/HttpFunctionRandomPortTestCase.java create mode 100644 test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/SocketUtil.java diff --git a/docs/src/main/asciidoc/funqy-gcp-functions.adoc b/docs/src/main/asciidoc/funqy-gcp-functions.adoc index 5a6b9fa9329dd..165a2a3900a15 100644 --- a/docs/src/main/asciidoc/funqy-gcp-functions.adoc +++ b/docs/src/main/asciidoc/funqy-gcp-functions.adoc @@ -385,6 +385,8 @@ To use it, you must add the following test dependency in your `pom.xml`. This extension provides a `@WithFunction` annotation that can be used to annotate `@QuarkusTest` test cases to start a Cloud Function invoker before you test cases and stop it at the end. This annotation must be configured with the type of the function you want to launch, and optionally the name of the function in case you have multiple functions inside your application. +The default Quarkus test port configuration (`quarkus.http.test-port`) will be honored and if you set it to 0 a random port will be assigned to the function invoker. + === Background Functions - PubSub [source,java] diff --git a/docs/src/main/asciidoc/gcp-functions.adoc b/docs/src/main/asciidoc/gcp-functions.adoc index d0ce3a5a1fe97..151d99319e314 100644 --- a/docs/src/main/asciidoc/gcp-functions.adoc +++ b/docs/src/main/asciidoc/gcp-functions.adoc @@ -490,6 +490,8 @@ To use it, you must add the following test dependency in your `pom.xml`. This extension provides a `@WithFunction` annotation that can be used to annotate `@QuarkusTest` test cases to start a Cloud Function invoker before you test cases and stop it at the end. This annotation must be configured with the type of the function you want to launch, and optionally the name of the function in case you have multiple functions inside your application. +The default Quarkus test port configuration (`quarkus.http.test-port`) will be honored and if you set it to 0 a random port will be assigned to the function invoker. + === The HttpFunction [source,java] diff --git a/integration-tests/google-cloud-functions/src/test/java/io/quarkus/gcp/function/test/HttpFunctionRandomPortTestCase.java b/integration-tests/google-cloud-functions/src/test/java/io/quarkus/gcp/function/test/HttpFunctionRandomPortTestCase.java new file mode 100644 index 0000000000000..62bc600f3ff76 --- /dev/null +++ b/integration-tests/google-cloud-functions/src/test/java/io/quarkus/gcp/function/test/HttpFunctionRandomPortTestCase.java @@ -0,0 +1,31 @@ +package io.quarkus.gcp.function.test; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.google.cloud.functions.test.FunctionType; +import io.quarkus.google.cloud.functions.test.WithFunction; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@WithFunction(FunctionType.HTTP) +class HttpFunctionRandomPortTestCase { + + @BeforeAll + public static void setup() { + System.setProperty("quarkus.http.test-port", "0"); + } + + @Test + public void test() { + // test the function using RestAssured + when() + .get() + .then() + .statusCode(200) + .body(is("Hello World!")); + } +} diff --git a/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/CloudFunctionsInvoker.java b/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/CloudFunctionsInvoker.java index 7398177930c74..efe8648db5351 100644 --- a/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/CloudFunctionsInvoker.java +++ b/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/CloudFunctionsInvoker.java @@ -11,8 +11,12 @@ class CloudFunctionsInvoker { } CloudFunctionsInvoker(FunctionType functionType, int port) { + int realPort = port == 0 ? SocketUtil.findAvailablePort() : port; + if (realPort != port) { + System.setProperty("quarkus.http.test-port", String.valueOf(realPort)); + } this.invoker = new Invoker( - port, + realPort, functionType.getTarget(), functionType.getSignatureType(), Thread.currentThread().getContextClassLoader()); diff --git a/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/SocketUtil.java b/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/SocketUtil.java new file mode 100644 index 0000000000000..cc29160ef51f0 --- /dev/null +++ b/test-framework/google-cloud-functions/src/main/java/io/quarkus/google/cloud/functions/test/SocketUtil.java @@ -0,0 +1,29 @@ +package io.quarkus.google.cloud.functions.test; + +import java.io.IOException; +import java.net.ServerSocket; + +// copied from the mailer extension: io.quarkus.mailer.runtime.SocketUtil +final class SocketUtil { + + private SocketUtil() { + } + + static int findAvailablePort() { + ServerSocket serverSocket = null; + try { + serverSocket = new ServerSocket(0); + return serverSocket.getLocalPort(); + } catch (Exception e) { + // return a default port + return 25347; + } finally { + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e) { + } + } + } + } +} From 421f3ae4f8d5dcb53a93b25d5a53ce668ff68e3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:51:59 +0000 Subject: [PATCH 003/212] Bump mongo-client.version from 4.11.0 to 4.11.1 Bumps `mongo-client.version` from 4.11.0 to 4.11.1. Updates `org.mongodb:mongodb-driver-sync` from 4.11.0 to 4.11.1 - [Release notes](https://github.com/mongodb/mongo-java-driver/releases) - [Commits](https://github.com/mongodb/mongo-java-driver/compare/r4.11.0...r4.11.1) Updates `org.mongodb:mongodb-driver-core` from 4.11.0 to 4.11.1 - [Release notes](https://github.com/mongodb/mongo-java-driver/releases) - [Commits](https://github.com/mongodb/mongo-java-driver/compare/r4.11.0...r4.11.1) Updates `org.mongodb:mongodb-driver-legacy` from 4.11.0 to 4.11.1 - [Release notes](https://github.com/mongodb/mongo-java-driver/releases) - [Commits](https://github.com/mongodb/mongo-java-driver/compare/r4.11.0...r4.11.1) Updates `org.mongodb:mongodb-driver-reactivestreams` from 4.11.0 to 4.11.1 - [Release notes](https://github.com/mongodb/mongo-java-driver/releases) - [Commits](https://github.com/mongodb/mongo-java-driver/compare/r4.11.0...r4.11.1) Updates `org.mongodb:bson` from 4.11.0 to 4.11.1 - [Release notes](https://github.com/mongodb/mongo-java-driver/releases) - [Commits](https://github.com/mongodb/mongo-java-driver/compare/r4.11.0...r4.11.1) --- updated-dependencies: - dependency-name: org.mongodb:mongodb-driver-sync dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.mongodb:mongodb-driver-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.mongodb:mongodb-driver-legacy dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.mongodb:mongodb-driver-reactivestreams dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.mongodb:bson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit cf282855bf8e539e85d2ac28019c26ed42f0cc0a) --- 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 1292752df2858..60a1b126adc6f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -172,7 +172,7 @@ 4.24.0 2.2 6.0.0 - 4.11.0 + 4.11.1 1.8.0 0.34.1 3.25.5 From 8f43d7df9f8c13d2ca9b85ce61d385787397f5e4 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 15 Nov 2023 10:39:19 +0200 Subject: [PATCH 004/212] Remove `smallrye-opentracing` from native tests modules in CI The test was removed in https://github.com/quarkusio/quarkus/pull/36602 (cherry picked from commit f63d7b5f1289bef773b8091f70b9453bf12987a8) --- .github/native-tests.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/native-tests.json b/.github/native-tests.json index add0cc7c6e1d3..d137a6435f5a1 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -111,7 +111,7 @@ { "category": "Misc3", "timeout": 80, - "test-modules": "kubernetes-client, openshift-client, kubernetes-service-binding-jdbc, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics, smallrye-opentracing", + "test-modules": "kubernetes-client, openshift-client, kubernetes-service-binding-jdbc, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics", "os-name": "ubuntu-latest" }, { From 7fed75b1ef9451cbd2573fd52eea5ab1e1a67110 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:57:23 +0000 Subject: [PATCH 005/212] Bump jakarta.json:jakarta.json-api from 2.1.2 to 2.1.3 Bumps [jakarta.json:jakarta.json-api](https://github.com/eclipse-ee4j/jsonp) from 2.1.2 to 2.1.3. - [Release notes](https://github.com/eclipse-ee4j/jsonp/releases) - [Commits](https://github.com/eclipse-ee4j/jsonp/commits) --- updated-dependencies: - dependency-name: jakarta.json:jakarta.json-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit d04e0c6b81f6881279ce5e3cb585a1674c4f662a) --- bom/application/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 60a1b126adc6f..ff4474c22a395 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -71,7 +71,7 @@ 4.0.1 2.0.1 2.1.0 - 2.1.2 + 2.1.3 3.0.0 2.0.1 3.1.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 82ecd9308c337..0b5bf88744fa0 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -38,7 +38,7 @@ 1.0.0 5.0.0 - 2.1.2 + 2.1.3 3.1.0 4.0.1 1.1.5 From 07abcf85377e4ef95b836be75db9aa45feb7ce35 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Tue, 14 Nov 2023 22:09:18 +0100 Subject: [PATCH 006/212] Add te actual coordinates of the MySQL driver (cherry picked from commit 0624b2984de2f7cbedd79be81dfd26a82f0dbf6e) --- bom/application/pom.xml | 12 ++++++++++++ extensions/devservices/mysql/pom.xml | 4 ++-- extensions/jdbc/jdbc-mysql/runtime/pom.xml | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index ff4474c22a395..0135c83c4f7a5 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -5173,6 +5173,7 @@ + mysql mysql-connector-java ${mysql-jdbc.version} @@ -5183,6 +5184,17 @@ + + com.mysql + mysql-connector-j + ${mysql-jdbc.version} + + + com.google.protobuf + protobuf-java + + + org.apache.derby derbyclient diff --git a/extensions/devservices/mysql/pom.xml b/extensions/devservices/mysql/pom.xml index 7aa75b4ff35ae..28b73be41d864 100644 --- a/extensions/devservices/mysql/pom.xml +++ b/extensions/devservices/mysql/pom.xml @@ -41,8 +41,8 @@ test - mysql - mysql-connector-java + com.mysql + mysql-connector-j diff --git a/extensions/jdbc/jdbc-mysql/runtime/pom.xml b/extensions/jdbc/jdbc-mysql/runtime/pom.xml index dae4cc9ab9800..524987f3ac027 100644 --- a/extensions/jdbc/jdbc-mysql/runtime/pom.xml +++ b/extensions/jdbc/jdbc-mysql/runtime/pom.xml @@ -23,8 +23,8 @@ true - mysql - mysql-connector-java + com.mysql + mysql-connector-j org.graalvm.sdk @@ -45,7 +45,7 @@ quarkus-extension-maven-plugin - mysql:mysql-connector-java + com.mysql:mysql-connector-j From 7aa525d89237f58df6aa4cc7e5248497ac40d6d7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 15 Nov 2023 11:30:01 +0100 Subject: [PATCH 007/212] Make analytics tests more a bit more resilient We have seen several failures lately, let's hope this fixes it. (cherry picked from commit fa38177033e96517e2956af60991881c5de163d9) --- .../analytics/AnalyticsServicePromptTest.java | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/independent-projects/tools/analytics-common/src/test/java/io/quarkus/analytics/AnalyticsServicePromptTest.java b/independent-projects/tools/analytics-common/src/test/java/io/quarkus/analytics/AnalyticsServicePromptTest.java index 07d3df954c868..dec63b23316c8 100644 --- a/independent-projects/tools/analytics-common/src/test/java/io/quarkus/analytics/AnalyticsServicePromptTest.java +++ b/independent-projects/tools/analytics-common/src/test/java/io/quarkus/analytics/AnalyticsServicePromptTest.java @@ -66,25 +66,36 @@ void testConsoleQuestion_no() throws IOException { @Test void testConsoleQuestion_promptTimeout() throws IOException { - System.setProperty("quarkus.analytics.prompt.timeout", "0"); - assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); - service.buildAnalyticsUserInput((String prompt) -> { - assertEquals(ACCEPTANCE_PROMPT, prompt); - return "n"; - }); - assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); - System.clearProperty("quarkus.analytics.prompt.timeout"); + try { + System.setProperty("quarkus.analytics.prompt.timeout", "0"); + assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); + service.buildAnalyticsUserInput((String prompt) -> { + assertEquals(ACCEPTANCE_PROMPT, prompt); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return "n"; + }); + assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); + } finally { + System.clearProperty("quarkus.analytics.prompt.timeout"); + } } @Test void testConsoleQuestion_AnalyticsDisabled() throws IOException { - System.setProperty("quarkus.analytics.disabled", "true"); - assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); - service.buildAnalyticsUserInput((String prompt) -> { - fail("Prompt should be disabled"); - return "n"; - }); - assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); - System.clearProperty("quarkus.analytics.disabled"); + try { + System.setProperty("quarkus.analytics.disabled", "true"); + assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); + service.buildAnalyticsUserInput((String prompt) -> { + fail("Prompt should be disabled"); + return "n"; + }); + assertFalse(fileLocations.getLocalConfigFile().toFile().exists()); + } finally { + System.clearProperty("quarkus.analytics.disabled"); + } } } From 33e234186a074ad787963b20ede96e3965520707 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Thu, 2 Nov 2023 14:30:35 +0100 Subject: [PATCH 008/212] SmallRye GraphQL 2.6 + custom scalar registration (cherry picked from commit 0498a7aa5dafe9f0424a32a64ccfd8cd6dca74b2) --- bom/application/pom.xml | 2 +- .../deployment/SmallRyeGraphQLProcessor.java | 14 +++- .../AdditionalGraphQLScalarsTest.java | 65 +++++++++++++++++++ .../smallrye/graphql/runtime/ExtraScalar.java | 7 ++ .../runtime/SmallRyeGraphQLConfig.java | 7 ++ .../runtime/SmallRyeGraphQLRecorder.java | 18 ++++- 6 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/AdditionalGraphQLScalarsTest.java create mode 100644 extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/ExtraScalar.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 0135c83c4f7a5..d4031b931096d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -54,7 +54,7 @@ 4.0.4 4.0.0 3.7.0 - 2.5.1 + 2.6.0 6.2.6 4.3.1 2.1.0 diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 56af0e947c50d..f10de933cc1da 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -58,6 +58,7 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.runtime.metrics.MetricsFactory; +import io.quarkus.smallrye.graphql.runtime.ExtraScalar; import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLConfig; import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLConfigMapping; import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLLocaleResolver; @@ -99,6 +100,7 @@ import io.smallrye.graphql.schema.model.InputType; import io.smallrye.graphql.schema.model.Operation; import io.smallrye.graphql.schema.model.Reference; +import io.smallrye.graphql.schema.model.Scalars; import io.smallrye.graphql.schema.model.Schema; import io.smallrye.graphql.schema.model.Type; import io.smallrye.graphql.schema.model.UnionType; @@ -305,9 +307,10 @@ void buildExecutionService( SmallRyeGraphQLConfig graphQLConfig) { activateFederation(graphQLConfig, systemPropertyProducer, graphQLFinalIndexBuildItem); + graphQLConfig.extraScalars.ifPresent(this::registerExtraScalarsInSchema); Schema schema = SchemaBuilder.build(graphQLFinalIndexBuildItem.getFinalIndex(), graphQLConfig.autoNameStrategy); - RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema); + RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema, graphQLConfig); graphQLInitializedProducer.produce(new SmallRyeGraphQLInitializedBuildItem(initialized)); // Make sure the complex object from the application can work in native mode @@ -319,6 +322,15 @@ void buildExecutionService( .produce(ReflectiveClassBuildItem.builder(getGraphQLJavaClasses()).methods().fields().build()); } + private void registerExtraScalarsInSchema(List extraScalars) { + for (ExtraScalar extraScalar : extraScalars) { + switch (extraScalar) { + case UUID: + Scalars.addUuid(); + } + } + } + @Record(ExecutionTime.RUNTIME_INIT) @BuildStep void buildSchemaEndpoint( diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/AdditionalGraphQLScalarsTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/AdditionalGraphQLScalarsTest.java new file mode 100644 index 0000000000000..23bee0b2d7cc7 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/AdditionalGraphQLScalarsTest.java @@ -0,0 +1,65 @@ +package io.quarkus.smallrye.graphql.deployment; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +import java.util.UUID; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class AdditionalGraphQLScalarsTest extends AbstractGraphQLTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(UuidApi.class) + .addAsResource(new StringAsset("quarkus.smallrye-graphql.extra-scalars=UUID\n" + + "quarkus.smallrye-graphql.schema-include-scalars=true"), + "application.properties") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @Test + public void testUuid() { + String uuid = UUID.randomUUID().toString(); + String query = getPayload("{ echo(uuid: \"" + uuid + "\") }"); + RestAssured.given() + .body(query) + .contentType(MEDIATYPE_JSON) + .post("/graphql/") + .then() + .assertThat() + .statusCode(200) + .body("data.echo", equalTo(uuid)); + } + + @Test + public void testUuidSchemaDefinition() { + RestAssured.given() + .get("/graphql/schema.graphql") + .prettyPeek() + .then() + .assertThat() + .body(containsString("scalar UUID")) + .body(containsString("echo(uuid: UUID): UUID")) + .statusCode(200); + } + + @GraphQLApi + public static class UuidApi { + + @Query + public UUID echo(UUID uuid) { + return uuid; + } + + } + +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/ExtraScalar.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/ExtraScalar.java new file mode 100644 index 0000000000000..4f6a011ed2158 --- /dev/null +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/ExtraScalar.java @@ -0,0 +1,7 @@ +package io.quarkus.smallrye.graphql.runtime; + +public enum ExtraScalar { + + UUID + +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java index 1042bcb037f58..540b7d5b37eab 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLConfig.java @@ -214,4 +214,11 @@ public class SmallRyeGraphQLConfig { @ConfigItem @ConfigDocSection public SmallRyeGraphQLUIConfig ui; + + /** + * Additional scalars to register in the schema. + * These are taken from the `graphql-java-extended-scalars` library. + */ + @ConfigItem + public Optional> extraScalars; } diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java index a46f622d9022f..8cea023f71160 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java @@ -17,6 +17,7 @@ import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; import io.smallrye.graphql.cdi.producer.GraphQLProducer; +import io.smallrye.graphql.scalar.GraphQLScalarTypes; import io.smallrye.graphql.schema.model.Schema; import io.vertx.core.Handler; import io.vertx.ext.web.Route; @@ -25,12 +26,27 @@ @Recorder public class SmallRyeGraphQLRecorder { - public RuntimeValue createExecutionService(BeanContainer beanContainer, Schema schema) { + public RuntimeValue createExecutionService(BeanContainer beanContainer, + Schema schema, + SmallRyeGraphQLConfig graphQLConfig) { GraphQLProducer graphQLProducer = beanContainer.beanInstance(GraphQLProducer.class); + if (graphQLConfig.extraScalars.isPresent()) { + registerExtraScalars(graphQLConfig.extraScalars.get()); + } GraphQLSchema graphQLSchema = graphQLProducer.initialize(schema); return new RuntimeValue<>(graphQLSchema != null); } + private void registerExtraScalars(List extraScalars) { + for (ExtraScalar extraScalar : extraScalars) { + switch (extraScalar) { + case UUID: + GraphQLScalarTypes.addUuid(); + break; + } + } + } + public Handler executionHandler(RuntimeValue initialized, boolean allowGet, boolean allowPostWithQueryParameters, boolean runBlocking, boolean allowCompression) { if (initialized.getValue()) { From af94c3207c9b0baf8779469c13dd2e7f58a8df9f Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 14 Nov 2023 14:09:44 +0100 Subject: [PATCH 009/212] Handle duplicated context in the CacheResultInterceptor - Capture the context when calling the interceptor - Make sure the item is emitted on the captured context (cherry picked from commit f781b7fd106fd2d90656ac78d21f8e01374f0b68) --- .../cache/runtime/CacheResultInterceptor.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java index dadc40e07d033..e6e1397b53dd4 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java @@ -1,6 +1,7 @@ package io.quarkus.cache.runtime; import java.time.Duration; +import java.util.concurrent.Executor; import java.util.function.Function; import java.util.function.Supplier; @@ -16,6 +17,10 @@ import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.TimeoutException; import io.smallrye.mutiny.Uni; +import io.vertx.core.Context; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.impl.ContextInternal; @CacheResult(cacheName = "") // The `cacheName` attribute is @Nonbinding. @Interceptor @@ -53,6 +58,7 @@ public Object intercept(InvocationContext invocationContext) throws Throwable { try { ReturnType returnType = determineReturnType(invocationContext.getMethod().getReturnType()); if (returnType != ReturnType.NonAsync) { + Context context = Vertx.currentContext(); Uni cacheValue = cache.getAsync(key, new Function>() { @SuppressWarnings("unchecked") @Override @@ -65,11 +71,54 @@ public Uni apply(Object key) { throw new CacheException(e); } } + }).emitOn(new Executor() { + // We need make sure we go back to the original context when the cache value is computed. + // Otherwise, we would always emit on the context having computed the value, which could + // break the duplicated context isolation. + @Override + public void execute(Runnable command) { + Context ctx = Vertx.currentContext(); + if (context == null) { + // We didn't capture a context + if (ctx == null) { + // We are not on a context => we can execute immediately. + command.run(); + } else { + // We are on a context. + // We cannot continue on the current context as we may share a duplicated context. + // We need a new one. Note that duplicate() does not duplicate the duplicated context, + // but the root context. + ((ContextInternal) ctx).duplicate() + .runOnContext(new Handler() { + @Override + public void handle(Void ignored) { + command.run(); + } + }); + } + } else { + // We captured a context. + if (ctx == context) { + // We are on the same context => we can execute immediately + command.run(); + } else { + // 1) We are not on a context (ctx == null) => we need to switch to the captured context. + // 2) We are on a different context (ctx != null) => we need to switch to the captured context. + context.runOnContext(new Handler() { + @Override + public void handle(Void ignored) { + command.run(); + } + }); + } + } + } }); if (binding.lockTimeout() <= 0) { return createAsyncResult(cacheValue, returnType); } + // IMPORTANT: The item/failure are emitted on the captured context. cacheValue = cacheValue.ifNoItem().after(Duration.ofMillis(binding.lockTimeout())) .recoverWithUni(new Supplier>() { @Override From 01d3d4886974e1f835499b0022a52894f7b33cec Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 15 Nov 2023 20:27:59 +1100 Subject: [PATCH 010/212] Fix filter per extension in dev ui Signed-off-by: Phillip Kruger (cherry picked from commit 7187227d3463a87c0d314df38f0cfdffb51a3cae) --- .../resources/dev-ui/qwc/qwc-configuration.js | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js index 84670fc933c50..0cb7923ad5746 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-configuration.js @@ -35,7 +35,7 @@ export class QwcConfiguration extends observeState(LitElement) { flex-direction: column; overflow: hidden; } - + .confTopBar { display: flex; justify-content: space-between; @@ -50,7 +50,7 @@ export class QwcConfiguration extends observeState(LitElement) { vertical-align: top; width: 100%; } - + .description { padding: 1em; } @@ -70,7 +70,7 @@ export class QwcConfiguration extends observeState(LitElement) { cursor: pointer; color: var(--lumo-primary-color); } - + .lock-icon { color: var(--lumo-contrast-60pct); font-size: small; @@ -100,6 +100,15 @@ export class QwcConfiguration extends observeState(LitElement) { constructor() { super(); + this._detailsOpenedItem = []; + this._busy = null; + + this._showOnlyOwnProperties = false; + this._searchTerm = ''; + } + + connectedCallback() { + super.connectedCallback(); this._filteredValue = this.routerController.getQueryParameter("filter"); if(this._filteredValue){ @@ -109,15 +118,10 @@ export class QwcConfiguration extends observeState(LitElement) { this._allConfiguration = e.result; this._visibleConfiguration = e.result; this._filtered = e.result; - }) + }); this.jsonRpc.getAllValues().then(e => { this._values = e.result; }); - this._detailsOpenedItem = []; - this._busy = null; - - this._showOnlyOwnProperties = false; - this._searchTerm = ''; } render() { @@ -219,7 +223,7 @@ export class QwcConfiguration extends observeState(LitElement) { @@ -306,11 +310,11 @@ export class QwcConfiguration extends observeState(LitElement) { `; } else if (prop.typeName === "java.lang.Float" || prop.typeName === "java.lang.Double") { return html` - - + `; } else { return html` - - @@ -374,7 +378,7 @@ export class QwcConfiguration extends observeState(LitElement) { } } res = res.toUpperCase(); - + let def = "Default value: None"; if (prop.defaultValue) { def = "Default value: " + prop.defaultValue; From 237c923ed28f7ad8700ef21ce2b2a7ee276490d0 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 16 Nov 2023 08:52:34 +0100 Subject: [PATCH 011/212] Properly handle authority-pseudo header in the ForwardedParser (cherry picked from commit 86d5c1a26b8a5706d86fb54682e4532b7f6b740a) --- .../java/io/quarkus/vertx/http/runtime/ForwardedParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java index 0bdee036fbf3b..1b11431a81c7f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ForwardedParser.java @@ -121,7 +121,7 @@ private void calculate() { calculated = true; remoteAddress = delegate.remoteAddress(); scheme = delegate.scheme(); - setHostAndPort(delegate.getHeader(HttpHeaders.HOST), port); + setHostAndPort(delegate.host(), port); uri = delegate.uri(); if (trustedProxyCheck.isProxyAllowed()) { From 3c31c65a419becef1d6a10b74bc0f3514e8c8254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Thu, 16 Nov 2023 00:06:57 +0100 Subject: [PATCH 012/212] Do not report unused deprecated runtime props with default val (cherry picked from commit d5141269e66b5184e3d61eb9d4b8fb577a318b9d) --- .../configuration/DeprecatedRuntimePropertiesRecorder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeprecatedRuntimePropertiesRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeprecatedRuntimePropertiesRecorder.java index 6ff8eb37c3f82..994e2fbd5c0dc 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeprecatedRuntimePropertiesRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeprecatedRuntimePropertiesRecorder.java @@ -7,6 +7,7 @@ import org.jboss.logging.Logger; import io.quarkus.runtime.annotations.Recorder; +import io.smallrye.config.SmallRyeConfig; @Recorder public class DeprecatedRuntimePropertiesRecorder { @@ -17,6 +18,11 @@ public void reportDeprecatedProperties(Set deprecatedRuntimeProperties) Config config = ConfigProvider.getConfig(); for (String property : config.getPropertyNames()) { if (deprecatedRuntimeProperties.contains(property)) { + String configSourceName = ((SmallRyeConfig) config).getConfigValue(property).getConfigSourceName(); + // this condition can be removed when support of the @ConfigRoot annotation on classes is removed + if ("DefaultValuesConfigSource".equals(configSourceName)) { + continue; + } log.warnf("The '%s' config property is deprecated and should not be used anymore", property); } } From ec33c29a4cebd84078eaa9cd2a6d7d3e30946a2e Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Wed, 15 Nov 2023 19:38:18 +0000 Subject: [PATCH 013/212] Docs - Add missing steps to Basic authentication how-to SME and QE fixes fix bullet fix bad link Fix xref (cherry picked from commit e60c3d5d6ebbdd2acb101370420c809e61765fed) --- .../security-basic-authentication-howto.adoc | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/security-basic-authentication-howto.adoc b/docs/src/main/asciidoc/security-basic-authentication-howto.adoc index 6477a57b342bb..331874887dc40 100644 --- a/docs/src/main/asciidoc/security-basic-authentication-howto.adoc +++ b/docs/src/main/asciidoc/security-basic-authentication-howto.adoc @@ -15,23 +15,39 @@ Enable xref:security-basic-authentication.adoc[Basic authentication] for your Qu == Prerequisites -* You have installed at least one extension that provides an `IdentityProvider` based on username and password, such as xref:security-jdbc.adoc[Elytron JDBC]. +* You have installed at least one extension that provides an `IdentityProvider` based on username and password. +For example: + +** xref:security-jpa.adoc[Quarkus Security Jakarta Persistence extensions (`security-jpa` or `security-jpa-reactive`)] +** xref:security-properties.adoc[Elytron security properties file extension `(quarkus-elytron-security-properties-file)`] +** xref:security-jdbc.adoc[Elytron security JDBC extension `(quarkus-elytron-security-jdbc)`] + +The following procedure outlines how you can enable Basic authentication for your application by using the `elytron-security-properties-file` extension. == Procedure -. Enable Basic authentication by setting the `quarkus.http.auth.basic` property to `true`. +. In the `application.properties` file, set the `quarkus.http.auth.basic` property to `true`. + [source,properties] ---- quarkus.http.auth.basic=true ---- -. For testing purposes, you can configure the required user credentials, user name, secret, and roles, in the `application.properties` file. +. **Optional:** In a non-production environment only and purely for testing Quarkus Security in your applications: +.. To enable authentication for the embedded realm, set the `quarkus.security.users.embedded.enabled` property to `true`. ++ +[source,properties] +---- +security.users.embedded.enabled=true +---- + +.. You can also configure the required user credentials, user name, secret, and roles. For example: + [source,properties] ---- quarkus.http.auth.basic=true +quarkus.security.users.embedded.enabled=true quarkus.security.users.embedded.plain-text=true quarkus.security.users.embedded.users.alice=alice <1> quarkus.security.users.embedded.users.bob=bob <2> From 18feaa1b8bbe2f762d55b5d4c7d3321b5e5c8f08 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 14 Nov 2023 11:51:46 +0100 Subject: [PATCH 014/212] Use 3.2 as the example stream for update-quarkus.adoc It makes little sense to use 3.0 now but 3.2 makes perfect sense as it is an LTS version. (cherry picked from commit df6cf370ceb927629e7e0670ce02ceab0f42ecae) --- docs/src/main/asciidoc/update-quarkus.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/update-quarkus.adoc b/docs/src/main/asciidoc/update-quarkus.adoc index 8b355b27f1f1c..0d6fc592957eb 100644 --- a/docs/src/main/asciidoc/update-quarkus.adoc +++ b/docs/src/main/asciidoc/update-quarkus.adoc @@ -50,7 +50,7 @@ Confirm the version number using `quarkus -v`. ---- quarkus update ---- -Optional: To specify a particular stream, use the `stream` option; for example: `--stream=3.0` +Optional: To specify a particular stream, use the `--stream` option; for example: `--stream=3.2` ifndef::devtools-no-maven[] ifdef::devtools-wrapped[+] **** @@ -63,7 +63,7 @@ ifdef::devtools-wrapped[+] ---- ./mvnw {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:update -N ---- -Optional: To specify a particular stream, use the `Dstream` option; for example: `-Dstream=3.0` +Optional: To specify a particular stream, use the `-Dstream` option; for example: `-Dstream=3.2` endif::[] ifndef::devtools-no-gradle[] ifdef::devtools-wrapped[+] From 77859498b2a6497d32d699a13bdbeb76cee068d9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 13 Nov 2023 16:03:37 +0100 Subject: [PATCH 015/212] Adjust Quarkiverse Antora doc templates a bit We discussed this quite a long time ago, and while we have fixed all the existing extensions, we never fixed the templates. Did that + adjusted the tests to check for regressions in the created extension. (cherry picked from commit 023a10fa40d783a2cc9bfed85deefe04ac480bc5) --- .../quarkiverse/java/docs/antora.tpl.qute.yml | 2 +- .../java/docs/modules/ROOT/nav.tpl.qute.adoc | 2 +- .../maven/it/CreateExtensionMojoIT.java | 13 ++ ...uarkiverse-ext_.github_workflows_build.yml | 59 +++++ ...erse-ext_.github_workflows_pre-release.yml | 33 +++ ...xt_.github_workflows_quarkus-snapshot.yaml | 60 ++++++ ...rkiverse-ext_.github_workflows_release.yml | 74 +++++++ .../quarkus-my-quarkiverse-ext_LICENSE | 201 ++++++++++++++++++ .../quarkus-my-quarkiverse-ext_README.md | 25 +++ ...quarkus-my-quarkiverse-ext_docs_antora.yml | 5 + ...quarkiverse-ext_docs_modules_ROOT_nav.adoc | 1 + ...rse-ext_docs_modules_ROOT_pages_index.adoc | 27 +++ .../quarkus-my-quarkiverse-ext_docs_pom.xml | 106 +++++++++ ...-quarkiverse-ext_integration-tests_pom.xml | 98 +++++++++ ..._resources_META-INF_quarkus-extension.yaml | 14 ++ 15 files changed, 718 insertions(+), 2 deletions(-) create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_build.yml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_pre-release.yml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_quarkus-snapshot.yaml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_release.yml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_LICENSE create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_README.md create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_antora.yml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_nav.adoc create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_pages_index.adoc create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_pom.xml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml create mode 100644 integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_runtime_src_main_resources_META-INF_quarkus-extension.yaml diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/antora.tpl.qute.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/antora.tpl.qute.yml index b5e3021566432..b38bbf37c0b89 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/antora.tpl.qute.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/antora.tpl.qute.yml @@ -1,5 +1,5 @@ name: {namespace.id}{extension.id} -title: {extension.full-name} +title: {extension.name} version: dev nav: - modules/ROOT/nav.adoc diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/modules/ROOT/nav.tpl.qute.adoc b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/modules/ROOT/nav.tpl.qute.adoc index 3c4fdef0a75c1..d2454cdb59abc 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/modules/ROOT/nav.tpl.qute.adoc +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/quarkiverse/java/docs/modules/ROOT/nav.tpl.qute.adoc @@ -1 +1 @@ -* xref:index.adoc[{extension.full-name}] +* xref:index.adoc[Getting started] diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateExtensionMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateExtensionMojoIT.java index ba64fbdd9b6cb..90a6ffe4daaaa 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateExtensionMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateExtensionMojoIT.java @@ -111,6 +111,19 @@ public void testCreateQuarkiverseExtension(TestInfo testInfo) throws Throwable { "quarkus-my-quarkiverse-ext/integration-tests/src/test/java/io/quarkiverse/my/quarkiverse/ext/it/MyQuarkiverseExtResourceTest.java"); assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/pom.xml"); assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/runtime/pom.xml"); + assertThatMatchSnapshot(testInfo, testDirPath, + "quarkus-my-quarkiverse-ext/runtime/src/main/resources/META-INF/quarkus-extension.yaml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/LICENSE"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/README.md"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/.github/workflows/build.yml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/.github/workflows/pre-release.yml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/.github/workflows/quarkus-snapshot.yaml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/.github/workflows/release.yml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/docs/pom.xml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/docs/antora.yml"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/docs/modules/ROOT/nav.adoc"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/docs/modules/ROOT/pages/index.adoc"); + assertThatMatchSnapshot(testInfo, testDirPath, "quarkus-my-quarkiverse-ext/integration-tests/pom.xml"); } @Test diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_build.yml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_build.yml new file mode 100644 index 0000000000000..5da57b27e4dba --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_build.yml @@ -0,0 +1,59 @@ +name: Build + +on: + push: + branches: + - "main" + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Build on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: +# os: [windows-latest, macos-latest, ubuntu-latest] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Prepare git + run: git config --global core.autocrlf false + if: startsWith(matrix.os, 'windows') + + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + cache: 'maven' + + - name: Build with Maven + run: mvn -B clean install -Dno-format + + - name: Build with Maven (Native) + run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_pre-release.yml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_pre-release.yml new file mode 100644 index 0000000000000..0a9e64eaa882f --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_pre-release.yml @@ -0,0 +1,33 @@ +name: Quarkiverse Pre Release + +on: + pull_request: + paths: + - '.github/project.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + release: + runs-on: ubuntu-latest + name: pre release + + steps: + - uses: radcortez/project-metadata-action@master + name: retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - name: Validate version + if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') + run: | + echo '::error::Cannot release a SNAPSHOT version.' + exit 1 \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_quarkus-snapshot.yaml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_quarkus-snapshot.yaml new file mode 100644 index 0000000000000..ed1e496f85d6b --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_quarkus-snapshot.yaml @@ -0,0 +1,60 @@ +name: "Quarkus ecosystem CI" +on: + workflow_dispatch: + watch: + types: [started] + + # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, + # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository + +env: + ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci + ECOSYSTEM_CI_REPO_FILE: context.yaml + JAVA_VERSION: 11 + + ######################### + # Repo specific setting # + ######################### + + ECOSYSTEM_CI_REPO_PATH: quarkiverse-my-quarkiverse-ext + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: "Build against latest Quarkus snapshot" + runs-on: ubuntu-latest + # Allow to manually launch the ecosystem CI in addition to the bots + if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' + + steps: + - name: Install yq + uses: dcarbone/install-yq-action@v1.0.1 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + + - name: Checkout repo + uses: actions/checkout@v3 + with: + path: current-repo + + - name: Checkout Ecosystem + uses: actions/checkout@v3 + with: + repository: ${{ env.ECOSYSTEM_CI_REPO }} + path: ecosystem-ci + + - name: Setup and Run Tests + run: ./ecosystem-ci/setup-and-test + env: + ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_release.yml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_release.yml new file mode 100644 index 0000000000000..0a3894f1bdd33 --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_.github_workflows_release.yml @@ -0,0 +1,74 @@ +name: Quarkiverse Release + +on: + pull_request: + types: [closed] + paths: + - '.github/project.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + release: + runs-on: ubuntu-latest + name: release + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/project-metadata-action@main + name: Retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - uses: actions/checkout@v3 + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Configure Git author + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Update latest release version in docs + run: | + mvn -B -ntp -pl docs -am generate-resources -Denforcer.skip -Dformatter.skip -Dimpsort.skip + if ! git diff --quiet docs/modules/ROOT/pages/includes/attributes.adoc; then + git add docs/modules/ROOT/pages/includes/attributes.adoc + git commit -m "Update the latest release version ${{steps.metadata.outputs.current-version}} in documentation" + fi + + - name: Maven release ${{steps.metadata.outputs.current-version}} + run: | + mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + mvn -B release:perform -Darguments=-DperformRelease -DperformRelease -Prelease + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + + - name: Push changes to ${{github.base_ref}} branch + run: | + git push + git push origin ${{steps.metadata.outputs.current-version}} diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_LICENSE b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_LICENSE new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_README.md b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_README.md new file mode 100644 index 0000000000000..5e844fd542126 --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_README.md @@ -0,0 +1,25 @@ +# Quarkus My Quarkiverse extension + +[![Version](https://img.shields.io/maven-central/v/io.quarkiverse.my-quarkiverse-ext/quarkus-my-quarkiverse-ext?logo=apache-maven&style=flat-square)](https://search.maven.org/artifact/io.quarkiverse.my-quarkiverse-ext/quarkus-my-quarkiverse-ext) + +## Welcome to Quarkiverse! + +Congratulations and thank you for creating a new Quarkus extension project in Quarkiverse! + +Feel free to replace this content with the proper description of your new project and necessary instructions how to use and contribute to it. + +You can find the basic info, Quarkiverse policies and conventions in [the Quarkiverse wiki](https://github.com/quarkiverse/quarkiverse/wiki). + +In case you are creating a Quarkus extension project for the first time, please follow [Building My First Extension](https://quarkus.io/guides/building-my-first-extension) guide. + +Other useful articles related to Quarkus extension development can be found under the [Writing Extensions](https://quarkus.io/guides/#writing-extensions) guide category on the [Quarkus.io](https://quarkus.io) website. + +Thanks again, good luck and have fun! + +## Documentation + +The documentation for this extension should be maintained as part of this repository and it is stored in the `docs/` directory. + +The layout should follow the [Antora's Standard File and Directory Set](https://docs.antora.org/antora/2.3/standard-directories/). + +Once the docs are ready to be published, please open a PR including this repository in the [Quarkiverse Docs Antora playbook](https://github.com/quarkiverse/quarkiverse-docs/blob/main/antora-playbook.yml#L7). See an example [here](https://github.com/quarkiverse/quarkiverse-docs/pull/1). diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_antora.yml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_antora.yml new file mode 100644 index 0000000000000..c56ac65d6aced --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_antora.yml @@ -0,0 +1,5 @@ +name: quarkus-my-quarkiverse-ext +title: My Quarkiverse extension +version: dev +nav: + - modules/ROOT/nav.adoc diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_nav.adoc b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_nav.adoc new file mode 100644 index 0000000000000..d2454cdb59abc --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_nav.adoc @@ -0,0 +1 @@ +* xref:index.adoc[Getting started] diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_pages_index.adoc b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_pages_index.adoc new file mode 100644 index 0000000000000..483daf11f0c0c --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_modules_ROOT_pages_index.adoc @@ -0,0 +1,27 @@ += Quarkus My Quarkiverse extension + +include::./includes/attributes.adoc[] + +TIP: Describe what the extension does here. + +== Installation + +If you want to use this extension, you need to add the `io.quarkiverse.my-quarkiverse-ext:quarkus-my-quarkiverse-ext` extension first to your build file. + +For instance, with Maven, add the following dependency to your POM file: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.my-quarkiverse-ext + quarkus-my-quarkiverse-ext + {project-version} + +---- + +[[extension-configuration-reference]] +== Extension Configuration Reference + +TIP: Remove this section if you don't have Quarkus configuration properties in your extension. + +include::includes/quarkus-my-quarkiverse-ext.adoc[leveloffset=+1, opts=optional] diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_pom.xml new file mode 100644 index 0000000000000..655dffbc523ec --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_docs_pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + io.quarkiverse.my-quarkiverse-ext + quarkus-my-quarkiverse-ext-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-my-quarkiverse-ext-docs + Quarkus My Quarkiverse extension - Documentation + + + + + io.quarkiverse.my-quarkiverse-ext + quarkus-my-quarkiverse-ext-deployment + ${project.version} + + + + + modules/ROOT/examples + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + it.ozimov + yaml-properties-maven-plugin + + + initialize + + read-project-properties + + + + ${project.basedir}/../.github/project.yml + + + + + + + maven-resources-plugin + + + copy-resources + generate-resources + + copy-resources + + + ${project.basedir}/modules/ROOT/pages/includes/ + + + ${project.basedir}/../target/asciidoc/generated/config/ + quarkus-my-quarkiverse-ext.adoc + false + + + ${project.basedir}/templates/includes + attributes.adoc + true + + + + + + copy-images + prepare-package + + copy-resources + + + ${project.build.directory}/generated-docs/_images/ + + + ${project.basedir}/modules/ROOT/assets/images/ + false + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + + + + + diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml new file mode 100644 index 0000000000000..3efa805c3451b --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + io.quarkiverse.my-quarkiverse-ext + quarkus-my-quarkiverse-ext-parent + 999-SNAPSHOT + + quarkus-my-quarkiverse-ext-integration-tests + Quarkus My Quarkiverse extension - Integration Tests + + true + + + + io.quarkus + quarkus-resteasy + + + io.quarkiverse.my-quarkiverse-ext + quarkus-my-quarkiverse-ext + ${project.version} + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-devtools-testing + test + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + + + + + false + native + + + + diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_runtime_src_main_resources_META-INF_quarkus-extension.yaml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_runtime_src_main_resources_META-INF_quarkus-extension.yaml new file mode 100644 index 0000000000000..d635fe2e40fc4 --- /dev/null +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_runtime_src_main_resources_META-INF_quarkus-extension.yaml @@ -0,0 +1,14 @@ +name: My Quarkiverse extension +#description: My Quarkiverse extension description +metadata: +# keywords: +# - my-quarkiverse-ext +# guide: https://quarkiverse.github.io/quarkiverse-docs/my-quarkiverse-ext/dev/ # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension +# categories: +# - "miscellaneous" +# status: "preview" + codestart: + name: my-quarkiverse-ext + languages: + - "java" + artifact: "io.quarkiverse.my-quarkiverse-ext:quarkus-my-quarkiverse-ext:codestarts:jar:${project.version}" From fab63aec816f63b7df23bd04034e150eb4216f0f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 13 Nov 2023 19:05:05 +0000 Subject: [PATCH 016/212] Clarify dynamic Environment Variables name conversion (cherry picked from commit 0bd7d5fb8f627d0df0dd1df8fa7a4bd4dc6a26d2) --- docs/src/main/asciidoc/config-reference.adoc | 36 ++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/config-reference.adoc b/docs/src/main/asciidoc/config-reference.adoc index e3c22718953a0..8d68f8462abdf 100644 --- a/docs/src/main/asciidoc/config-reference.adoc +++ b/docs/src/main/asciidoc/config-reference.adoc @@ -79,17 +79,33 @@ nor `\_` with `_`: `FOO_BAR_0_` or `FOO_BAR_0__BAZ` [CAUTION] ==== -In some situations, looking up the exact property name is impossible. For instance, when looking -up a configuration that is part of a `Map`, and the property name contains a dynamic segment (the `Map` key). In this -case, Quarkus relies upon each source’s list of property names. These must be converted back to their most likely -dotted format for Environment Variables. +In some situations, looking up the exact property name is impossible. This is the case for configuration names that +contain user defined path segments. -By default, the underscore `_` of an Environment Variable name always maps to a dot `.`. If the property name contains -a dash or some other special character, that property name can be specified in another Config Source, with the expected -dotted format. It will provide additional information to perform a two-way conversion and match the property names. +Applying the conversion rules for Environment Variables names, `quarkus.datasource."datasource-name".jdbc.url` becomes +`QUARKUS_DATASOURCE\__DATASOURCE_NAME__JDBC_URL`. The configuration will work as expected if both properties are +available in the Config system. + +If only `QUARKUS_DATASOURCE\__DATASOURCE_NAME__JDBC_URL` is present, the Config system needs to reconvert the +configuration name to its most likely dotted format. This works fine for fixed configuration segments, but not for +names that contain dynamic segments. In this case, Quarkus is unable to determine if `DATASOURCE_NAME` should be +converted to `datasource.name` or `datasource-name` (or any other special character separator). + +For this reason, such properties always require their dotted version name in another source (the value can be left +empty) to disambiguate the Environment Variable name. It will provide additional information to perform a two-way +conversion and match the property names together. + +[source,properties] +---- +# value can be left empty +quarkus.datasource."datasource-name".jdbc.url= +---- + +[source,bash] +---- +EXPORT QUARKUS_DATASOURCE__DATASOURCE_NAME__JDBC_URL=jdbc:postgresql://localhost:5432/database +---- -To correctly lookup `Map` properties where `FOO_BAR_BAZ` is the property name and `BAR_BAZ` is the key, add -`foo.bar-baz` in a source with an ordinal lower than the EnvConfigSource (`300`). ==== @@ -101,7 +117,7 @@ To correctly lookup `Map` properties where `FOO_BAR_BAZ` is the property name an ---- QUARKUS_DATASOURCE_PASSWORD=youshallnotpass <1> ---- -<1> The name `QUARKUS_DATASOURCE_PASSWORD` the same conversion rules used for <>. +<1> The name `QUARKUS_DATASOURCE_PASSWORD` follows the same conversion rules used for <>. For `dev` mode, this file can be placed in the root of the project, but it is advised to **not** check it in to version control because it typically contains passwords, access tokens, API keys or other secrets. From a5ca37c644d1572ca6d880dd0c4a82b391a07120 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 10 Nov 2023 16:12:52 +0100 Subject: [PATCH 017/212] Move failsafe config to the root instead of in an execution Fixes #36976 (cherry picked from commit a88bb1683fba1ec18dfa7ac891a5d55c1aa9f92f) --- .../java/integration-tests/pom.tpl.qute.xml | 14 +++++++------- .../buildtool/maven/base/pom.tpl.qute.xml | 18 +++++++++--------- .../generateDefault/pom.xml | 14 +++++++------- .../generateMavenWithCustomDep/pom.xml | 14 +++++++------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml index bd9fbadde6637..4e3da1d372638 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/integration-tests/java/integration-tests/pom.tpl.qute.xml @@ -77,15 +77,15 @@ integration-test verify - - - $\{project.build.directory}/$\{project.build.finalName}-runner - org.jboss.logmanager.LogManager - $\{maven.home} - - + + + $\{project.build.directory}/$\{project.build.finalName}-runner + org.jboss.logmanager.LogManager + $\{maven.home} + + diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml index 1cc019c90e0ac..f4d187b624662 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/base/pom.tpl.qute.xml @@ -177,17 +177,17 @@ integration-test verify - - - {#if generate-native} - $\{project.build.directory}/$\{project.build.finalName}-runner - {/if} - org.jboss.logmanager.LogManager - $\{maven.home} - - + + + {#if generate-native} + $\{project.build.directory}/$\{project.build.finalName}-runner + {/if} + org.jboss.logmanager.LogManager + $\{maven.home} + + diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml index 936986bec5705..51ef9c95e7f4e 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateDefault/pom.xml @@ -83,15 +83,15 @@ integration-test verify - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml index cc3b1dd6e7174..9871c74a9df19 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusCodestartGenerationTest/generateMavenWithCustomDep/pom.xml @@ -98,15 +98,15 @@ integration-test verify - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + From 4c5270aab772522f131019a1c796731237a6fab8 Mon Sep 17 00:00:00 2001 From: Luke Morfill Date: Wed, 15 Nov 2023 21:55:37 +0000 Subject: [PATCH 018/212] Add support for GraphQL WebSocket authorization (cherry picked from commit 80995537eabf064761607932c0f50e9706ca0c6d) --- .github/native-tests.json | 2 +- .../deployment/pom.xml | 10 + ...phQLClientWebSocketAuthenticationTest.java | 207 +++++++++++++++ .../resources/application-secured.properties | 9 + .../src/test/resources/roles.properties | 1 + .../src/test/resources/users.properties | 1 + .../deployment/SmallRyeGraphQLProcessor.java | 3 +- .../SmallRyeGraphQLOverWebSocketHandler.java | 28 +- integration-tests/pom.xml | 1 + .../smallrye-graphql-client-keycloak/pom.xml | 247 ++++++++++++++++++ .../keycloak/GraphQLAuthExpiryTester.java | 83 ++++++ .../graphql/keycloak/SecuredResource.java | 42 +++ .../src/main/resources/application.properties | 4 + .../graphql/keycloak/GraphQLAuthExpiryIT.java | 7 + .../keycloak/GraphQLAuthExpiryTest.java | 32 +++ .../KeycloakRealmResourceManager.java | 133 ++++++++++ 16 files changed, 807 insertions(+), 3 deletions(-) create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationTest.java create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured.properties create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/resources/roles.properties create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/resources/users.properties create mode 100644 integration-tests/smallrye-graphql-client-keycloak/pom.xml create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/SecuredResource.java create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/main/resources/application.properties create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryIT.java create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java create mode 100644 integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java diff --git a/.github/native-tests.json b/.github/native-tests.json index d137a6435f5a1..6542e8537d8b9 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -111,7 +111,7 @@ { "category": "Misc3", "timeout": 80, - "test-modules": "kubernetes-client, openshift-client, kubernetes-service-binding-jdbc, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics", + "test-modules": "kubernetes-client, openshift-client, kubernetes-service-binding-jdbc, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-graphql-client-keycloak, smallrye-metrics", "os-name": "ubuntu-latest" }, { diff --git a/extensions/smallrye-graphql-client/deployment/pom.xml b/extensions/smallrye-graphql-client/deployment/pom.xml index e2416a9795720..c955fb72b1a02 100644 --- a/extensions/smallrye-graphql-client/deployment/pom.xml +++ b/extensions/smallrye-graphql-client/deployment/pom.xml @@ -63,6 +63,16 @@ stork-service-discovery-static-list test + + io.quarkus + quarkus-elytron-security-deployment + test + + + io.quarkus + quarkus-elytron-security-properties-file-deployment + test + diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationTest.java new file mode 100644 index 0000000000000..b04b74a6b3a24 --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationTest.java @@ -0,0 +1,207 @@ +package io.quarkus.smallrye.graphql.client.deployment; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.json.JsonValue; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.graphql.api.Subscription; +import io.smallrye.graphql.client.Response; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder; +import io.smallrye.mutiny.Multi; + +/** + * Due to the complexity of establishing a WebSocket, WebSocket/Subscription testing of the GraphQL server is done here, + * as the client framework comes in very useful for establishing the connection to the server. + *
+ * This test establishes connections to the server, and ensures that the connected user has the necessary permissions to + * execute the operation. + */ +public class DynamicGraphQLClientWebSocketAuthenticationTest { + + static String url = "http://" + System.getProperty("quarkus.http.host", "localhost") + ":" + + System.getProperty("quarkus.http.test-port", "8081") + "/graphql"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(SecuredApi.class, Foo.class) + .addAsResource("application-secured.properties", "application.properties") + .addAsResource("users.properties") + .addAsResource("roles.properties") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @Test + public void testAuthenticatedUserForSubscription() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz"); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Multi subscription = client + .subscription("subscription fooSub { fooSub { message } }"); + + assertNotNull(subscription); + + AtomicBoolean hasData = new AtomicBoolean(false); + AtomicBoolean hasCompleted = new AtomicBoolean(false); + + subscription.subscribe().with(item -> { + assertFalse(hasData.get()); + assertTrue(item.hasData()); + assertEquals(JsonValue.ValueType.OBJECT, item.getData().get("fooSub").getValueType()); + assertEquals("foo", item.getData().getJsonObject("fooSub").getString("message")); + hasData.set(true); + }, Assertions::fail, () -> { + hasCompleted.set(true); + }); + + await().untilTrue(hasCompleted); + assertTrue(hasData.get()); + } + } + + @Test + public void testAuthenticatedUserForQueryWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") + .executeSingleOperationsOverWebsocket(true); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Response response = client.executeSync("{ foo { message} }"); + assertTrue(response.hasData()); + assertEquals("foo", response.getData().getJsonObject("foo").getString("message")); + } + } + + @Test + public void testAuthorizedAndUnauthorizedForQueryWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") + .executeSingleOperationsOverWebsocket(true); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Response response = client.executeSync("{ foo { message} }"); + assertTrue(response.hasData()); + assertEquals("foo", response.getData().getJsonObject("foo").getString("message")); + + // Run a second query with a different result to validate that the result of the first query isn't being cached at all. + response = client.executeSync("{ bar { message} }"); + assertEquals(JsonValue.ValueType.NULL, response.getData().get("bar").getValueType()); + } + } + + @Test + public void testUnauthorizedUserForSubscription() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz"); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Multi subscription = client + .subscription("subscription barSub { barSub { message } }"); + + assertNotNull(subscription); + + AtomicBoolean returned = new AtomicBoolean(false); + + subscription.subscribe().with(item -> { + assertEquals(JsonValue.ValueType.NULL, item.getData().get("barSub").getValueType()); + returned.set(true); + }, throwable -> Assertions.fail(throwable)); + + await().untilTrue(returned); + } + } + + @Test + public void testUnauthorizedUserForQueryWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .header("Authorization", "Basic ZGF2aWQ6cXdlcnR5MTIz") + .executeSingleOperationsOverWebsocket(true); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Response response = client.executeSync("{ bar { message } }"); + assertEquals(JsonValue.ValueType.NULL, response.getData().get("bar").getValueType()); + } + } + + @Test + public void testUnauthenticatedForQueryWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .executeSingleOperationsOverWebsocket(true); + try (DynamicGraphQLClient client = clientBuilder.build()) { + Response response = client.executeSync("{ foo { message} }"); + assertEquals(JsonValue.ValueType.NULL, response.getData().get("foo").getValueType()); + } + } + + public static class Foo { + + private String message; + + public Foo(String foo) { + this.message = foo; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + } + + @GraphQLApi + public static class SecuredApi { + + @Query + @RolesAllowed("fooRole") + @NonBlocking + public Foo foo() { + return new Foo("foo"); + } + + @Query + @RolesAllowed("barRole") + public Foo bar() { + return new Foo("bar"); + } + + @Subscription + @RolesAllowed("fooRole") + public Multi fooSub() { + return Multi.createFrom().emitter(emitter -> { + emitter.emit(new Foo("foo")); + emitter.complete(); + }); + } + + @Subscription + @RolesAllowed("barRole") + public Multi barSub() { + return Multi.createFrom().emitter(emitter -> { + emitter.emit(new Foo("bar")); + emitter.complete(); + }); + } + + } +} diff --git a/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured.properties b/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured.properties new file mode 100644 index 0000000000000..eb7d901e0c93f --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured.properties @@ -0,0 +1,9 @@ +quarkus.security.users.file.enabled=true +quarkus.security.users.file.plain-text=true +quarkus.security.users.file.users=users.properties +quarkus.security.users.file.roles=roles.properties +quarkus.http.auth.basic=true + +quarkus.smallrye-graphql.log-payload=queryAndVariables +quarkus.smallrye-graphql.print-data-fetcher-exception=true +quarkus.smallrye-graphql.error-extension-fields=exception,classification,code,description,validationErrorType,queryPath diff --git a/extensions/smallrye-graphql-client/deployment/src/test/resources/roles.properties b/extensions/smallrye-graphql-client/deployment/src/test/resources/roles.properties new file mode 100644 index 0000000000000..ef2a67ac7e9e6 --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/resources/roles.properties @@ -0,0 +1 @@ +david=fooRole \ No newline at end of file diff --git a/extensions/smallrye-graphql-client/deployment/src/test/resources/users.properties b/extensions/smallrye-graphql-client/deployment/src/test/resources/users.properties new file mode 100644 index 0000000000000..0f1cc7592d055 --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/resources/users.properties @@ -0,0 +1 @@ +david=qwerty123 \ No newline at end of file diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index f10de933cc1da..4ead0d35da8f1 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -65,6 +65,7 @@ import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLRecorder; import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLRuntimeConfig; import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem; +import io.quarkus.vertx.http.deployment.FilterBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; @@ -149,7 +150,7 @@ public class SmallRyeGraphQLProcessor { private static final List SUPPORTED_WEBSOCKET_SUBPROTOCOLS = List.of(SUBPROTOCOL_GRAPHQL_WS, SUBPROTOCOL_GRAPHQL_TRANSPORT_WS); - private static final int GRAPHQL_WEBSOCKET_HANDLER_ORDER = -10000; + private static final int GRAPHQL_WEBSOCKET_HANDLER_ORDER = (-1 * FilterBuildItem.AUTHORIZATION) + 1; private static final String GRAPHQL_MEDIA_TYPE = "application/graphql+json"; diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java index 2cae3cd455bb6..d593b32ef6640 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java @@ -1,17 +1,21 @@ package io.quarkus.smallrye.graphql.runtime; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.jboss.logging.Logger; +import io.netty.util.concurrent.ScheduledFuture; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.smallrye.graphql.websocket.GraphQLWebSocketSession; import io.smallrye.graphql.websocket.GraphQLWebsocketHandler; import io.smallrye.graphql.websocket.graphqltransportws.GraphQLTransportWSSubprotocolHandler; import io.smallrye.graphql.websocket.graphqlws.GraphQLWSSubprotocolHandler; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.ServerWebSocket; +import io.vertx.core.net.impl.ConnectionBase; import io.vertx.ext.web.RoutingContext; /** @@ -54,9 +58,31 @@ protected void doHandle(final RoutingContext ctx) { serverWebSocket.close(); return; } + + QuarkusHttpUser user = (QuarkusHttpUser) ctx.user(); + ScheduledFuture authExpiryFuture = null; + if (user != null) { + //close the connection when the identity expires + Long expire = user.getSecurityIdentity().getAttribute("quarkus.identity.expire-time"); + if (expire != null) { + authExpiryFuture = ((ConnectionBase) ctx.request().connection()).channel().eventLoop() + .schedule(() -> { + if (!serverWebSocket.isClosed()) { + serverWebSocket.close((short) 1008, "Authentication expired"); + } + }, (expire * 1000) - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + } + log.debugf("Starting websocket with subprotocol = %s", subprotocol); GraphQLWebsocketHandler finalHandler = handler; - serverWebSocket.closeHandler(v -> finalHandler.onClose()); + ScheduledFuture finalAuthExpiryFuture = authExpiryFuture; + serverWebSocket.closeHandler(v -> { + finalHandler.onClose(); + if (finalAuthExpiryFuture != null) { + finalAuthExpiryFuture.cancel(false); + } + }); serverWebSocket.endHandler(v -> finalHandler.onEnd()); serverWebSocket.exceptionHandler(finalHandler::onThrowable); serverWebSocket.textMessageHandler(finalHandler::onMessage); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 3fdb4245dcf4b..8e027683779b5 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -318,6 +318,7 @@ smallrye-metrics smallrye-graphql smallrye-graphql-client + smallrye-graphql-client-keycloak smallrye-stork-registration jpa-without-entity quartz diff --git a/integration-tests/smallrye-graphql-client-keycloak/pom.xml b/integration-tests/smallrye-graphql-client-keycloak/pom.xml new file mode 100644 index 0000000000000..e87c0810c3da8 --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/pom.xml @@ -0,0 +1,247 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-smallrye-graphql-client-keycloak + Quarkus - Integration Tests - SmallRye GraphQL Client with Keycloak + + + http://localhost:8180/auth + + + + + io.quarkus + quarkus-smallrye-graphql + + + io.quarkus + quarkus-smallrye-graphql-client + + + io.quarkus + quarkus-resteasy-deployment + + + io.rest-assured + rest-assured + + + + io.quarkus + quarkus-rest-client-deployment + + + io.quarkus + quarkus-junit5 + test + + + + + io.quarkus + quarkus-smallrye-graphql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-smallrye-graphql-client-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-test-keycloak-server + test + + + junit + junit + + + + + io.quarkus + quarkus-oidc-deployment + test + + + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-keycloak + + + test-containers + + + + + + maven-surefire-plugin + + false + + ${keycloak.url} + + + + + maven-failsafe-plugin + + false + + ${keycloak.url} + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + docker-keycloak + + + start-containers + + + + http://localhost:8180/auth + + + + + io.fabric8 + docker-maven-plugin + + + + ${keycloak.docker.legacy.image} + quarkus-test-keycloak + + + 8180:8080 + + + admin + admin + + + Keycloak: + default + cyan + + + + + http://localhost:8180 + + + + + + + true + + + + docker-start + compile + + stop + start + + + + docker-stop + post-integration-test + + stop + + + + + + + + + + + diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java new file mode 100644 index 0000000000000..3aa53e1acb18a --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java @@ -0,0 +1,83 @@ +package io.quarkus.io.smallrye.graphql.keycloak; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.restassured.RestAssured; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder; + +/** + * We can't perform these tests in the `@Test` methods directly, because the GraphQL client + * relies on CDI, and CDI is not available in native mode on the `@Test` side. + * Therefore the test only calls this REST endpoint which then performs all the client related work. + *
+ * This test establishes connections to the server, and ensures that if authentication has an expiry, that following the + * expiry of their access the connection is correctly terminated. + */ +@Path("/") +public class GraphQLAuthExpiryTester { + + @ConfigProperty(name = "quarkus.oidc.auth-server-url") + String keycloakRealm; + + @GET + @Path("/dynamic-subscription-auth-expiry/{url}") + @Blocking + public void dynamicSubscription(@PathParam("url") String url) + throws Exception { + String authHeader = getAuthHeader(); + + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url + "/graphql") + .header("Authorization", authHeader) + .executeSingleOperationsOverWebsocket(true); + + try (DynamicGraphQLClient client = clientBuilder.build()) { + CompletableFuture authenticationExpired = new CompletableFuture<>(); + AtomicBoolean receivedValue = new AtomicBoolean(false); + client.subscription("subscription { sub { value } }").subscribe().with(item -> { + if (item.hasData()) { + receivedValue.set(true); + } else { + authenticationExpired.completeExceptionally(new RuntimeException("Subscription provided no data")); + } + }, cause -> { + if (cause.getMessage().contains("Authentication expired")) { + authenticationExpired.complete(null); + } else { + authenticationExpired + .completeExceptionally(new RuntimeException("Invalid close response from server.", cause)); + } + }, () -> authenticationExpired + .completeExceptionally(new RuntimeException("Subscription should not complete successfully"))); + + authenticationExpired.get(10, TimeUnit.SECONDS); + if (!receivedValue.get()) { + throw new RuntimeException("Did not receive subscription value"); + } + } + } + + private String getAuthHeader() { + io.restassured.response.Response response = RestAssured.given() + .contentType("application/x-www-form-urlencoded") + .accept("application/json") + .formParam("username", "alice") + .formParam("password", "alice") + .param("client_id", "quarkus-app") + .param("client_secret", "secret") + .formParam("grant_type", "password") + .post(keycloakRealm + "/protocol/openid-connect/token"); + + return "Bearer " + response.getBody().jsonPath().getString("access_token"); + } +} diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/SecuredResource.java b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/SecuredResource.java new file mode 100644 index 0000000000000..1957991834db2 --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/SecuredResource.java @@ -0,0 +1,42 @@ +package io.quarkus.io.smallrye.graphql.keycloak; + +import jakarta.annotation.security.RolesAllowed; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; + +import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.graphql.api.Subscription; +import io.smallrye.mutiny.Multi; + +@GraphQLApi +public class SecuredResource { + + // Seems to be a requirement to have a query or mutation in a GraphQLApi. + // This is a workaround for the time being. + @Query + public TestResponse unusedQuery() { + return null; + } + + @Subscription + @RolesAllowed("user") + @NonBlocking + public Multi sub() { + return Multi.createFrom().emitter(emitter -> emitter.emit(new TestResponse("Hello World"))); + } + + public static class TestResponse { + + private final String value; + + public TestResponse(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + +} diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/main/resources/application.properties b/integration-tests/smallrye-graphql-client-keycloak/src/main/resources/application.properties new file mode 100644 index 0000000000000..20c981d528c15 --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/main/resources/application.properties @@ -0,0 +1,4 @@ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.secret=secret +quarkus.smallrye-graphql.log-payload=queryAndVariables +quarkus.keycloak.devservices.enabled=false diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryIT.java b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryIT.java new file mode 100644 index 0000000000000..758e4780144ae --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.smallrye.graphql.keycloak; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class GraphQLAuthExpiryIT extends GraphQLAuthExpiryTest { +} diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java new file mode 100644 index 0000000000000..554753d09fc98 --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java @@ -0,0 +1,32 @@ +package io.quarkus.it.smallrye.graphql.keycloak; + +import static io.restassured.RestAssured.when; + +import java.net.URL; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusTest; + +/** + * See `GraphQLClientTester` for the actual testing code that uses GraphQL clients. + */ +@QuarkusTest +@QuarkusTestResource(KeycloakRealmResourceManager.class) +public class GraphQLAuthExpiryTest { + + @TestHTTPResource + URL url; + + @Test + public void testDynamicClientWebSocketAuthenticationExpiry() { + when() + .get("/dynamic-subscription-auth-expiry/" + url.toString()) + .then() + .log().everything() + .statusCode(204); + } + +} diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java new file mode 100644 index 0000000000000..80dd04f08a79e --- /dev/null +++ b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java @@ -0,0 +1,133 @@ +package io.quarkus.it.smallrye.graphql.keycloak; + +import java.io.IOException; +import java.util.*; + +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.*; +import org.keycloak.util.JsonSerialization; +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager { + + private static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth"); + //private static String KEYCLOAK_SERVER_URL; + private static final String KEYCLOAK_REALM = "quarkus"; + //private static final String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:22.0.5"; + + private GenericContainer keycloak; + + @Override + public Map start() { + RealmRepresentation realm = createRealm(KEYCLOAK_REALM); + realm.setRevokeRefreshToken(true); + realm.setRefreshTokenMaxReuse(0); + realm.setAccessTokenLifespan(3); + + realm.getClients().add(createClient("quarkus-app")); + realm.getUsers().add(createUser("alice", "user")); + + try { + Response response = RestAssured + .given() + .auth().oauth2(getAdminAccessToken()) + .contentType("application/json") + .body(JsonSerialization.writeValueAsBytes(realm)) + .when() + .post(KEYCLOAK_SERVER_URL + "/admin/realms"); + response.then() + .statusCode(201); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + Map properties = new HashMap<>(); + + properties.put("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); + properties.put("keycloak.url", KEYCLOAK_SERVER_URL); + + return properties; + } + + private static String getAdminAccessToken() { + return RestAssured + .given() + .param("grant_type", "password") + .param("username", "admin") + .param("password", "admin") + .param("client_id", "admin-cli") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getToken(); + } + + private static RealmRepresentation createRealm(String name) { + RealmRepresentation realm = new RealmRepresentation(); + + realm.setRealm(name); + realm.setEnabled(true); + realm.setUsers(new ArrayList<>()); + realm.setClients(new ArrayList<>()); + realm.setAccessTokenLifespan(3); + realm.setSsoSessionMaxLifespan(3); + RolesRepresentation roles = new RolesRepresentation(); + List realmRoles = new ArrayList<>(); + + roles.setRealm(realmRoles); + realm.setRoles(roles); + + realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); + realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); + + return realm; + } + + private static ClientRepresentation createClient(String clientId) { + ClientRepresentation client = new ClientRepresentation(); + + client.setClientId(clientId); + client.setPublicClient(false); + client.setSecret("secret"); + client.setDirectAccessGrantsEnabled(true); + client.setServiceAccountsEnabled(true); + client.setRedirectUris(Arrays.asList("*")); + client.setEnabled(true); + client.setDefaultClientScopes(List.of("microprofile-jwt")); + + return client; + } + + private static UserRepresentation createUser(String username, String... realmRoles) { + UserRepresentation user = new UserRepresentation(); + + user.setUsername(username); + user.setEnabled(true); + user.setCredentials(new ArrayList<>()); + user.setRealmRoles(Arrays.asList(realmRoles)); + user.setEmail(username + "@gmail.com"); + + CredentialRepresentation credential = new CredentialRepresentation(); + + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue(username); + credential.setTemporary(false); + + user.getCredentials().add(credential); + + return user; + } + + @Override + public void stop() { + RestAssured + .given() + .auth().oauth2(getAdminAccessToken()) + .when() + .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); + } +} From 2bfe3903094da3273d9195d3d2891becd7d93b24 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 15 Nov 2023 16:00:44 +0100 Subject: [PATCH 019/212] Use Vert.x instace of Netty to schedule the closing task (cherry picked from commit a5246bf5ab1ce1a99c7340bfe64d3ec49df196d7) --- .../SmallRyeGraphQLOverWebSocketHandler.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java index d593b32ef6640..c309a4f09eaf7 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLOverWebSocketHandler.java @@ -1,11 +1,9 @@ package io.quarkus.smallrye.graphql.runtime; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.jboss.logging.Logger; -import io.netty.util.concurrent.ScheduledFuture; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; @@ -13,9 +11,9 @@ import io.smallrye.graphql.websocket.GraphQLWebsocketHandler; import io.smallrye.graphql.websocket.graphqltransportws.GraphQLTransportWSSubprotocolHandler; import io.smallrye.graphql.websocket.graphqlws.GraphQLWSSubprotocolHandler; +import io.vertx.core.Handler; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.ServerWebSocket; -import io.vertx.core.net.impl.ConnectionBase; import io.vertx.ext.web.RoutingContext; /** @@ -60,27 +58,30 @@ protected void doHandle(final RoutingContext ctx) { } QuarkusHttpUser user = (QuarkusHttpUser) ctx.user(); - ScheduledFuture authExpiryFuture = null; + long cancellation = -1L; // Do not use 0, as you won't be able to distinguish between not set, and the first task Id if (user != null) { //close the connection when the identity expires Long expire = user.getSecurityIdentity().getAttribute("quarkus.identity.expire-time"); if (expire != null) { - authExpiryFuture = ((ConnectionBase) ctx.request().connection()).channel().eventLoop() - .schedule(() -> { - if (!serverWebSocket.isClosed()) { - serverWebSocket.close((short) 1008, "Authentication expired"); + cancellation = ctx.vertx().setTimer((expire * 1000) - System.currentTimeMillis(), + new Handler() { + @Override + public void handle(Long event) { + if (!serverWebSocket.isClosed()) { + serverWebSocket.close((short) 1008, "Authentication expired"); + } } - }, (expire * 1000) - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + }); } } log.debugf("Starting websocket with subprotocol = %s", subprotocol); GraphQLWebsocketHandler finalHandler = handler; - ScheduledFuture finalAuthExpiryFuture = authExpiryFuture; + long finalCancellation = cancellation; serverWebSocket.closeHandler(v -> { finalHandler.onClose(); - if (finalAuthExpiryFuture != null) { - finalAuthExpiryFuture.cancel(false); + if (finalCancellation != -1) { + ctx.vertx().cancelTimer(finalCancellation); } }); serverWebSocket.endHandler(v -> finalHandler.onEnd()); From e3344471a078a408cd5d95c1609e0f624adcc323 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Thu, 16 Nov 2023 08:33:56 +0100 Subject: [PATCH 020/212] Add test for graphql over websockets with only HTTP-based permissions (cherry picked from commit 631a30794a601d79dcc33272b0867814c08e8494) --- ...cketAuthenticationHttpPermissionsTest.java | 137 ++++++++++++++++++ ...cation-secured-http-permissions.properties | 13 ++ 2 files changed, 150 insertions(+) create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationHttpPermissionsTest.java create mode 100644 extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured-http-permissions.properties diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationHttpPermissionsTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationHttpPermissionsTest.java new file mode 100644 index 0000000000000..f2a72d45f8231 --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientWebSocketAuthenticationHttpPermissionsTest.java @@ -0,0 +1,137 @@ +package io.quarkus.smallrye.graphql.client.deployment; + +import jakarta.annotation.security.RolesAllowed; + +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.graphql.api.Subscription; +import io.smallrye.graphql.client.Response; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.helpers.test.AssertSubscriber; +import io.vertx.core.http.UpgradeRejectedException; + +/** + * Due to the complexity of establishing a WebSocket, WebSocket/Subscription testing of the GraphQL server is done here, + * as the client framework comes in very useful for establishing the connection to the server. + *
+ * This test establishes connections to the server, and ensures that the connected user has the necessary permissions to + * execute the operation. + */ +public class DynamicGraphQLClientWebSocketAuthenticationHttpPermissionsTest { + + static String url = "http://" + System.getProperty("quarkus.http.host", "localhost") + ":" + + System.getProperty("quarkus.http.test-port", "8081") + "/graphql"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(SecuredApi.class, Foo.class) + .addAsResource("application-secured-http-permissions.properties", "application.properties") + .addAsResource("users.properties") + .addAsResource("roles.properties") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + @Disabled("TODO: enable after upgrade to smallrye-graphql 1.6.1, with 1.6.0 a websocket upgrade failure causes a hang here") + @Test + public void testUnauthenticatedForQueryWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url) + .executeSingleOperationsOverWebsocket(true); + try (DynamicGraphQLClient client = clientBuilder.build()) { + try { + client.executeSync("{ baz { message} }"); + Assertions.fail("WebSocket upgrade should fail"); + } catch (UpgradeRejectedException e) { + // ok + } + } + } + + @Test + public void testUnauthenticatedForSubscriptionWebSocket() throws Exception { + DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() + .url(url); + try (DynamicGraphQLClient client = clientBuilder.build()) { + AssertSubscriber subscriber = new AssertSubscriber<>(); + client.subscription("{ bazSub { message} }").subscribe().withSubscriber(subscriber); + subscriber.awaitFailure().assertFailedWith(UpgradeRejectedException.class); + } + } + + public static class Foo { + + private String message; + + public Foo(String foo) { + this.message = foo; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + } + + @GraphQLApi + public static class SecuredApi { + + @Query + @RolesAllowed("fooRole") + @NonBlocking + public Foo foo() { + return new Foo("foo"); + } + + @Query + @RolesAllowed("barRole") + public Foo bar() { + return new Foo("bar"); + } + + @Query + public Foo baz() { + return new Foo("baz"); + } + + @Subscription + @RolesAllowed("fooRole") + public Multi fooSub() { + return Multi.createFrom().emitter(emitter -> { + emitter.emit(new Foo("foo")); + emitter.complete(); + }); + } + + @Subscription + @RolesAllowed("barRole") + public Multi barSub() { + return Multi.createFrom().emitter(emitter -> { + emitter.emit(new Foo("bar")); + emitter.complete(); + }); + } + + @Subscription + public Multi bazSub() { + return Multi.createFrom().emitter(emitter -> { + emitter.emit(new Foo("baz")); + emitter.complete(); + }); + } + + } +} diff --git a/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured-http-permissions.properties b/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured-http-permissions.properties new file mode 100644 index 0000000000000..770567e9e3565 --- /dev/null +++ b/extensions/smallrye-graphql-client/deployment/src/test/resources/application-secured-http-permissions.properties @@ -0,0 +1,13 @@ +quarkus.security.users.file.enabled=true +quarkus.security.users.file.plain-text=true +quarkus.security.users.file.users=users.properties +quarkus.security.users.file.roles=roles.properties +quarkus.http.auth.basic=true + +quarkus.smallrye-graphql.log-payload=queryAndVariables +quarkus.smallrye-graphql.print-data-fetcher-exception=true +quarkus.smallrye-graphql.error-extension-fields=exception,classification,code,description,validationErrorType,queryPath + +quarkus.http.auth.permission.authenticated.paths=/graphql +quarkus.http.auth.permission.authenticated.methods=GET,POST +quarkus.http.auth.permission.authenticated.policy=authenticated \ No newline at end of file From 8edc9bace2f6c3023fb3bf38c21ddacaf128cb14 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Thu, 16 Nov 2023 11:03:06 +0100 Subject: [PATCH 021/212] Fix native mode GraphQLAuthExpiryIT (cherry picked from commit c1fde71411588022c78c53a43b4884bb3229a1be) --- .../smallrye-graphql-client-keycloak/pom.xml | 13 +++++++++ .../keycloak/GraphQLAuthExpiryTester.java | 27 +++---------------- .../keycloak/GraphQLAuthExpiryTest.java | 3 ++- .../KeycloakRealmResourceManager.java | 13 +++++++++ 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/integration-tests/smallrye-graphql-client-keycloak/pom.xml b/integration-tests/smallrye-graphql-client-keycloak/pom.xml index e87c0810c3da8..b1481bb6df0bf 100644 --- a/integration-tests/smallrye-graphql-client-keycloak/pom.xml +++ b/integration-tests/smallrye-graphql-client-keycloak/pom.xml @@ -29,9 +29,14 @@ io.quarkus quarkus-resteasy-deployment + + io.quarkus + quarkus-oidc + io.rest-assured rest-assured + test @@ -85,7 +90,15 @@ io.quarkus quarkus-oidc-deployment + ${project.version} + pom test + + + * + * + + diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java index 3aa53e1acb18a..9a8c49e6b28f4 100644 --- a/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java +++ b/integration-tests/smallrye-graphql-client-keycloak/src/main/java/io/quarkus/io/smallrye/graphql/keycloak/GraphQLAuthExpiryTester.java @@ -8,9 +8,6 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import io.restassured.RestAssured; import io.smallrye.common.annotation.Blocking; import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder; @@ -26,19 +23,14 @@ @Path("/") public class GraphQLAuthExpiryTester { - @ConfigProperty(name = "quarkus.oidc.auth-server-url") - String keycloakRealm; - @GET - @Path("/dynamic-subscription-auth-expiry/{url}") + @Path("/dynamic-subscription-auth-expiry/{token}/{url}") @Blocking - public void dynamicSubscription(@PathParam("url") String url) + public void dynamicSubscription(@PathParam("token") String token, @PathParam("url") String url) throws Exception { - String authHeader = getAuthHeader(); - DynamicGraphQLClientBuilder clientBuilder = DynamicGraphQLClientBuilder.newBuilder() .url(url + "/graphql") - .header("Authorization", authHeader) + .header("Authorization", "Bearer " + token) .executeSingleOperationsOverWebsocket(true); try (DynamicGraphQLClient client = clientBuilder.build()) { @@ -67,17 +59,4 @@ public void dynamicSubscription(@PathParam("url") String url) } } - private String getAuthHeader() { - io.restassured.response.Response response = RestAssured.given() - .contentType("application/x-www-form-urlencoded") - .accept("application/json") - .formParam("username", "alice") - .formParam("password", "alice") - .param("client_id", "quarkus-app") - .param("client_secret", "secret") - .formParam("grant_type", "password") - .post(keycloakRealm + "/protocol/openid-connect/token"); - - return "Bearer " + response.getBody().jsonPath().getString("access_token"); - } } diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java index 554753d09fc98..01338e9915215 100644 --- a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java +++ b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/GraphQLAuthExpiryTest.java @@ -22,8 +22,9 @@ public class GraphQLAuthExpiryTest { @Test public void testDynamicClientWebSocketAuthenticationExpiry() { + String token = KeycloakRealmResourceManager.getAccessToken(); when() - .get("/dynamic-subscription-auth-expiry/" + url.toString()) + .get("/dynamic-subscription-auth-expiry/" + token + "/" + url.toString()) .then() .log().everything() .statusCode(204); diff --git a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java index 80dd04f08a79e..a527c265f76af 100644 --- a/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java +++ b/integration-tests/smallrye-graphql-client-keycloak/src/test/java/io/quarkus/it/smallrye/graphql/keycloak/KeycloakRealmResourceManager.java @@ -130,4 +130,17 @@ public void stop() { .when() .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); } + + public static String getAccessToken() { + io.restassured.response.Response response = RestAssured.given() + .contentType("application/x-www-form-urlencoded") + .accept("application/json") + .formParam("username", "alice") + .formParam("password", "alice") + .param("client_id", "quarkus-app") + .param("client_secret", "secret") + .formParam("grant_type", "password") + .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token"); + return response.getBody().jsonPath().getString("access_token"); + } } From 41ad26158884e97ecfbee709edc03006450a6291 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 16 Nov 2023 16:41:47 +0000 Subject: [PATCH 022/212] Bump smallrye-jwt version to 4.4.0 (cherry picked from commit 30458904e0ceaa0d6b44e74d6a18aadced2dc250) --- 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 d4031b931096d..f75c135c0a77d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -56,7 +56,7 @@ 3.7.0 2.6.0 6.2.6 - 4.3.1 + 4.4.0 2.1.0 1.0.13 3.0.1 From 2840e760be560df0bcde49134e5c413ef7546e90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:26:15 +0000 Subject: [PATCH 023/212] Bump com.squareup.okio:okio from 1.17.2 to 1.17.6 in /bom/application Bumps [com.squareup.okio:okio](https://github.com/square/okio) from 1.17.2 to 1.17.6. - [Changelog](https://github.com/square/okio/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/okio/compare/okio-parent-1.17.2...okio-parent-1.17.6) --- updated-dependencies: - dependency-name: com.squareup.okio:okio dependency-type: direct:production ... Signed-off-by: dependabot[bot] (cherry picked from commit d9e25ced15cabf4cc121952fa872c42c4457e4eb) --- 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 f75c135c0a77d..7b5173d1f084d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -177,7 +177,7 @@ 0.34.1 3.25.5 3.14.9 - 1.17.2 + 1.17.6 0.2.1 4.9.2 5.2.SP7 From e09a028763babe54fb24d1b111407b1c331f9379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:18:20 +0000 Subject: [PATCH 024/212] Bump com.unboundid:unboundid-ldapsdk from 6.0.9 to 6.0.10 Bumps [com.unboundid:unboundid-ldapsdk](https://github.com/pingidentity/ldapsdk) from 6.0.9 to 6.0.10. - [Release notes](https://github.com/pingidentity/ldapsdk/releases) - [Changelog](https://github.com/pingidentity/ldapsdk/blob/master/docs/release-notes.html) - [Commits](https://github.com/pingidentity/ldapsdk/compare/6.0.9...6.0.10) --- updated-dependencies: - dependency-name: com.unboundid:unboundid-ldapsdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit dcd83e5c12bfb344bf2333b71c3e7a682993458e) --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index e82ebbc846621..cfe5555cab915 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -106,7 +106,7 @@ quay.io/keycloak/keycloak:${keycloak.version} quay.io/keycloak/keycloak:${keycloak.wildfly.version}-legacy - 6.0.9 + 6.0.10 3.24.2 From 930a775cbdb0b1b2945defa4c29d56583620aa2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:23:55 +0000 Subject: [PATCH 025/212] Bump io.quarkus:quarkus-platform-bom-maven-plugin from 0.0.97 to 0.0.99 Bumps [io.quarkus:quarkus-platform-bom-maven-plugin](https://github.com/quarkusio/quarkus-platform-bom-generator) from 0.0.97 to 0.0.99. - [Release notes](https://github.com/quarkusio/quarkus-platform-bom-generator/releases) - [Commits](https://github.com/quarkusio/quarkus-platform-bom-generator/compare/0.0.97...0.0.99) --- updated-dependencies: - dependency-name: io.quarkus:quarkus-platform-bom-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 5ff4a090f76979d584a08794d0e28495a79bb8ca) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8e7cfdf42099f..90c5862d3c077 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ jdbc:postgresql:hibernate_orm_test 4.5.1 - 0.0.97 + 0.0.99 false false From 57de134f3ad95d177dd18337b05e2376d0923c4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:24:36 +0000 Subject: [PATCH 026/212] Bump org.apache.commons:commons-text from 1.10.0 to 1.11.0 Bumps org.apache.commons:commons-text from 1.10.0 to 1.11.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit 393452cf051b096f2d5842f1102058bf0735b17f) --- 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 7b5173d1f084d..8faa6ff92cfd5 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -198,7 +198,7 @@ 4.7.5 1.1.0 1.24.0 - 1.10.0 + 1.11.0 2.10.1 1.1.1.Final 2.20.0 From d1a2ffd7a2daa064f07b1faea39fe025987f388e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 17 Nov 2023 10:19:19 +0100 Subject: [PATCH 027/212] Fix snapshots following a collision of pull requests (cherry picked from commit cc155b24565bef395d8ff3482c479028bae5270e) --- ...us-my-quarkiverse-ext_integration-tests_pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml index 3efa805c3451b..efeae2164b76c 100644 --- a/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml +++ b/integration-tests/maven/src/test/resources/__snapshots__/CreateExtensionMojoIT/testCreateQuarkiverseExtension/quarkus-my-quarkiverse-ext_integration-tests_pom.xml @@ -59,15 +59,15 @@ integration-test verify - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + +
From 47110c6737c597c2e00f090fd5da269231f7769b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 17 Nov 2023 11:51:21 +0100 Subject: [PATCH 028/212] Ignore files coming from quarkus-ide-launcher jar (cherry picked from commit 6d853647c015a4a64addbb4dcfdbf3f83bebdb16) --- independent-projects/parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/parent/pom.xml b/independent-projects/parent/pom.xml index 67ff724afd4c0..0c88d626eeb8e 100644 --- a/independent-projects/parent/pom.xml +++ b/independent-projects/parent/pom.xml @@ -422,7 +422,7 @@ - **/quarkus-ide-launcher-*.jar + META-INF/ide-deps/** From 5ea58a121a5b68de4081aa978331e79cbc27bce1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 18 Nov 2023 13:43:50 +0100 Subject: [PATCH 029/212] Make sure we always include the Develocity scan URL in reports (cherry picked from commit 1af1783b1bf5fdbeae2a439138e2324841ffd187) --- .github/workflows/ci-actions-incremental.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 33e0b6c4b65c1..92b758f0d09ef 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -732,6 +732,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json + **/target/gradle-build-scan-url.txt LICENSE.txt retention-days: 2 - name: Save Build Scan @@ -926,7 +927,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json - target/gradle-build-scan-url.txt + **/target/gradle-build-scan-url.txt LICENSE.txt retention-days: 2 - name: Save Build Scan @@ -1023,7 +1024,7 @@ jobs: **/target/*-reports/TEST-*.xml **/build/test-results/test/TEST-*.xml target/build-report.json - target/gradle-build-scan-url.txt + **/target/gradle-build-scan-url.txt LICENSE.txt retention-days: 2 - name: Save Build Scan From 3bc7085ffb42aa5a55ee8d6c73b1dae6c8a7629d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 18 Nov 2023 13:50:46 +0100 Subject: [PATCH 030/212] Output JSON for debugging purposes (cherry picked from commit b45817a7c6c48097fbbb3ff7717524bb7318181a) --- .../develocity-publish-build-scans.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/develocity-publish-build-scans.yml b/.github/workflows/develocity-publish-build-scans.yml index a285e66a8445b..40866c25e6f2e 100644 --- a/.github/workflows/develocity-publish-build-scans.yml +++ b/.github/workflows/develocity-publish-build-scans.yml @@ -31,13 +31,12 @@ jobs: develocity-url: 'https://ge.quarkus.io' develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} skip-comment: true - - name: Push to summary - if: ${{ contains(fromJson(steps.extract-preapproved-developers.outputs.preapproved-developpers).preapproved-developers, github.event.workflow_run.actor.login) }} + - name: Inject build scans in reports + uses: quarkusio/action-helpers@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + action: inject-build-scans + workflow-run-id: ${{ github.event.workflow_run.id }} + - name: Output JSON file run: | - echo -n "Pull request: " >> ${GITHUB_STEP_SUMMARY} - cat pr-number.out >> ${GITHUB_STEP_SUMMARY} - echo >> ${GITHUB_STEP_SUMMARY} - echo >> ${GITHUB_STEP_SUMMARY} - echo "| Job | Status | Build scan |" >> ${GITHUB_STEP_SUMMARY} - echo "|---|---|---|" >> ${GITHUB_STEP_SUMMARY} - cat publication.out >> ${GITHUB_STEP_SUMMARY} + if [ -f build-metadata.json ]; then jq '.' build-metadata.json >> $GITHUB_STEP_SUMMARY; fi From 5aa9cd3aacbed058cb2308979136dc56074130b9 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 19 Nov 2023 01:34:15 +0900 Subject: [PATCH 031/212] Fixed sample code for KotlinModule initialization. Initialization using the constructor has been deprecated. (cherry picked from commit 2948027551663dd735cd5d8f9b5ddf004db58c16) --- docs/src/main/asciidoc/kotlin.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index ef117f0c8d0fb..0d7bc89658eb4 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -470,9 +470,9 @@ import io.fabric8.kubernetes.client.utils.Serialization import com.fasterxml.jackson.module.kotlin.KotlinModule ... - -Serialization.jsonMapper().registerModule(KotlinModule()) -Serialization.yamlMapper().registerModule(KotlinModule()) +val kotlinModule = KotlinModule.Builder().build() +Serialization.jsonMapper().registerModule(kotlinModule) +Serialization.yamlMapper().registerModule(kotlinModule) ---- _Please test this carefully on compilation to native images and fallback to Java-compatible Jackson bindings if you experience problems._ From 4c2b0e0731824e82db333e188328ac5621ab9b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sun, 19 Nov 2023 17:28:15 +0100 Subject: [PATCH 032/212] Support SecureField roles allowed config expressions (cherry picked from commit c3bb17331acd5ecd8bf54ba0fb679959d98bcd49) --- docs/src/main/asciidoc/resteasy-reactive.adoc | 21 ++++++- .../deployment/pom.xml | 6 +- .../ResteasyReactiveJacksonProcessor.java | 62 +++++++++++++++++++ .../test/AbstractPersonResource.java | 2 + .../test/CustomSerializationResource.java | 2 + .../jackson/deployment/test/Person.java | 22 +++++++ .../deployment/test/SimpleJsonResource.java | 8 +++ .../deployment/test/SimpleJsonTest.java | 58 ++++++++++++++++- ...ResteasyReactiveServerJacksonRecorder.java | 45 ++++++++++++++ .../RolesAllowedConfigExpStorage.java | 34 ++++++++++ .../security/SecurityPropertyFilter.java | 31 ++++++++++ .../deployment/SecurityProcessor.java | 24 +++++-- .../runtime/SecurityCheckRecorder.java | 11 ++++ ...olesAllowedConfigExpResolverBuildItem.java | 43 +++++++++++++ 14 files changed, 361 insertions(+), 8 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/RolesAllowedConfigExpStorage.java create mode 100644 extensions/security/spi/src/main/java/io/quarkus/security/spi/RolesAllowedConfigExpResolverBuildItem.java diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index 7d9792a76c145..a5e4e544f6649 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -1301,6 +1301,8 @@ public class Person { private final Long id; private final String first; private final String last; + @SecureField(rolesAllowed = ${role:admin}") <1> + private String address; public Person(Long id, String first, String last) { this.id = id; @@ -1319,8 +1321,20 @@ public class Person { public String getLast() { return last; } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } } ---- +<1> The `io.quarkus.resteasy.reactive.jackson.SecureField.rolesAllowed` property supports xref:config-reference.adoc#property-expressions[property expressions] +exactly in the same fashion the `jakarta.annotation.security.RolesAllowed` annotation does. For more information, please +refer to the xref:security-authorize-web-endpoints-reference.adoc#standard-security-annotations[Standard security annotations] +section of the Authorization of web endpoints guide. A very simple Jakarta REST Resource that uses `Person` could be: @@ -1337,7 +1351,7 @@ public class Person { @Path("{id}") @GET public Person getPerson(Long id) { - return new Person(id, "foo", "bar"); + return new Person(id, "foo", "bar", "Brick Lane"); } } ---- @@ -1350,7 +1364,8 @@ performs an HTTP GET on `/person/1` they will receive: { "id": 1, "first": "foo", - "last": "bar" + "last": "bar", + "address", "Brick Lane" } ---- @@ -1369,6 +1384,8 @@ Any user however that does not have the `admin` role will receive: NOTE: No additional configuration needs to be applied for this secure serialization to take place. However, users can use the `@io.quarkus.resteasy.reactive.jackson.EnableSecureSerialization` and `@io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization` annotation to opt in or out for specific Jakarta REST Resource classes or methods. +WARNING: Configuration expressions set with the `SecureField.rolesAllowed` property are validated during application startup even when the `@io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization` annotation is used. + ===== @JsonView support Jakarta REST methods can be annotated with https://fasterxml.github.io/jackson-annotations/javadoc/2.10/com/fasterxml/jackson/annotation/JsonView.html[@JsonView] diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/pom.xml index 87da39c8b69fc..f357b700c7e4c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/pom.xml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/pom.xml @@ -50,7 +50,11 @@ quarkus-jaxrs-client-reactive-deployment test - + + io.quarkus + quarkus-security-test-utils + test + diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 74f4cc444bc45..f40508ca11665 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.jackson.deployment.processor; +import static io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem.isSecurityConfigExpressionCandidate; import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_NDJSON; import static org.jboss.resteasy.reactive.common.util.RestMediaType.APPLICATION_STREAM_JSON; @@ -10,7 +11,10 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import jakarta.inject.Singleton; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.core.Cookie; @@ -34,13 +38,20 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.SynthesisFinishedBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem; @@ -54,6 +65,7 @@ import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder; import io.quarkus.resteasy.reactive.jackson.runtime.mappers.DefaultMismatchedInputException; import io.quarkus.resteasy.reactive.jackson.runtime.mappers.NativeInvalidDefinitionExceptionMapper; +import io.quarkus.resteasy.reactive.jackson.runtime.security.RolesAllowedConfigExpStorage; import io.quarkus.resteasy.reactive.jackson.runtime.security.SecurityCustomSerialization; import io.quarkus.resteasy.reactive.jackson.runtime.serialisers.BasicServerJacksonMessageBodyWriter; import io.quarkus.resteasy.reactive.jackson.runtime.serialisers.FullyFeaturedServerJacksonMessageBodyReader; @@ -69,6 +81,7 @@ import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyReaderBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem; +import io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem; import io.quarkus.vertx.deployment.ReinitializeVertxJsonBuildItem; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -310,6 +323,46 @@ void handleJsonAnnotations(Optional resourceSca } } + @Record(ExecutionTime.STATIC_INIT) + @BuildStep + public void resolveRolesAllowedConfigExpressions(BuildProducer resolverProducer, + Capabilities capabilities, ResteasyReactiveServerJacksonRecorder recorder, CombinedIndexBuildItem indexBuildItem, + BuildProducer syntheticBeanProducer, + BuildProducer initAndValidateItemProducer) { + if (capabilities.isPresent(Capability.SECURITY)) { + BiConsumer> configValRecorder = null; + for (AnnotationInstance instance : indexBuildItem.getIndex().getAnnotations(SECURE_FIELD)) { + for (String role : instance.value("rolesAllowed").asStringArray()) { + if (isSecurityConfigExpressionCandidate(role)) { + if (configValRecorder == null) { + var storage = recorder.createConfigExpToAllowedRoles(); + configValRecorder = recorder.recordRolesAllowedConfigExpression(storage); + syntheticBeanProducer.produce(SyntheticBeanBuildItem + .configure(RolesAllowedConfigExpStorage.class) + .scope(Singleton.class) + .supplier(recorder.createRolesAllowedConfigExpStorage(storage)) + .unremovable() + .done()); + initAndValidateItemProducer.produce(new InitAndValidateRolesAllowedConfigExp()); + } + resolverProducer.produce(new RolesAllowedConfigExpResolverBuildItem(role, configValRecorder)); + } + } + } + } + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + @Consume(RuntimeConfigSetupCompleteBuildItem.class) + @Consume(SynthesisFinishedBuildItem.class) + public void initializeRolesAllowedConfigExp(ResteasyReactiveServerJacksonRecorder recorder, + Optional initAndValidateItem) { + if (initAndValidateItem.isPresent()) { + recorder.initAndValidateRolesAllowedConfigExp(); + } + } + @BuildStep public void handleFieldSecurity(ResteasyReactiveResourceMethodEntriesBuildItem resourceMethodEntries, JaxRsResourceIndexBuildItem index, @@ -432,4 +485,13 @@ private String getMethodId(MethodInfo methodInfo, ClassInfo declaringClassInfo) return MethodId.get(methodInfo.name(), declaringClassInfo.name().toString(), parameterClassNames.toArray(EMPTY_STRING_ARRAY)); } + + /** + * Purely marker build item so that we know at least one allowed role with configuration + * expressions has been detected. + */ + public static final class InitAndValidateRolesAllowedConfigExp extends SimpleBuildItem { + private InitAndValidateRolesAllowedConfigExp() { + } + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/AbstractPersonResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/AbstractPersonResource.java index 00bb0ab2d3d51..5c9c00326443e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/AbstractPersonResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/AbstractPersonResource.java @@ -11,6 +11,8 @@ public Person abstractPerson() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); return person; } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomSerializationResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomSerializationResource.java index 6bbb582914d7a..becd3f13bed80 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomSerializationResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/CustomSerializationResource.java @@ -44,6 +44,8 @@ public Person getPerson() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); return person; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java index 7f5abadd4cc69..133e3a9f619e4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/Person.java @@ -17,6 +17,12 @@ public class Person { @JsonView(Views.Private.class) public int id = 0; + @SecureField(rolesAllowed = { "${admin-expression:disabled}", "${user-expression:disabled}" }) + private String address; + + @SecureField(rolesAllowed = "${birth-date-roles:disabled}") + private String birthDate; + public String getFirst() { return first; } @@ -40,4 +46,20 @@ public int getId() { public void setId(int id) { this.id = id; } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getBirthDate() { + return birthDate; + } + + public void setBirthDate(String birthDate) { + this.birthDate = birthDate; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java index b538997b62f25..9621a6fdde84a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java @@ -56,6 +56,8 @@ public Person getPerson() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); return person; } @@ -247,6 +249,8 @@ public void run() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); response.resume(person); } }).start(); @@ -285,6 +289,8 @@ public Multi getMulti1() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); return Multi.createFrom().items(person); } @@ -294,6 +300,8 @@ public Multi getMulti2() { Person person = new Person(); person.setFirst("Bob"); person.setLast("Builder"); + person.setAddress("10 Downing St"); + person.setBirthDate("November 30, 1874"); Person person2 = new Person(); person2.setFirst("Bob2"); person2.setLast("Builder2"); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java index 9ebb1b30d6d5e..a36cd7a2aef68 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java @@ -11,10 +11,13 @@ import org.hamcrest.Matchers; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.security.test.utils.TestIdentityController; +import io.quarkus.security.test.utils.TestIdentityProvider; import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; @@ -28,7 +31,10 @@ public JavaArchive get() { return ShrinkWrap.create(JavaArchive.class) .addClasses(Person.class, SimpleJsonResource.class, User.class, Views.class, SuperClass.class, OtherPersonResource.class, AbstractPersonResource.class, DataItem.class, Item.class, - NoopReaderInterceptor.class); + NoopReaderInterceptor.class, TestIdentityProvider.class, TestIdentityController.class) + .addAsResource(new StringAsset("admin-expression=admin\n" + + "user-expression=user\n" + + "birth-date-roles=alice,bob\n"), "application.properties"); } }); @@ -389,6 +395,52 @@ public void testSecureRestResponsePerson() { doTestSecurePerson("/simple", "/secure-rest-response-person"); } + @Test + public void testSecureFieldRolesAllowedConfigExp() { + TestIdentityController.resetRoles().add("max", "max", "admin"); + RestAssured.given() + .auth().preemptive().basic("max", "max") + .get("/simple/secure-person") + .then() + .statusCode(200) + .contentType("application/json") + .header("transfer-encoding", nullValue()) + .header("content-length", notNullValue()) + .body(containsString("Bob")) + .body(containsString("0")) + .body(containsString("10 Downing St")) + .body(not(containsString("November 30, 1874"))) + .body(containsString("Builder")); + TestIdentityController.resetRoles().add("max", "max", "user"); + RestAssured.given() + .auth().preemptive().basic("max", "max") + .get("/simple/secure-person") + .then() + .statusCode(200) + .contentType("application/json") + .header("transfer-encoding", nullValue()) + .header("content-length", notNullValue()) + .body(containsString("Bob")) + .body(containsString("0")) + .body(containsString("10 Downing St")) + .body(not(containsString("November 30, 1874"))) + .body(not(containsString("Builder"))); + TestIdentityController.resetRoles().add("max", "max", "alice"); + RestAssured.given() + .auth().preemptive().basic("max", "max") + .get("/simple/secure-person") + .then() + .statusCode(200) + .contentType("application/json") + .header("transfer-encoding", nullValue()) + .header("content-length", notNullValue()) + .body(containsString("Bob")) + .body(containsString("0")) + .body(not(containsString("10 Downing St"))) + .body(containsString("November 30, 1874")) + .body(not(containsString("Builder"))); + } + private void doTestSecurePerson(String basePath, final String path) { RestAssured.get(basePath + path) .then() @@ -398,6 +450,8 @@ private void doTestSecurePerson(String basePath, final String path) { .header("content-length", notNullValue()) .body(containsString("Bob")) .body(containsString("0")) + .body(not(containsString("10 Downing St"))) + .body(not(containsString("November 30, 1874"))) .body(not(containsString("Builder"))); } @@ -410,6 +464,8 @@ private void doTestSecurePersonWithPublicView(String basePath, final String path .header("content-length", notNullValue()) .body(containsString("Bob")) .body(not(containsString("0"))) + .body(not(containsString("10 Downing St"))) + .body(not(containsString("November 30, 1874"))) .body(not(containsString("Builder"))); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/ResteasyReactiveServerJacksonRecorder.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/ResteasyReactiveServerJacksonRecorder.java index 2e86508b9f498..a55ddadc4e3ce 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/ResteasyReactiveServerJacksonRecorder.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/ResteasyReactiveServerJacksonRecorder.java @@ -3,12 +3,18 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.Supplier; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; +import io.quarkus.arc.Arc; +import io.quarkus.resteasy.reactive.jackson.runtime.security.RolesAllowedConfigExpStorage; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; @@ -19,6 +25,45 @@ public class ResteasyReactiveServerJacksonRecorder { private static final Map> customSerializationMap = new HashMap<>(); private static final Map> customDeserializationMap = new HashMap<>(); + /* STATIC INIT */ + public RuntimeValue>> createConfigExpToAllowedRoles() { + return new RuntimeValue<>(new ConcurrentHashMap<>()); + } + + /* STATIC INIT */ + public BiConsumer> recordRolesAllowedConfigExpression( + RuntimeValue>> configExpToAllowedRoles) { + return new BiConsumer>() { + @Override + public void accept(String configKey, Supplier configValueSupplier) { + configExpToAllowedRoles.getValue().put(configKey, configValueSupplier); + } + }; + } + + /* STATIC INIT */ + public Supplier createRolesAllowedConfigExpStorage( + RuntimeValue>> configExpToAllowedRoles) { + return new Supplier() { + @Override + public RolesAllowedConfigExpStorage get() { + Map> map = configExpToAllowedRoles.getValue(); + if (map.isEmpty()) { + // there is no reason why this should happen, because we initialize the bean ourselves + // when runtime configuration is ready + throw new IllegalStateException( + "The 'RolesAllowedConfigExpStorage' bean is created before runtime configuration is ready"); + } + return new RolesAllowedConfigExpStorage(configExpToAllowedRoles.getValue()); + } + }; + } + + /* RUNTIME INIT */ + public void initAndValidateRolesAllowedConfigExp() { + Arc.container().instance(RolesAllowedConfigExpStorage.class).get().resolveRolesAllowedConfigExp(); + } + public void recordJsonView(String targetId, String className) { jsonViewMap.put(targetId, loadClass(className)); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/RolesAllowedConfigExpStorage.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/RolesAllowedConfigExpStorage.java new file mode 100644 index 0000000000000..e1ef44af6bed1 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/RolesAllowedConfigExpStorage.java @@ -0,0 +1,34 @@ +package io.quarkus.resteasy.reactive.jackson.runtime.security; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class RolesAllowedConfigExpStorage { + + private final Map> configExpToAllowedRoles; + private final Map rolesAllowedExpCache; + + public RolesAllowedConfigExpStorage(Map> configExpToAllowedRoles) { + this.configExpToAllowedRoles = Map.copyOf(configExpToAllowedRoles); + this.rolesAllowedExpCache = new HashMap<>(); + } + + /** + * Transforms configuration expressions to configuration values. + * Should be called on startup once runtime config is ready. + */ + public synchronized void resolveRolesAllowedConfigExp() { + if (rolesAllowedExpCache.isEmpty()) { + for (Map.Entry> e : configExpToAllowedRoles.entrySet()) { + String roleConfigExp = e.getKey(); + Supplier rolesSupplier = e.getValue(); + rolesAllowedExpCache.put(roleConfigExp, rolesSupplier.get()); + } + } + } + + String[] getRoles(String configExpression) { + return rolesAllowedExpCache.get(configExpression); + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/SecurityPropertyFilter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/SecurityPropertyFilter.java index e361614fb7f3b..7d08f8b0fce5c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/SecurityPropertyFilter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/security/SecurityPropertyFilter.java @@ -12,6 +12,23 @@ public class SecurityPropertyFilter extends SimpleBeanPropertyFilter { static final String FILTER_ID = "securityFilter"; + private volatile InstanceHandle rolesAllowedConfigExpStorage; + + private RolesAllowedConfigExpStorage getRolesAllowedConfigExpStorage(ArcContainer container) { + if (rolesAllowedConfigExpStorage == null) { + synchronized (this) { + if (rolesAllowedConfigExpStorage == null) { + rolesAllowedConfigExpStorage = container.instance(RolesAllowedConfigExpStorage.class); + } + } + } + + if (rolesAllowedConfigExpStorage.isAvailable()) { + return rolesAllowedConfigExpStorage.get(); + } else { + return null; + } + } @Override protected boolean include(PropertyWriter writer) { @@ -31,7 +48,21 @@ protected boolean include(PropertyWriter writer) { } SecurityIdentity securityIdentity = instance.get(); + RolesAllowedConfigExpStorage rolesConfigExpStorage = getRolesAllowedConfigExpStorage(container); for (String role : secureField.rolesAllowed()) { + if (rolesConfigExpStorage != null) { + // role config expression => resolved roles + String[] roles = rolesConfigExpStorage.getRoles(role); + if (roles != null) { + for (String r : roles) { + if (securityIdentity.hasRole(r)) { + return true; + } + } + continue; + } + // at this point, we know 'role' is not a configuration expression + } if (securityIdentity.hasRole(role)) { return true; } diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index 6c4e61670bd02..d781085f835ac 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -105,6 +105,7 @@ import io.quarkus.security.runtime.interceptor.SecurityHandler; import io.quarkus.security.spi.AdditionalSecuredClassesBuildItem; import io.quarkus.security.spi.AdditionalSecuredMethodsBuildItem; +import io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem; import io.quarkus.security.spi.runtime.AuthorizationController; import io.quarkus.security.spi.runtime.DevModeDisabledAuthorizationController; import io.quarkus.security.spi.runtime.MethodDescription; @@ -520,6 +521,7 @@ void transformSecurityAnnotations(BuildProducer @Record(ExecutionTime.STATIC_INIT) void gatherSecurityChecks(BuildProducer syntheticBeans, BuildProducer configExpSecurityCheckProducer, + List rolesAllowedConfigExpResolverBuildItems, BeanArchiveIndexBuildItem beanArchiveBuildItem, BuildProducer classPredicate, BuildProducer configBuilderProducer, @@ -540,7 +542,7 @@ void gatherSecurityChecks(BuildProducer syntheticBeans, IndexView index = beanArchiveBuildItem.getIndex(); Map securityChecks = gatherSecurityAnnotations(index, configExpSecurityCheckProducer, additionalSecured.values(), config.denyUnannotated, recorder, configBuilderProducer, - reflectiveClassBuildItemBuildProducer); + reflectiveClassBuildItemBuildProducer, rolesAllowedConfigExpResolverBuildItems); for (AdditionalSecurityCheckBuildItem additionalSecurityCheck : additionalSecurityChecks) { securityChecks.put(additionalSecurityCheck.getMethodInfo(), additionalSecurityCheck.getSecurityCheck()); @@ -587,7 +589,8 @@ private Map gatherSecurityAnnotations(IndexView index BuildProducer configExpSecurityCheckProducer, Collection additionalSecuredMethods, boolean denyUnannotated, SecurityCheckRecorder recorder, BuildProducer configBuilderProducer, - BuildProducer reflectiveClassBuildItemBuildProducer) { + BuildProducer reflectiveClassBuildItemBuildProducer, + List rolesAllowedConfigExpResolverBuildItems) { Map methodToInstanceCollector = new HashMap<>(); Map classAnnotations = new HashMap<>(); @@ -670,11 +673,24 @@ public SecurityCheck apply(Set allowedRolesSet) { })); } + final boolean registerRolesAllowedConfigSource; + // way to resolve roles allowed configuration expressions specified via annotations to configuration values + if (!rolesAllowedConfigExpResolverBuildItems.isEmpty()) { + registerRolesAllowedConfigSource = true; + for (RolesAllowedConfigExpResolverBuildItem item : rolesAllowedConfigExpResolverBuildItems) { + recorder.recordRolesAllowedConfigExpression(item.getRoleConfigExpr(), keyIndex.getAndIncrement(), + item.getConfigValueRecorder()); + } + } else { + registerRolesAllowedConfigSource = hasRolesAllowedCheckWithConfigExp.get(); + } + if (hasRolesAllowedCheckWithConfigExp.get()) { - // make sure config expressions are resolved when app starts + // make sure config expressions are eagerly resolved inside security checks when app starts configExpSecurityCheckProducer .produce(new ConfigExpRolesAllowedSecurityCheckBuildItem()); - + } + if (registerRolesAllowedConfigSource) { // register config source with the Config system configBuilderProducer .produce(new RunTimeConfigBuilderBuildItem(QuarkusSecurityRolesAllowedConfigBuilder.class.getName())); diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java index 8ffc983e7e057..ee6639d2ef495 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java @@ -10,6 +10,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -68,6 +69,16 @@ public SecurityCheck rolesAllowedSupplier(String[] allowedRoles, int[] configExp return check; } + /* STATIC INIT */ + public void recordRolesAllowedConfigExpression(String configExpression, int configKeyIndex, + BiConsumer> configValueRecorder) { + QuarkusSecurityRolesAllowedConfigBuilder.addProperty(configKeyIndex, configExpression); + // one configuration expression resolves to string array because the expression can be list treated as list + Supplier configValSupplier = resolveRolesAllowedConfigExp(new String[] { configExpression }, + new int[] { 0 }, new int[] { configKeyIndex }); + configValueRecorder.accept(configExpression, configValSupplier); + } + private static Supplier resolveRolesAllowedConfigExp(String[] allowedRoles, int[] configExpIndexes, int[] configKeys) { diff --git a/extensions/security/spi/src/main/java/io/quarkus/security/spi/RolesAllowedConfigExpResolverBuildItem.java b/extensions/security/spi/src/main/java/io/quarkus/security/spi/RolesAllowedConfigExpResolverBuildItem.java new file mode 100644 index 0000000000000..95649dd6ca6ed --- /dev/null +++ b/extensions/security/spi/src/main/java/io/quarkus/security/spi/RolesAllowedConfigExpResolverBuildItem.java @@ -0,0 +1,43 @@ +package io.quarkus.security.spi; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Provides a way to transform roles allowed specified as configuration expressions in annotations to runtime + * configuration values. + */ +public final class RolesAllowedConfigExpResolverBuildItem extends MultiBuildItem { + private final String roleConfigExpr; + private final BiConsumer> configValueRecorder; + + /** + * @param roleConfigExpr roles allowed configuration expression + * @param configValueRecorder roles allowed supplier will be recorded to this consumer created during static-init; + * runtime roles allowed expressions are supplied correctly only when runtime config is ready + */ + public RolesAllowedConfigExpResolverBuildItem(String roleConfigExpr, + BiConsumer> configValueRecorder) { + this.roleConfigExpr = Objects.requireNonNull(roleConfigExpr); + this.configValueRecorder = Objects.requireNonNull(configValueRecorder); + } + + public String getRoleConfigExpr() { + return roleConfigExpr; + } + + public BiConsumer> getConfigValueRecorder() { + return configValueRecorder; + } + + public static boolean isSecurityConfigExpressionCandidate(String configExpression) { + if (configExpression == null || configExpression.length() < 4) { + return false; + } + final int exprStart = configExpression.indexOf("${"); + return exprStart >= 0 && configExpression.indexOf('}', exprStart + 2) > 0; + } +} From 572e22701235cf9617179fb91f2f9681d81d5f8b Mon Sep 17 00:00:00 2001 From: barreiro Date: Mon, 20 Nov 2023 01:12:39 +0000 Subject: [PATCH 033/212] recognize quarkus.tls.trust-all property by keycloak-admin-client extension (cherry picked from commit 6e414c29dcc77c8f168b233277a7989f78ab8b94) --- .../reactive/KeycloakAdminClientReactiveProcessor.java | 5 +++-- .../reactive/runtime/ResteasyReactiveClientProvider.java | 8 +++++++- .../ResteasyReactiveKeycloakAdminClientRecorder.java | 4 ++-- .../deployment/KeycloakAdminClientProcessor.java | 5 +++-- .../adminclient/ResteasyKeycloakAdminClientRecorder.java | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java b/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java index 0a486c5e024d1..27c8d4a6f95c2 100644 --- a/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java +++ b/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java @@ -25,6 +25,7 @@ import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled; import io.quarkus.keycloak.admin.client.reactive.runtime.ResteasyReactiveClientProvider; import io.quarkus.keycloak.admin.client.reactive.runtime.ResteasyReactiveKeycloakAdminClientRecorder; +import io.quarkus.runtime.TlsConfig; public class KeycloakAdminClientReactiveProcessor { @@ -53,8 +54,8 @@ public void nativeImage(BuildProducer serviceProviderP @Record(ExecutionTime.STATIC_INIT) @Produce(ServiceStartBuildItem.class) @BuildStep - public void integrate(ResteasyReactiveKeycloakAdminClientRecorder recorder) { - recorder.setClientProvider(); + public void integrate(ResteasyReactiveKeycloakAdminClientRecorder recorder, TlsConfig tlsConfig) { + recorder.setClientProvider(tlsConfig.trustAll); } @Record(ExecutionTime.RUNTIME_INIT) diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java index a8bb66a6d0096..c39ffee71d45a 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java @@ -30,9 +30,15 @@ public class ResteasyReactiveClientProvider implements ResteasyClientProvider { private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON); private static final int PROVIDER_PRIORITY = Priorities.USER + 100; // ensures that it will be used first + private final boolean tlsTrustAll; + + public ResteasyReactiveClientProvider(boolean tlsTrustAll) { + this.tlsTrustAll = tlsTrustAll; + } + @Override public Client newRestEasyClient(Object messageHandler, SSLContext sslContext, boolean disableTrustManager) { - ClientBuilderImpl clientBuilder = new ClientBuilderImpl().trustAll(disableTrustManager); + ClientBuilderImpl clientBuilder = new ClientBuilderImpl().trustAll(tlsTrustAll || disableTrustManager); return registerJacksonProviders(clientBuilder).build(); } diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java index 61d7605485442..12458c795592f 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java @@ -21,8 +21,8 @@ public ResteasyReactiveKeycloakAdminClientRecorder( this.keycloakAdminClientConfigRuntimeValue = keycloakAdminClientConfigRuntimeValue; } - public void setClientProvider() { - Keycloak.setClientProvider(new ResteasyReactiveClientProvider()); + public void setClientProvider(boolean tlsTrustAll) { + Keycloak.setClientProvider(new ResteasyReactiveClientProvider(tlsTrustAll)); } public Supplier createAdminClient() { diff --git a/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java b/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java index 056b3c1d5bf93..5ac5f6fef3237 100644 --- a/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java +++ b/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java @@ -25,6 +25,7 @@ import io.quarkus.keycloak.admin.client.common.AutoCloseableDestroyer; import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled; import io.quarkus.keycloak.adminclient.ResteasyKeycloakAdminClientRecorder; +import io.quarkus.runtime.TlsConfig; public class KeycloakAdminClientProcessor { @@ -48,8 +49,8 @@ ReflectiveClassBuildItem reflect() { @Record(ExecutionTime.STATIC_INIT) @Produce(ServiceStartBuildItem.class) @BuildStep - public void integrate(ResteasyKeycloakAdminClientRecorder recorder) { - recorder.setClientProvider(); + public void integrate(ResteasyKeycloakAdminClientRecorder recorder, TlsConfig tlsConfig) { + recorder.setClientProvider(tlsConfig.trustAll); } @Record(ExecutionTime.RUNTIME_INIT) diff --git a/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java b/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java index 9dda7e9c3c475..75fb6d2924896 100644 --- a/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java +++ b/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java @@ -58,13 +58,13 @@ public Keycloak get() { }; } - public void setClientProvider() { + public void setClientProvider(boolean tlsTrustAll) { Keycloak.setClientProvider(new ResteasyClientClassicProvider() { @Override public Client newRestEasyClient(Object customJacksonProvider, SSLContext sslContext, boolean disableTrustManager) { // point here is to use default Quarkus providers rather than org.keycloak.admin.client.JacksonProvider // as it doesn't work properly in native mode - return ClientBuilderWrapper.create(sslContext, disableTrustManager).build(); + return ClientBuilderWrapper.create(sslContext, tlsTrustAll || disableTrustManager).build(); } }); } From 7b09488763a9ab6e8fafac20eee27224f21c44ef Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 20 Nov 2023 10:48:45 +0100 Subject: [PATCH 034/212] Build cache - Ignore more files for Spotless (cherry picked from commit 2712330280c6870a991f6b724cf4433ae7baaae7) --- independent-projects/parent/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/independent-projects/parent/pom.xml b/independent-projects/parent/pom.xml index 0c88d626eeb8e..be3075bf1548b 100644 --- a/independent-projects/parent/pom.xml +++ b/independent-projects/parent/pom.xml @@ -562,6 +562,8 @@ .settings/* target/* .cache/* + .factorypath + *.log true From 67e3a31be0e86ef1670b55ef585463d8e4c270c6 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 20 Nov 2023 10:49:00 +0100 Subject: [PATCH 035/212] Build cache - Avoid runtimeClasspath being overridden (cherry picked from commit 7824826929ab4bcecbcd55991b27a70988e41c81) --- integration-tests/oidc-client-reactive/pom.xml | 3 +++ integration-tests/rest-client-reactive/pom.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/integration-tests/oidc-client-reactive/pom.xml b/integration-tests/oidc-client-reactive/pom.xml index 5c9c5f1f2e838..5d504c6944136 100644 --- a/integration-tests/oidc-client-reactive/pom.xml +++ b/integration-tests/oidc-client-reactive/pom.xml @@ -156,6 +156,9 @@ + + META-INF/ide-deps/** + application.properties diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml index 05fc6fb2cf4c5..e70e0e07b177d 100644 --- a/integration-tests/rest-client-reactive/pom.xml +++ b/integration-tests/rest-client-reactive/pom.xml @@ -156,6 +156,9 @@ + + META-INF/ide-deps/** + application.properties From fef4a1328c9a93b040b89958cd67454900b86abe Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 20 Nov 2023 11:04:46 +0100 Subject: [PATCH 036/212] Build cache - Avoid hardcoded path in generated test file (cherry picked from commit 5727c2dcbd1afc6c0e0a13bd7edcd3cb0085e204) --- .../project-using-test-template-from-extension/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/test-extension/tests/src/test/resources-filtered/projects/project-using-test-template-from-extension/pom.xml b/integration-tests/test-extension/tests/src/test/resources-filtered/projects/project-using-test-template-from-extension/pom.xml index 2750c013fadbf..eef420dac8589 100644 --- a/integration-tests/test-extension/tests/src/test/resources-filtered/projects/project-using-test-template-from-extension/pom.xml +++ b/integration-tests/test-extension/tests/src/test/resources-filtered/projects/project-using-test-template-from-extension/pom.xml @@ -46,7 +46,7 @@ io.quarkus integration-test-extension-that-defines-junit-test-extensions ${quarkus.version} - test + test @@ -109,7 +109,7 @@ - ${project.build.directory}/${project.build.finalName}-runner + \${project.build.directory}/\${project.build.finalName}-runner org.jboss.logmanager.LogManager \${maven.home} From 402aec19938ebee3aca88992bcf00a3408b7f9cf Mon Sep 17 00:00:00 2001 From: Bernhard Schuhmann Date: Mon, 20 Nov 2023 12:56:02 +0100 Subject: [PATCH 037/212] Use LinkedHashMap for parts map to ensure user input order (cherry picked from commit 6c69e6ddad8ab45bb4f5ec3419d7f1cbdcb41728) --- .../reactive/server/multipart/MultipartFormDataOutput.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/multipart/MultipartFormDataOutput.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/multipart/MultipartFormDataOutput.java index df175a86839c8..3b8212907a3ca 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/multipart/MultipartFormDataOutput.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/multipart/MultipartFormDataOutput.java @@ -1,7 +1,7 @@ package org.jboss.resteasy.reactive.server.multipart; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import jakarta.ws.rs.core.MediaType; @@ -10,7 +10,7 @@ * Used when a Resource method needs to return a multipart output */ public final class MultipartFormDataOutput { - private final Map parts = new HashMap<>(); + private final Map parts = new LinkedHashMap<>(); public Map getFormData() { return Collections.unmodifiableMap(parts); From ff7f9763afa2be9f3542428de96850e87c899167 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 16 Nov 2023 23:44:47 +0000 Subject: [PATCH 038/212] Support custom Authorization schemes for OIDC bearer tokens (cherry picked from commit 7a8d03522b31fcf6dbe926126cbf7a1c6ff37c89) --- .../io/quarkus/oidc/OidcTenantConfig.java | 14 ++++++ .../BearerAuthenticationMechanism.java | 17 +++++--- .../runtime/OidcAuthenticationMechanism.java | 5 +-- .../it/keycloak/CustomTenantResolver.java | 3 ++ .../io/quarkus/it/keycloak/UsersResource.java | 8 ++++ .../src/main/resources/application.properties | 6 +++ .../BearerTokenAuthorizationTest.java | 43 +++++++++++++++++++ 7 files changed, 87 insertions(+), 9 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index ccee7c9ca76c5..731715560132d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -1477,6 +1477,12 @@ public static Token fromAudience(String... audience) { @ConfigItem public Optional header = Optional.empty(); + /** + * HTTP Authorization header scheme. + */ + @ConfigItem(defaultValue = OidcConstants.BEARER_SCHEME) + public String authorizationScheme = OidcConstants.BEARER_SCHEME; + /** * Required signature algorithm. * OIDC providers support many signature algorithms but if necessary you can restrict @@ -1697,6 +1703,14 @@ public boolean isSubjectRequired() { public void setSubjectRequired(boolean subjectRequired) { this.subjectRequired = subjectRequired; } + + public String getAuthorizationScheme() { + return authorizationScheme; + } + + public void setAuthorizationScheme(String authorizationScheme) { + this.authorizationScheme = authorizationScheme; + } } public static enum ApplicationType { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java index 78f4f2fe978de..f6c22753ab98e 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java @@ -1,10 +1,11 @@ package io.quarkus.oidc.runtime; +import java.util.function.Function; + import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.OidcTenantConfig; -import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.security.identity.IdentityProviderManager; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.ChallengeData; @@ -15,9 +16,6 @@ public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMechanism { - protected static final ChallengeData UNAUTHORIZED_CHALLENGE = new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(), - HttpHeaderNames.WWW_AUTHENTICATE, OidcConstants.BEARER_SCHEME); - public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) { String token = extractBearerToken(context, oidcTenantConfig); @@ -29,7 +27,14 @@ public Uni authenticate(RoutingContext context, } public Uni getChallenge(RoutingContext context) { - return Uni.createFrom().item(UNAUTHORIZED_CHALLENGE); + Uni tenantContext = resolver.resolveContext(context); + return tenantContext.onItem().transformToUni(new Function>() { + @Override + public Uni apply(TenantConfigContext tenantContext) { + return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(), + HttpHeaderNames.WWW_AUTHENTICATE, tenantContext.oidcConfig.token.authorizationScheme)); + } + }); } private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcConfig) { @@ -49,7 +54,7 @@ private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcC return headerValue; } - if (!OidcConstants.BEARER_SCHEME.equalsIgnoreCase(scheme)) { + if (!oidcConfig.token.authorizationScheme.equalsIgnoreCase(scheme)) { return null; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java index edf944566678b..bd5c8ab18aeea 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java @@ -23,8 +23,6 @@ @ApplicationScoped public class OidcAuthenticationMechanism implements HttpAuthenticationMechanism { - private static HttpCredentialTransport OIDC_SERVICE_TRANSPORT = new HttpCredentialTransport( - HttpCredentialTransport.Type.AUTHORIZATION, OidcConstants.BEARER_SCHEME); private static HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport( HttpCredentialTransport.Type.AUTHORIZATION_CODE, OidcConstants.CODE_FLOW_CODE); @@ -105,7 +103,8 @@ public HttpCredentialTransport apply(OidcTenantConfig oidcTenantConfig) { return null; } return isWebApp(context, oidcTenantConfig) ? OIDC_WEB_APP_TRANSPORT - : OIDC_SERVICE_TRANSPORT; + : new HttpCredentialTransport( + HttpCredentialTransport.Type.AUTHORIZATION, oidcTenantConfig.token.authorizationScheme); } }); } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java index 1df7467d064ad..34ffd429732e1 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java @@ -44,6 +44,9 @@ public String resolve(RoutingContext context) { if (path.endsWith("bearer")) { return "bearer"; } + if (path.endsWith("bearer-id")) { + return "bearer-id"; + } if (path.endsWith("bearer-required-algorithm")) { return "bearer-required-algorithm"; } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResource.java index 3e55f570cfbe9..37f4565cbca0b 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResource.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/UsersResource.java @@ -29,6 +29,14 @@ public User principalName() { return new User(identity.getPrincipal().getName()); } + @GET + @Path("/me/bearer-id") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + public User principalNameId() { + return new User(identity.getPrincipal().getName()); + } + @GET @Path("/preferredUserName/bearer") @RolesAllowed("user") diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties index 1a0e9556492de..6ac2dbb4c4537 100644 --- a/integration-tests/oidc-wiremock/src/main/resources/application.properties +++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties @@ -121,6 +121,12 @@ quarkus.oidc.bearer.credentials.secret=secret quarkus.oidc.bearer.token.audience=https://service.example.com quarkus.oidc.bearer.allow-token-introspection-cache=false +quarkus.oidc.bearer-id.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.bearer-id.client-id=quarkus-app +quarkus.oidc.bearer-id.credentials.secret=secret +quarkus.oidc.bearer-id.allow-token-introspection-cache=false +quarkus.oidc.bearer-id.token.authorization-scheme=ID + quarkus.oidc.bearer-required-algorithm.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.bearer-required-algorithm.client-id=quarkus-app quarkus.oidc.bearer-required-algorithm.credentials.secret=secret diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index 4e31443081776..5cf6f2cc4f5f8 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -262,6 +262,17 @@ public void testExpiredBearerToken() { .header("WWW-Authenticate", equalTo("Bearer")); } + @Test + public void testBearerToken() { + String token = getAccessToken("alice", Set.of("user")); + + RestAssured.given().auth().oauth2(token).when() + .get("/api/users/me/bearer") + .then() + .statusCode(200) + .body(Matchers.containsString("alice")); + } + @Test public void testBearerTokenWrongIssuer() { String token = getAccessTokenWrongIssuer("alice", Set.of("user")); @@ -284,6 +295,38 @@ public void testBearerTokenWrongAudience() { .header("WWW-Authenticate", equalTo("Bearer")); } + @Test + public void testBearerTokenIdScheme() { + String token = getAccessToken("alice", Set.of("user")); + + RestAssured.given().header("Authorization", "ID " + token).when() + .get("/api/users/me/bearer-id") + .then() + .statusCode(200) + .body(Matchers.containsString("alice")); + } + + @Test + public void testBearerTokenIdSchemeButBearerSchemeIsUsed() { + String token = getAccessToken("alice", Set.of("user")); + + RestAssured.given().auth().oauth2(token).when() + .get("/api/users/me/bearer-id") + .then() + .statusCode(401); + } + + @Test + public void testBearerTokenIdSchemeWrongIssuer() { + String token = getAccessTokenWrongIssuer("alice", Set.of("user")); + + RestAssured.given().auth().oauth2(token).when() + .get("/api/users/me/bearer-id") + .then() + .statusCode(401) + .header("WWW-Authenticate", equalTo("ID")); + } + @Test public void testAcquiringIdentityOutsideOfHttpRequest() { String tenant = "bearer"; From d4eb2dc907b8aa57eede551228046f9df46167b7 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 20 Nov 2023 14:44:04 +0100 Subject: [PATCH 039/212] Update OpenJDK runtime image to 1.18 Update UBI minimal to 8.9 Update code to select OpenJDK Runtime image for Java 21 (cherry picked from commit 177edd51bfc523f0dc64fbff514f8920cd9f3be4) --- .../CompiledJavaVersionBuildItem.java | 13 ++++++++ .../main/asciidoc/building-native-image.adoc | 2 +- docs/src/main/asciidoc/container-image.adoc | 6 ++-- .../asciidoc/quarkus-runtime-base-image.adoc | 6 ++-- docs/src/main/asciidoc/virtual-threads.adoc | 4 +-- .../RedHatOpenJDKRuntimeBaseProviderTest.java | 14 +++++++-- .../UbiMinimalBaseProviderTest.java | 14 +++++++-- .../src/test/resources/openjdk-11-runtime | 2 +- .../src/test/resources/openjdk-17-runtime | 2 +- .../src/test/resources/openjdk-21-runtime | 18 +++++++++++ .../deployment/src/test/resources/ubi-java11 | 2 +- .../deployment/src/test/resources/ubi-java17 | 2 +- .../deployment/src/test/resources/ubi-java21 | 31 +++++++++++++++++++ .../deployment/ContainerImageJibConfig.java | 7 +++-- .../image/jib/deployment/JibProcessor.java | 9 ++++-- .../ContainerImageOpenshiftConfig.java | 23 ++++++++------ .../image/openshift/deployment/S2iConfig.java | 4 +-- .../deployment/ContainerImageS2iConfig.java | 12 ++++--- .../base/Dockerfile-layout.include.qute | 2 +- .../quarkus/tooling/dockerfiles/codestart.yml | 2 +- .../QuarkusCodestartGenerationTest.java | 8 ++--- .../awt/src/main/docker/Dockerfile.native | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- .../src/main/docker/Dockerfile.jvm | 2 +- 30 files changed, 149 insertions(+), 52 deletions(-) create mode 100644 extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime create mode 100644 extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java index 39c5385a9348e..88dadbcc63dd4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java @@ -30,6 +30,8 @@ public interface JavaVersion { Status isJava17OrHigher(); + Status isJava21OrHigher(); + Status isJava19OrHigher(); enum Status { @@ -58,6 +60,11 @@ public Status isJava17OrHigher() { return Status.UNKNOWN; } + @Override + public Status isJava21OrHigher() { + return Status.UNKNOWN; + } + @Override public Status isJava19OrHigher() { return Status.UNKNOWN; @@ -69,6 +76,7 @@ final class Known implements JavaVersion { private static final int JAVA_11_MAJOR = 55; private static final int JAVA_17_MAJOR = 61; private static final int JAVA_19_MAJOR = 63; + private static final int JAVA_21_MAJOR = 66; private final int determinedMajor; @@ -96,6 +104,11 @@ public Status isJava19OrHigher() { return higherOrEqualStatus(JAVA_19_MAJOR); } + @Override + public Status isJava21OrHigher() { + return higherOrEqualStatus(JAVA_21_MAJOR); + } + private Status higherOrEqualStatus(int javaMajor) { return determinedMajor >= javaMajor ? Status.TRUE : Status.FALSE; } diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index 96e4e1640cdb0..6e3712384afd5 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -532,7 +532,7 @@ The project generation has also provided a `Dockerfile.native` in the `src/main/ [source,dockerfile] ---- -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 WORKDIR /work/ RUN chown 1001 /work \ && chmod "g+rwX" /work \ diff --git a/docs/src/main/asciidoc/container-image.adoc b/docs/src/main/asciidoc/container-image.adoc index 953637e0a9159..eb5e7c3ebff19 100644 --- a/docs/src/main/asciidoc/container-image.adoc +++ b/docs/src/main/asciidoc/container-image.adoc @@ -47,7 +47,7 @@ For example, the presence of `src/main/jib/foo/bar` would result in `/foo/bar` There are cases where the built container image may need to have Java debugging conditionally enabled at runtime. -When the base image has not been changed (and therefore `ubi8/openjdk-11-runtime` or `ubi8/openjdk-17-runtime` is used), then the `quarkus.jib.jvm-arguments` configuration property can be used in order to +When the base image has not been changed (and therefore `ubi8/openjdk-11-runtime`, `ubi8/openjdk-17-runtime`, or `ubi8/openjdk-21-runtime` is used), then the `quarkus.jib.jvm-arguments` configuration property can be used in order to make the JVM listen on the debug port at startup. The exact configuration is: @@ -63,7 +63,7 @@ Other base images might provide launch scripts that enable debugging when an env The `quarkus.jib.jvm-entrypoint` configuration property can be used to completely override the container entry point and can thus be used to either hard code the JVM debug configuration or point to a script that handles the details. -For example, if the base images `ubi8/openjdk-11-runtime` or `ubi8/openjdk-17-runtime` are used to build the container, the entry point can be hard-coded on the application properties file. +For example, if the base images `ubi8/openjdk-11-runtime`, `ubi8/openjdk-17-runtime` or `ubi8/openjdk-21-runtime` are used to build the container, the entry point can be hard-coded on the application properties file. .Example application.properties [source,properties] @@ -88,7 +88,7 @@ java \ -jar quarkus-run.jar ---- -NOTE: `/home/jboss` is the WORKDIR for all quarkus binaries in the base images `ubi8/openjdk-11-runtime` and `ubi8/openjdk-17-runtime` (https://catalog.redhat.com/software/containers/ubi8/openjdk-17/618bdbf34ae3739687568813?container-tabs=dockerfile[Dockerfile for ubi8/openjdk-17-runtime, window="_blank"]) +NOTE: `/home/jboss` is the WORKDIR for all quarkus binaries in the base images `ubi8/openjdk-11-runtime`, `ubi8/openjdk-17-runtime` and `ubi8/openjdk-21-runtime` (https://catalog.redhat.com/software/containers/ubi8/openjdk-17/618bdbf34ae3739687568813?container-tabs=dockerfile[Dockerfile for ubi8/openjdk-17-runtime, window="_blank"]) ==== Multi-module projects and layering diff --git a/docs/src/main/asciidoc/quarkus-runtime-base-image.adoc b/docs/src/main/asciidoc/quarkus-runtime-base-image.adoc index 9d94a898fc410..ba9f0ff6c8b9a 100644 --- a/docs/src/main/asciidoc/quarkus-runtime-base-image.adoc +++ b/docs/src/main/asciidoc/quarkus-runtime-base-image.adoc @@ -39,7 +39,7 @@ In this case, you need to use a multi-stage `dockerfile` to copy the required li [source, dockerfile] ---- # First stage - install the dependencies in an intermediate container -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 as BUILD +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 as BUILD RUN microdnf install freetype # Second stage - copy the dependencies @@ -62,7 +62,7 @@ If you need to have access to the full AWT support, you need more than just `lib [source, dockerfile] ---- # First stage - install the dependencies in an intermediate container -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 as BUILD +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 as BUILD RUN microdnf install freetype fontconfig # Second stage - copy the dependencies @@ -112,7 +112,7 @@ To use this base image, use the following `Dockerfile`: [source, dockerfile] ---- -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 WORKDIR /work/ RUN chown 1001 /work \ && chmod "g+rwX" /work \ diff --git a/docs/src/main/asciidoc/virtual-threads.adoc b/docs/src/main/asciidoc/virtual-threads.adoc index eba2cde157550..2ee3545979f74 100644 --- a/docs/src/main/asciidoc/virtual-threads.adoc +++ b/docs/src/main/asciidoc/virtual-threads.adoc @@ -349,10 +349,10 @@ To containerize your Quarkus application that use `@RunOnVirtualThread`, add the quarkus.container-image.build=true quarkus.container-image.group= quarkus.container-image.name= -quarkus.jib.base-jvm-image=eclipse-temurin:21-ubi9-minimal <1> +quarkus.jib.base-jvm-image=registry.access.redhat.com/ubi8/openjdk-21-runtime <1> quarkus.jib.platforms=linux/amd64,linux/arm64 <2> ---- -<1> Make sure you use a base image supporting virtual threads. Here we use an image providing Java 21. +<1> Make sure you use a base image supporting virtual threads. Here we use an image providing Java 21. Quarkus picks an image providing Java 21+ automatically if you do not set one. <2> Select the target architecture. You can select more than one to build multi-archs images. Then, build your container as you would do usually. diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java index 88afd20df90e4..6114615670116 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java +++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/RedHatOpenJDKRuntimeBaseProviderTest.java @@ -16,7 +16,7 @@ void testImageWithJava11() { Path path = getPath("openjdk-11-runtime"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.17"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-11-runtime:1.18"); assertThat(v.getJavaVersion()).isEqualTo(11); }); } @@ -26,11 +26,21 @@ void testImageWithJava17() { Path path = getPath("openjdk-17-runtime"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.17"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18"); assertThat(v.getJavaVersion()).isEqualTo(17); }); } + @Test + void testImageWithJava21() { + Path path = getPath("openjdk-21-runtime"); + var result = sut.determine(path); + assertThat(result).hasValueSatisfying(v -> { + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18"); + assertThat(v.getJavaVersion()).isEqualTo(21); + }); + } + @Test void testUnhandled() { Path path = getPath("ubi-java11"); diff --git a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java index de17e9675c3ca..29c266279ac38 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java +++ b/extensions/container-image/container-image-docker/deployment/src/test/java/io/quarkus/container/image/docker/deployment/UbiMinimalBaseProviderTest.java @@ -16,7 +16,7 @@ void testImageWithJava11() { Path path = getPath("ubi-java11"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.8"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9"); assertThat(v.getJavaVersion()).isEqualTo(11); }); } @@ -26,11 +26,21 @@ void testImageWithJava17() { Path path = getPath("ubi-java17"); var result = sut.determine(path); assertThat(result).hasValueSatisfying(v -> { - assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal"); + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9"); assertThat(v.getJavaVersion()).isEqualTo(17); }); } + @Test + void testImageWithJava21() { + Path path = getPath("ubi-java21"); + var result = sut.determine(path); + assertThat(result).hasValueSatisfying(v -> { + assertThat(v.getBaseImage()).isEqualTo("registry.access.redhat.com/ubi8/ubi-minimal:8.9"); + assertThat(v.getJavaVersion()).isEqualTo(21); + }); + } + @Test void testUnhandled() { Path path = getPath("openjdk-11-runtime"); diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime index abba4268c7928..eb3b9e643de4c 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-11-runtime @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.17 +FROM registry.access.redhat.com/ubi8/openjdk-11-runtime:1.18 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime index 4542ffb9d471f..14f5d447dbd36 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-17-runtime @@ -1,5 +1,5 @@ # Use Java 17 base image -FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.17 +FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime new file mode 100644 index 0000000000000..8d11343e7b78e --- /dev/null +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/openjdk-21-runtime @@ -0,0 +1,18 @@ +# Use Java 17 base image +FROM registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' + +# Append additional options to the java process, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 + +ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ] diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11 b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11 index 9ad8990cf8fb5..64397357e0628 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11 +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java11 @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 index 5ae6e1e2f3ac4..77d59a96bc997 100644 --- a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java17 @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi8/ubi-minimal +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-17-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21 b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21 new file mode 100644 index 0000000000000..ee1e916b5d106 --- /dev/null +++ b/extensions/container-image/container-image-docker/deployment/src/test/resources/ubi-java21 @@ -0,0 +1,31 @@ +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 + +ARG JAVA_PACKAGE=java-21-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=1001 target/quarkus-app/*.jar /deployments/ +COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ +COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java index 53a676a68153a..b79d0303741fe 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/ContainerImageJibConfig.java @@ -16,9 +16,12 @@ public class ContainerImageJibConfig { /** * The base image to be used when a container image is being produced for the jar build. * - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.17} + * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.17} is used as the default. + * When the application is built against Java 17 or higher (but less than 21), + * {@code registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18} + * is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11-runtime:1.18} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index d6ac1b57c58c4..c3cb77ddc4a16 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -92,8 +92,10 @@ public class JibProcessor { private static final IsClassPredicate IS_CLASS_PREDICATE = new IsClassPredicate(); private static final String BINARY_NAME_IN_CONTAINER = "application"; - private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.17"; - private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.17"; + private static final String JAVA_21_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21-runtime:1.18"; + private static final String JAVA_17_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17-runtime:1.18"; + private static final String JAVA_11_BASE_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11-runtime:1.18"; + private static final String DEFAULT_BASE_IMAGE_USER = "185"; private static final String OPENTELEMETRY_CONTEXT_CONTEXT_STORAGE_PROVIDER_SYS_PROP = "io.opentelemetry.context.contextStorageProvider"; @@ -134,6 +136,9 @@ private String determineBaseJvmImage(ContainerImageJibConfig jibConfig, Compiled } var javaVersion = compiledJavaVersion.getJavaVersion(); + if (javaVersion.isJava21OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { + return JAVA_21_BASE_IMAGE; + } if (javaVersion.isJava17OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { return JAVA_17_BASE_IMAGE; } diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java index 9636ba877651d..f049d4a6692fe 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/ContainerImageOpenshiftConfig.java @@ -15,8 +15,10 @@ @ConfigRoot(name = "openshift", phase = ConfigPhase.BUILD_TIME) public class ContainerImageOpenshiftConfig { - public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.17"; - public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.17"; + public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11:1.18"; + public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17:1.18"; + public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21:1.18"; + public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0"; public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application"; @@ -29,11 +31,12 @@ public class ContainerImageOpenshiftConfig { public static final String FALLBACK_NATIVE_BINARY_DIRECTORY = "/home/quarkus/"; public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion version) { - switch (version.isJava17OrHigher()) { - case TRUE: - return DEFAULT_BASE_JVM_JDK17_IMAGE; - default: - return DEFAULT_BASE_JVM_JDK11_IMAGE; + if (version.isJava21OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { + return DEFAULT_BASE_JVM_JDK21_IMAGE; + } else if (version.isJava17OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { + return DEFAULT_BASE_JVM_JDK17_IMAGE; + } else { + return DEFAULT_BASE_JVM_JDK11_IMAGE; } } @@ -48,9 +51,11 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion * The value of this property is used to create an ImageStream for the builder image used in the Openshift build. * When it references images already available in the internal Openshift registry, the corresponding streams are used * instead. - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.17} + * When the application is built against Java 21 or higher, {@code registry.access.redhat.com/ubi8/openjdk-21:1.18} + * is used as the default. + * When the application is built against Java [17, 20], {@code registry.access.redhat.com/ubi8/openjdk-17:1.18} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.17} is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.18} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java index a636a645f7479..0c88e16cba29e 100644 --- a/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java +++ b/extensions/container-image/container-image-openshift/deployment/src/main/java/io/quarkus/container/image/openshift/deployment/S2iConfig.java @@ -41,9 +41,9 @@ public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion /** * The base image to be used when a container image is being produced for the jar build. * - * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.17} + * When the application is built against Java 17 or higher, {@code registry.access.redhat.com/ubi8/openjdk-17:1.18} * is used as the default. - * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.17} is used as the default. + * Otherwise {@code registry.access.redhat.com/ubi8/openjdk-11:1.18} is used as the default. */ @ConfigItem public Optional baseJvmImage; diff --git a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java index 361aba9082290..10e9f127f17b8 100644 --- a/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java +++ b/extensions/container-image/container-image-s2i/deployment/src/main/java/io/quarkus/container/image/s2i/deployment/ContainerImageS2iConfig.java @@ -15,15 +15,17 @@ public class ContainerImageS2iConfig { public static final String DEFAULT_BASE_JVM_JDK11_IMAGE = "registry.access.redhat.com/ubi8/openjdk-11"; public static final String DEFAULT_BASE_JVM_JDK17_IMAGE = "registry.access.redhat.com/ubi8/openjdk-17"; + public static final String DEFAULT_BASE_JVM_JDK21_IMAGE = "registry.access.redhat.com/ubi8/openjdk-21"; public static final String DEFAULT_BASE_NATIVE_IMAGE = "quay.io/quarkus/ubi-quarkus-native-binary-s2i:2.0"; public static final String DEFAULT_NATIVE_TARGET_FILENAME = "application"; public static String getDefaultJvmImage(CompiledJavaVersionBuildItem.JavaVersion version) { - switch (version.isJava17OrHigher()) { - case TRUE: - return DEFAULT_BASE_JVM_JDK17_IMAGE; - default: - return DEFAULT_BASE_JVM_JDK11_IMAGE; + if (version.isJava21OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { + return DEFAULT_BASE_JVM_JDK21_IMAGE; + } else if (version.isJava17OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) { + return DEFAULT_BASE_JVM_JDK17_IMAGE; + } else { + return DEFAULT_BASE_JVM_JDK11_IMAGE; } } diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute index c3dab99e60b8f..eade03aa1a939 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/base/Dockerfile-layout.include.qute @@ -77,7 +77,7 @@ # accessed directly. (example: "foo.example.com,bar.example.com") # ### -FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.17 +FROM registry.access.redhat.com/ubi8/openjdk-{java.version}:1.18 ENV LANGUAGE='en_US:en' diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/codestart.yml index 6cda40b09636a..22e7403ac8e26 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/tooling/dockerfiles/codestart.yml @@ -5,6 +5,6 @@ language: data: dockerfile: native: - from: registry.access.redhat.com/ubi8/ubi-minimal:8.8 + from: registry.access.redhat.com/ubi8/ubi-minimal:8.9 native-micro: from: quay.io/quarkus/quarkus-micro-image:2.0 diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java index c9585530d468f..e822bd176c4ea 100644 --- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartGenerationTest.java @@ -315,13 +315,13 @@ private void checkDockerfilesWithMaven(Path projectDir) { assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists() .satisfies(checkContains("./mvnw package")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.jvm")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.17"))//TODO: make a test for java17 + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.18"))//TODO: make a test for java17 .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) .satisfies(checkContains("ENTRYPOINT [ \"/opt/jboss/container/java/run/run-java.sh\" ]")); assertThat(projectDir.resolve("src/main/docker/Dockerfile.legacy-jar")).exists() .satisfies(checkContains("./mvnw package -Dquarkus.package.type=legacy-jar")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.legacy-jar")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.17")) + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.18")) .satisfies(checkContains("EXPOSE 8080")) .satisfies(checkContains("USER 185")) .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) @@ -341,13 +341,13 @@ private void checkDockerfilesWithGradle(Path projectDir) { assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists() .satisfies(checkContains("./gradlew build")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.jvm")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.17"))//TODO: make a test for java17 + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.18"))//TODO: make a test for java17 .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) .satisfies(checkContains("ENTRYPOINT [ \"/opt/jboss/container/java/run/run-java.sh\" ]")); assertThat(projectDir.resolve("src/main/docker/Dockerfile.legacy-jar")).exists() .satisfies(checkContains("./gradlew build -Dquarkus.package.type=legacy-jar")) .satisfies(checkContains("docker build -f src/main/docker/Dockerfile.legacy-jar")) - .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.17")) + .satisfies(checkContains("registry.access.redhat.com/ubi8/openjdk-11:1.18")) .satisfies(checkContains("EXPOSE 8080")) .satisfies(checkContains("USER 185")) .satisfies(checkContains("ENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"")) diff --git a/integration-tests/awt/src/main/docker/Dockerfile.native b/integration-tests/awt/src/main/docker/Dockerfile.native index f1fd2c79bd135..e2ff5cf61ed94 100644 --- a/integration-tests/awt/src/main/docker/Dockerfile.native +++ b/integration-tests/awt/src/main/docker/Dockerfile.native @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 # Dependencies for AWT RUN microdnf install freetype fontconfig \ && microdnf clean all diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm b/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-docker/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-multiple-tags-docker/src/main/docker/Dockerfile.jvm b/integration-tests/container-image/maven-invoker-way/src/it/container-build-multiple-tags-docker/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/container-image/maven-invoker-way/src/it/container-build-multiple-tags-docker/src/main/docker/Dockerfile.jvm +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-multiple-tags-docker/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-deployment/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-docker-build-and-deploy-statefulset/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-jib-build-and-deploy/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/kubernetes-with-existing-selectorless-manifest/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-docker-build-and-deploy/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 diff --git a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm index 53ed09abd23c2..566ba7b5a17bf 100644 --- a/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm +++ b/integration-tests/kubernetes/maven-invoker-way/src/it/openshift-s2i-build-and-deploy/src/main/docker/Dockerfile.jvm @@ -21,7 +21,7 @@ # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/rest-client-quickstart-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 From 8dfac09db83c3838662022084b32036386bfb752 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 20 Nov 2023 13:34:12 +0100 Subject: [PATCH 040/212] Fix OpenTelemetry trace exclusion of endpoints served from the management interface (cherry picked from commit 1c55119344d30875fa008e00cea2fd6146f6c2d1) --- .../deployment/tracing/TracerProcessor.java | 17 ++++++++++++++++- .../http/deployment/VertxHttpProcessor.java | 18 +++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index 63337d717d8f0..b07e5891fbd53 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.opentelemetry.deployment.tracing; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -118,7 +119,21 @@ void dropNames( // Drop framework paths List nonApplicationUris = new ArrayList<>(); frameworkEndpoints.ifPresent( - frameworkEndpointsBuildItem -> nonApplicationUris.addAll(frameworkEndpointsBuildItem.getEndpoints())); + frameworkEndpointsBuildItem -> { + for (String endpoint : frameworkEndpointsBuildItem.getEndpoints()) { + // Management routes are using full urls -> Extract the path. + if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) { + try { + nonApplicationUris.add(new URL(endpoint).getPath()); + } catch (Exception ignored) { // Not an URL + nonApplicationUris.add(endpoint); + } + } else { + nonApplicationUris.add(endpoint); + } + } + }); + dropNonApplicationUris.produce(new DropNonApplicationUrisBuildItem(nonApplicationUris)); // Drop Static Resources diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index fd8ec2b3ce36e..0d6835565655e 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -114,16 +114,24 @@ FrameworkEndpointsBuildItem frameworkEndpoints(NonApplicationRootPathBuildItem n for (RouteBuildItem route : routes) { if (FRAMEWORK_ROUTE.equals(route.getRouteType())) { if (route.getConfiguredPathInfo() != null) { - frameworkEndpoints.add(route.getConfiguredPathInfo().getEndpointPath(nonApplicationRootPath, - managementInterfaceBuildTimeConfig, launchModeBuildItem)); + String endpointPath = route.getConfiguredPathInfo().getEndpointPath(nonApplicationRootPath, + managementInterfaceBuildTimeConfig, launchModeBuildItem); + frameworkEndpoints.add(endpointPath); continue; } if (route.getRouteFunction() != null && route.getRouteFunction() instanceof BasicRoute) { BasicRoute basicRoute = (BasicRoute) route.getRouteFunction(); if (basicRoute.getPath() != null) { - // Calling TemplateHtmlBuilder does not see very correct here, but it is the underlying API for ConfiguredPathInfo - frameworkEndpoints - .add(adjustRoot(nonApplicationRootPath.getNonApplicationRootPath(), basicRoute.getPath())); + if (basicRoute.getPath().startsWith(nonApplicationRootPath.getNonApplicationRootPath())) { + // Do not repeat the non application root path. + frameworkEndpoints.add(basicRoute.getPath()); + } else { + // Calling TemplateHtmlBuilder does not see very correct here, but it is the underlying API for ConfiguredPathInfo + String adjustRoot = adjustRoot(nonApplicationRootPath.getNonApplicationRootPath(), + basicRoute.getPath()); + frameworkEndpoints.add(adjustRoot); + } + } } } From 04eddd2798ebe61ae340c37c076b61ac963bbd99 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 20 Nov 2023 12:49:41 +0200 Subject: [PATCH 041/212] Add basic Range header support Closes: #37205 (cherry picked from commit b84fcde5a7d22b52d0576dc55963b9fe54103a51) --- .../server/test/providers/FileTestCase.java | 15 ++ .../serialisers/ServerFileBodyHandler.java | 140 +++++++++++++++++- .../serialisers/ServerPathBodyHandler.java | 5 +- 3 files changed, 155 insertions(+), 5 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java index c9adc3def2c48..80f99ddf1105d 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java @@ -41,6 +41,21 @@ public void testFiles() throws Exception { .statusCode(200) .header(HttpHeaders.CONTENT_LENGTH, contentLength) .body(Matchers.equalTo(content)); + RestAssured.given().header("Range", "bytes=0-9").get("/providers/file/file") + .then() + .statusCode(206) + .header(HttpHeaders.CONTENT_LENGTH, "10") + .body(Matchers.equalTo(content.substring(0, 10))); + RestAssured.given().header("Range", "bytes=10-19").get("/providers/file/file") + .then() + .statusCode(206) + .header(HttpHeaders.CONTENT_LENGTH, "10") + .body(Matchers.equalTo(content.substring(10, 20))); + RestAssured.given().header("Range", "bytes=10-").get("/providers/file/file") + .then() + .statusCode(206) + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(content.length() - 10)) + .body(Matchers.equalTo(content.substring(10))); RestAssured.get("/providers/file/file-partial") .then() .statusCode(200) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java index aa974beede41f..bd33ed659fda6 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java @@ -3,13 +3,18 @@ import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; @@ -30,6 +35,139 @@ public boolean isWriteable(Class type, Type genericType, ResteasyReactiveReso @Override public void writeResponse(File o, Type genericType, ServerRequestContext context) throws WebApplicationException { - context.serverResponse().sendFile(o.getAbsolutePath(), 0, o.length()); + sendFile(o, context); + } + + static void sendFile(File file, ServerRequestContext context) { + ResteasyReactiveRequestContext ctx = ((ResteasyReactiveRequestContext) context); + Object rangeObj = ctx.getHeader("Range", true); + ByteRange byteRange = rangeObj == null ? null : ByteRange.parse(rangeObj.toString()); + if ((byteRange != null) && (byteRange.ranges.size() == 1)) { + ByteRange.Range range = byteRange.ranges.get(0); + long length = range.getEnd() == -1 ? Long.MAX_VALUE : range.getEnd() - range.getStart() + 1; + context.serverResponse() + .setStatusCode(Response.Status.PARTIAL_CONTENT.getStatusCode()) + .sendFile(file.getAbsolutePath(), range.getStart(), length); + + } else { + context.serverResponse().sendFile(file.getAbsolutePath(), 0, file.length()); + } + } + + /** + * Represents a byte range for a range request + * + * @author Stuart Douglas + * + * NOTE: copied from Quarkus HTTP + */ + public static class ByteRange { + + private static final Logger log = Logger.getLogger(ByteRange.class); + + private final List ranges; + + public ByteRange(List ranges) { + this.ranges = ranges; + } + + public int getRanges() { + return ranges.size(); + } + + /** + * Gets the start of the specified range segment, of -1 if this is a suffix range segment + * + * @param range The range segment to get + * @return The range start + */ + public long getStart(int range) { + return ranges.get(range).getStart(); + } + + /** + * Gets the end of the specified range segment, or the number of bytes if this is a suffix range segment + * + * @param range The range segment to get + * @return The range end + */ + public long getEnd(int range) { + return ranges.get(range).getEnd(); + } + + /** + * Attempts to parse a range request. If the range request is invalid it will just return null so that + * it may be ignored. + * + * @param rangeHeader The range spec + * @return A range spec, or null if the range header could not be parsed + */ + public static ByteRange parse(String rangeHeader) { + if (rangeHeader == null || rangeHeader.length() < 7) { + return null; + } + if (!rangeHeader.startsWith("bytes=")) { + return null; + } + List ranges = new ArrayList<>(); + String[] parts = rangeHeader.substring(6).split(","); + for (String part : parts) { + try { + int index = part.indexOf('-'); + if (index == 0) { + //suffix range spec + //represents the last N bytes + //internally we represent this using a -1 as the start position + long val = Long.parseLong(part.substring(1)); + if (val < 0) { + log.debugf("Invalid range spec %s", rangeHeader); + return null; + } + ranges.add(new Range(-1, val)); + } else { + if (index == -1) { + log.debugf("Invalid range spec %s", rangeHeader); + return null; + } + long start = Long.parseLong(part.substring(0, index)); + if (start < 0) { + log.debugf("Invalid range spec %s", rangeHeader); + return null; + } + long end; + if (index + 1 < part.length()) { + end = Long.parseLong(part.substring(index + 1)); + } else { + end = -1; + } + ranges.add(new Range(start, end)); + } + } catch (NumberFormatException e) { + log.debugf("Invalid range spec %s", rangeHeader); + return null; + } + } + if (ranges.isEmpty()) { + return null; + } + return new ByteRange(ranges); + } + + public static class Range { + private final long start, end; + + public Range(long start, long end) { + this.start = start; + this.end = end; + } + + public long getStart() { + return start; + } + + public long getEnd() { + return end; + } + } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerPathBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerPathBodyHandler.java index 0585d84426286..252ddd98a8dff 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerPathBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerPathBodyHandler.java @@ -12,7 +12,6 @@ import org.jboss.resteasy.reactive.common.providers.serialisers.PathBodyHandler; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; -import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; @@ -36,8 +35,6 @@ public boolean isWriteable(Class type, Type genericType, ResteasyReactiveReso @Override public void writeResponse(java.nio.file.Path o, Type genericType, ServerRequestContext context) throws WebApplicationException { - ServerHttpResponse serverResponse = context.serverResponse(); - // sendFile implies end(), even though javadoc doesn't say, if you add end() it will throw - serverResponse.sendFile(o.toString(), 0, Long.MAX_VALUE); + ServerFileBodyHandler.sendFile(o.toFile(), context); } } From df832bf8373bbc9efbdfaec7e7d5e545975fb664 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 17 Nov 2023 21:32:44 +0100 Subject: [PATCH 042/212] Removed DependencyFlags.REMOVED (cherry picked from commit 6ebbc22aead853b9125f4970f47acf1925cc9049) --- .../deployment/runnerjar/ExcludedArtifactsTest.java | 8 -------- .../bootstrap/model/ApplicationModelBuilder.java | 13 +++++-------- .../quarkus/maven/dependency/DependencyFlags.java | 7 ------- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExcludedArtifactsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExcludedArtifactsTest.java index 89935d3363858..b0db1dfb0cbc0 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExcludedArtifactsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExcludedArtifactsTest.java @@ -78,13 +78,5 @@ protected void assertAppModel(ApplicationModel model) throws Exception { expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "dep-g", "1"), DependencyFlags.RUNTIME_CP, DependencyFlags.DEPLOYMENT_CP)); assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.RUNTIME_CP)); - - expected = new HashSet<>(); - expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-dep", "1"), - DependencyFlags.REMOVED)); - expected.add(new ArtifactDependency(ArtifactCoords.jar("org.banned", "dep-e", "1"), DependencyFlags.REMOVED)); - expected.add(new ArtifactDependency(ArtifactCoords.jar("org.banned.too", "dep-d", "1"), DependencyFlags.REMOVED)); - expected.add(new ArtifactDependency(ArtifactCoords.jar("org.banned", "dep-f", "1"), DependencyFlags.REMOVED)); - assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.REMOVED)); } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java index b4db9c3811fc5..0dcc5c24da325 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java @@ -240,31 +240,28 @@ private boolean isExcluded(ArtifactCoords coords) { List buildDependencies() { for (ArtifactKey key : parentFirstArtifacts) { final ResolvedDependencyBuilder d = dependencies.get(key); - if (d != null && !d.isFlagSet(DependencyFlags.REMOVED)) { + if (d != null) { d.setFlags(DependencyFlags.CLASSLOADER_PARENT_FIRST); } } for (ArtifactKey key : runnerParentFirstArtifacts) { final ResolvedDependencyBuilder d = dependencies.get(key); - if (d != null && !d.isFlagSet(DependencyFlags.REMOVED)) { + if (d != null) { d.setFlags(DependencyFlags.CLASSLOADER_RUNNER_PARENT_FIRST); } } for (ArtifactKey key : lesserPriorityArtifacts) { final ResolvedDependencyBuilder d = dependencies.get(key); - if (d != null && !d.isFlagSet(DependencyFlags.REMOVED)) { + if (d != null) { d.setFlags(DependencyFlags.CLASSLOADER_LESSER_PRIORITY); } } final List result = new ArrayList<>(dependencies.size()); for (ResolvedDependencyBuilder db : this.dependencies.values()) { - if (isExcluded(db.getArtifactCoords())) { - db.setFlags(DependencyFlags.REMOVED); - db.clearFlag(DependencyFlags.DEPLOYMENT_CP); - db.clearFlag(DependencyFlags.RUNTIME_CP); + if (!isExcluded(db.getArtifactCoords())) { + result.add(db.build()); } - result.add(db.build()); } return result; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java index dc700789874bf..8d9c50148784a 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java @@ -23,13 +23,6 @@ public interface DependencyFlags { // once the processing of the whole tree has completed. int VISITED = 0b00100000000000; - /** - * Dependencies that were removed from the application model - * following {@code removed-artifacts} - * configuration properties collected from extension metadata. - */ - int REMOVED = 0b01000000000000; - /* @formatter:on */ } From 30a7164a070d6fa5e46a3dd507602eeae7f7f28d Mon Sep 17 00:00:00 2001 From: Alex Martel <13215031+manofthepeace@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:37:55 -0400 Subject: [PATCH 043/212] Add note that endpointdisabled does not work native (cherry picked from commit 047c5b766f7f7fd619b44f3ddce7c81f4a2a663d) --- docs/src/main/asciidoc/resteasy-reactive.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index a5e4e544f6649..5a4afac21f3d0 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -3006,6 +3006,10 @@ public class RuntimeResource { } } ---- +[IMPORTANT] +==== +This feature does not work when using native build. +==== == RESTEasy Reactive client From 00720c44f8fcbb8d1f8d0989bbf7419d11ab2032 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 21 Nov 2023 09:48:58 +0100 Subject: [PATCH 044/212] Allow to retrieve minimum and recommended Java versions from catalog metadata (cherry picked from commit 808fba793396abe0a808c88e60a326a184ec6514) --- .../platform/catalog/processor/CatalogProcessor.java | 8 ++++++++ .../platform/catalog/processor/MetadataValue.java | 2 +- .../src/main/resources/fake-catalog.json | 2 ++ .../io/quarkus/registry/catalog/ExtensionCatalog.java | 3 +++ .../quarkus/platform/catalog/CatalogProcessorTest.java | 9 +++++++++ .../devtools/src/test/resources/platform-metadata.json | 4 +++- 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/CatalogProcessor.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/CatalogProcessor.java index 8fbd392efdce0..ae0037e90f61b 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/CatalogProcessor.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/CatalogProcessor.java @@ -85,4 +85,12 @@ public List getProcessedCategoriesInOrder() { public static MetadataValue getMetadataValue(ExtensionCatalog catalog, String path) { return MetadataValue.get(catalog.getMetadata(), path); } + + public static String getMinimumJavaVersion(ExtensionCatalog catalog) { + return getMetadataValue(catalog, ExtensionCatalog.MD_MINIMUM_JAVA_VERSION).asString(); + } + + public static String getRecommendedJavaVersion(ExtensionCatalog catalog) { + return getMetadataValue(catalog, ExtensionCatalog.MD_RECOMMENDED_JAVA_VERSION).asString(); + } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/MetadataValue.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/MetadataValue.java index 11ffee9d9a9df..13a71dabb4321 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/MetadataValue.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/catalog/processor/MetadataValue.java @@ -5,7 +5,7 @@ import java.util.Locale; import java.util.Map; -final class MetadataValue { +public final class MetadataValue { private static final MetadataValue EMPTY_METADATA_VALUE = new MetadataValue(null); private final Object val; diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index c8e7e56118368..3b970eaec4e5a 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -386,6 +386,8 @@ "gradle-plugin-id": "io.quarkus", "gradle-plugin-version": "999-FAKE", "supported-maven-versions": "[3.6.2,)", + "minimum-java-version": "11", + "recommended-java-version": "17", "proposed-maven-version": "3.9.5", "maven-wrapper-version": "3.2.0", "gradle-wrapper-version": "8.4" diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java index 2bcdb7eef419a..91f7d027e61cb 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/ExtensionCatalog.java @@ -11,6 +11,9 @@ public interface ExtensionCatalog extends ExtensionOrigin { + String MD_MINIMUM_JAVA_VERSION = "project.properties.minimum-java-version"; + String MD_RECOMMENDED_JAVA_VERSION = "project.properties.recommended-java-version"; + /** * All the origins this catalog is derived from. * diff --git a/integration-tests/devtools/src/test/java/io/quarkus/platform/catalog/CatalogProcessorTest.java b/integration-tests/devtools/src/test/java/io/quarkus/platform/catalog/CatalogProcessorTest.java index 0b7f884660c55..83dbe859c7c00 100644 --- a/integration-tests/devtools/src/test/java/io/quarkus/platform/catalog/CatalogProcessorTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/platform/catalog/CatalogProcessorTest.java @@ -1,7 +1,9 @@ package io.quarkus.platform.catalog; import static io.quarkus.devtools.testing.FakeExtensionCatalog.newFakeExtensionCatalog; +import static io.quarkus.platform.catalog.processor.CatalogProcessor.getMinimumJavaVersion; import static io.quarkus.platform.catalog.processor.CatalogProcessor.getProcessedCategoriesInOrder; +import static io.quarkus.platform.catalog.processor.CatalogProcessor.getRecommendedJavaVersion; import static org.assertj.core.api.Assertions.assertThat; import java.util.Objects; @@ -27,6 +29,13 @@ void testCategoryOrder() { .startsWith("web", "core", "reactive", "serialization", "compatibility", "alt-languages", "uncategorized"); } + @Test + void testJavaVersions() { + final ExtensionCatalog catalog = newFakeExtensionCatalog(); + assertThat(getMinimumJavaVersion(catalog)).isEqualTo("11"); + assertThat(getRecommendedJavaVersion(catalog)).isEqualTo("17"); + } + @Test void testExtensionsOrder() { final ExtensionCatalog catalog = newFakeExtensionCatalog(); diff --git a/integration-tests/devtools/src/test/resources/platform-metadata.json b/integration-tests/devtools/src/test/resources/platform-metadata.json index 59160da30fb5b..b4b073f5b6adf 100644 --- a/integration-tests/devtools/src/test/resources/platform-metadata.json +++ b/integration-tests/devtools/src/test/resources/platform-metadata.json @@ -43,7 +43,9 @@ "supported-maven-versions": "${supported-maven-versions}", "proposed-maven-version": "${proposed-maven-version}", "maven-wrapper-version": "${maven-wrapper.version}", - "gradle-wrapper-version": "${gradle-wrapper.version}" + "gradle-wrapper-version": "${gradle-wrapper.version}", + "minimum-java-version": "${minimum-java-version}", + "recommended-java-version": "${recommended-java-version}" } }, "codestarts-artifacts": [ From 008344712857ab7ab805fe917843b9f5bfa794f8 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 21 Nov 2023 10:03:50 +0100 Subject: [PATCH 045/212] Add method to retrieve the list of compatible java versions (cherry picked from commit 542e2b92cba4e8af11e799736fa9da0ace015f92) --- .../java/io/quarkus/devtools/project/JavaVersion.java | 10 ++++++++++ .../io/quarkus/devtools/project/JavaVersionTest.java | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java index 6dd09ec9c887b..ffa49e92acafb 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java @@ -6,6 +6,7 @@ import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public final class JavaVersion { @@ -71,6 +72,15 @@ public static int determineBestJavaLtsVersion() { return determineBestJavaLtsVersion(Runtime.version().feature()); } + public static SortedSet getCompatibleLTSVersions(JavaVersion minimumJavaVersion) { + if (minimumJavaVersion.isEmpty()) { + return JAVA_VERSIONS_LTS; + } + return JAVA_VERSIONS_LTS.stream() + .filter(v -> v >= minimumJavaVersion.getAsInt()) + .collect(Collectors.toCollection(TreeSet::new)); + } + public static int determineBestJavaLtsVersion(int runtimeVersion) { int bestLtsVersion = DEFAULT_JAVA_VERSION; for (int ltsVersion : JAVA_VERSIONS_LTS) { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java index 285a582e81b2c..3e0d82dd75241 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/JavaVersionTest.java @@ -1,10 +1,13 @@ package io.quarkus.devtools.project; import static io.quarkus.devtools.project.JavaVersion.DETECT_JAVA_RUNTIME_VERSION; +import static io.quarkus.devtools.project.JavaVersion.JAVA_VERSIONS_LTS; import static io.quarkus.devtools.project.JavaVersion.computeJavaVersion; import static io.quarkus.devtools.project.JavaVersion.determineBestJavaLtsVersion; +import static io.quarkus.devtools.project.JavaVersion.getCompatibleLTSVersions; import static io.quarkus.devtools.project.SourceType.JAVA; import static io.quarkus.devtools.project.SourceType.KOTLIN; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; @@ -31,6 +34,14 @@ public void givenJavaVersion18ShouldReturn17() { assertEquals("17", computeJavaVersion(JAVA, "18")); } + @Test + void shouldProperlyUseMinJavaVersion() { + assertThat(getCompatibleLTSVersions(new JavaVersion("11"))).isEqualTo(JAVA_VERSIONS_LTS); + assertThat(getCompatibleLTSVersions(new JavaVersion("17"))).containsExactly(17, 21); + assertThat(getCompatibleLTSVersions(new JavaVersion("100"))).isEmpty(); + assertThat(getCompatibleLTSVersions(JavaVersion.NA)).isEqualTo(JAVA_VERSIONS_LTS); + } + @Test public void givenAutoDetectShouldReturnAppropriateVersion() { final String bestJavaLtsVersion = String.valueOf(determineBestJavaLtsVersion(Runtime.version().feature())); From fca5cb8c1ffa6d267bcdb479ecef5e5a069669ca Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 20 Nov 2023 16:25:02 +0200 Subject: [PATCH 046/212] Allow REST Client to return the entire SSE event This can be useful when the id or the name of the event contain useful metadata Closes: #37107 (cherry picked from commit 6b66359920880494aff9be13945735ca3ab72b9f) --- .../reactive/jackson/test/MultiSseTest.java | 94 +++++++++++++++++++ .../resteasy/reactive/client/SseEvent.java | 15 +++ .../reactive/client/impl/MultiInvoker.java | 44 ++++++++- 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java index aa715e04fb948..780bb6b931694 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java @@ -8,15 +8,21 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.sse.OutboundSseEvent; +import jakarta.ws.rs.sse.Sse; +import jakarta.ws.rs.sse.SseEventSink; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.jboss.resteasy.reactive.RestStreamElementType; +import org.jboss.resteasy.reactive.client.SseEvent; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -112,6 +118,63 @@ void shouldRestStreamElementTypeOverwriteProducesAtClassLevel() { .containsExactly(new Dto("foo", "bar"), new Dto("chocolate", "bar"))); } + @Test + void shouldBeAbleReadEntireEvent() { + var resultList = new CopyOnWriteArrayList<>(); + createClient() + .event() + .subscribe().with(new Consumer<>() { + @Override + public void accept(SseEvent event) { + resultList.add(new EventContainer(event.id(), event.name(), event.data())); + } + }); + await().atMost(5, TimeUnit.SECONDS) + .untilAsserted( + () -> assertThat(resultList).containsExactly( + new EventContainer("id0", "name0", new Dto("name0", "0")), + new EventContainer("id1", "name1", new Dto("name1", "1")))); + } + + static class EventContainer { + final String id; + final String name; + final Dto dto; + + EventContainer(String id, String name, Dto dto) { + this.id = id; + this.name = name; + this.dto = dto; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventContainer that = (EventContainer) o; + return Objects.equals(id, that.id) && Objects.equals(name, that.name) + && Objects.equals(dto, that.dto); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, dto); + } + + @Override + public String toString() { + return "EventContainer{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", dto=" + dto + + '}'; + } + } + private SseClient createClient() { return QuarkusRestClientBuilder.newBuilder() .baseUri(uri) @@ -144,6 +207,11 @@ public interface SseClient { @Produces(MediaType.SERVER_SENT_EVENTS) @Path("/with-entity-json") Multi> postAndReadAsMap(String entity); + + @GET + @Path("/event") + @Produces(MediaType.SERVER_SENT_EVENTS) + Multi> event(); } @Path("/sse") @@ -175,6 +243,24 @@ public Multi post(String entity) { public Multi postAndReadAsMap(String entity) { return Multi.createBy().repeating().supplier(() -> new Dto("foo", entity)).atMost(3); } + + @GET + @Path("/event") + @Produces(MediaType.SERVER_SENT_EVENTS) + public void event(@Context SseEventSink sink, @Context Sse sse) { + // send a stream of few events + try (sink) { + for (int i = 0; i < 2; i++) { + final OutboundSseEvent.Builder builder = sse.newEventBuilder(); + builder.id("id" + i) + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .data(Dto.class, new Dto("name" + i, String.valueOf(i))) + .name("name" + i); + + sink.send(builder.build()); + } + } + } } @Path("/sse-rest-stream-element-type") @@ -226,5 +312,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, value); } + + @Override + public String toString() { + return "Dto{" + + "name='" + name + '\'' + + ", value='" + value + '\'' + + '}'; + } } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java new file mode 100644 index 0000000000000..a6978b93d2dc7 --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java @@ -0,0 +1,15 @@ +package org.jboss.resteasy.reactive.client; + +/** + * Represents the entire SSE response from the server + */ +public interface SseEvent { + + String id(); + + String name(); + + String comment(); + + T data(); +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java index fe6a93492c42f..e483baa0ce357 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -10,6 +11,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.jboss.resteasy.reactive.client.SseEvent; import org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl; import org.jboss.resteasy.reactive.common.util.RestMediaType; @@ -151,10 +153,17 @@ private boolean isNewlineDelimited(ResponseImpl response) { RestMediaType.APPLICATION_NDJSON_TYPE.isCompatible(response.getMediaType()); } + @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerForSse(MultiRequest multiRequest, GenericType responseType, Response response, HttpClientResponse vertxResponse, String defaultContentType) { + + boolean returnSseEvent = SseEvent.class.equals(responseType.getRawType()); + GenericType responseTypeFirstParam = responseType.getType() instanceof ParameterizedType + ? new GenericType(((ParameterizedType) responseType.getType()).getActualTypeArguments()[0]) + : null; + // honestly, isn't reconnect contradictory with completion? // FIXME: Reconnect settings? // For now we don't want multi to reconnect @@ -165,10 +174,39 @@ private void registerForSse(MultiRequest multiRequest, sseSource.register(event -> { // DO NOT pass the response mime type because it's SSE: let the event pick between the X-SSE-Content-Type header or // the content-type SSE field - R item = event.readData(responseType); - if (item != null) { // we don't emit null because it breaks Multi (by design) - multiRequest.emit(item); + if (returnSseEvent) { + multiRequest.emit((R) new SseEvent() { + @Override + public String id() { + return event.getId(); + } + + @Override + public String name() { + return event.getName(); + } + + @Override + public String comment() { + return event.getComment(); + } + + @Override + public Object data() { + if (responseTypeFirstParam != null) { + return event.readData(responseTypeFirstParam); + } else { + return event.readData(); // TODO: is this correct? + } + } + }); + } else { + R item = event.readData(responseType); + if (item != null) { // we don't emit null because it breaks Multi (by design) + multiRequest.emit(item); + } } + }, multiRequest::fail, multiRequest::complete); // watch for user cancelling sseSource.registerAfterRequest(vertxResponse); From 71228976ce03a2eb5ae499a3ffee860cfde02060 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 21 Nov 2023 10:56:35 +0200 Subject: [PATCH 047/212] Allow SSE events to be filtered out from REST Client (cherry picked from commit c9d1eeae65a34632ee993b19ab48755e16cdf596) --- .../reactive/jackson/test/MultiSseTest.java | 85 +++++++++++++++++++ .../client/reactive/deployment/DotNames.java | 3 + .../RestClientReactiveProcessor.java | 37 ++++++++ .../resteasy/reactive/client/SseEvent.java | 27 ++++++ .../reactive/client/SseEventFilter.java | 22 +++++ .../reactive/client/impl/MultiInvoker.java | 69 +++++++++++++-- 6 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEventFilter.java diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java index 780bb6b931694..629b881a93bec 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java @@ -9,6 +9,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Predicate; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; @@ -23,6 +24,7 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.client.SseEvent; +import org.jboss.resteasy.reactive.client.SseEventFilter; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -136,6 +138,25 @@ public void accept(SseEvent event) { new EventContainer("id1", "name1", new Dto("name1", "1")))); } + @Test + void shouldBeAbleReadEntireEventWhileAlsoBeingAbleToFilterEvents() { + var resultList = new CopyOnWriteArrayList<>(); + createClient() + .eventWithFilter() + .subscribe().with(new Consumer<>() { + @Override + public void accept(SseEvent event) { + resultList.add(new EventContainer(event.id(), event.name(), event.data())); + } + }); + await().atMost(5, TimeUnit.SECONDS) + .untilAsserted( + () -> assertThat(resultList).containsExactly( + new EventContainer("id", "n0", new Dto("name0", "0")), + new EventContainer("id", "n1", new Dto("name1", "1")), + new EventContainer("id", "n2", new Dto("name2", "2")))); + } + static class EventContainer { final String id; final String name; @@ -212,6 +233,26 @@ public interface SseClient { @Path("/event") @Produces(MediaType.SERVER_SENT_EVENTS) Multi> event(); + + @GET + @Path("/event-with-filter") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseEventFilter(CustomFilter.class) + Multi> eventWithFilter(); + } + + public static class CustomFilter implements Predicate> { + + @Override + public boolean test(SseEvent event) { + if ("heartbeat".equals(event.id())) { + return false; + } + if ("END".equals(event.data())) { + return false; + } + return true; + } } @Path("/sse") @@ -261,6 +302,50 @@ public void event(@Context SseEventSink sink, @Context Sse sse) { } } } + + @GET + @Path("/event-with-filter") + @Produces(MediaType.SERVER_SENT_EVENTS) + public void eventWithFilter(@Context SseEventSink sink, @Context Sse sse) { + try (sink) { + sink.send(sse.newEventBuilder() + .id("id") + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .data(Dto.class, new Dto("name0", "0")) + .name("n0") + .build()); + + sink.send(sse.newEventBuilder() + .id("heartbeat") + .comment("heartbeat") + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .build()); + + sink.send(sse.newEventBuilder() + .id("id") + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .data(Dto.class, new Dto("name1", "1")) + .name("n1") + .build()); + + sink.send(sse.newEventBuilder() + .id("heartbeat") + .comment("heartbeat") + .build()); + + sink.send(sse.newEventBuilder() + .id("id") + .mediaType(MediaType.APPLICATION_JSON_TYPE) + .data(Dto.class, new Dto("name2", "2")) + .name("n2") + .build()); + + sink.send(sse.newEventBuilder() + .id("end") + .data("END") + .build()); + } + } } @Path("/sse-rest-stream-element-type") diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java index add3e44795d65..f635e470595a4 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/DotNames.java @@ -12,6 +12,7 @@ import org.eclipse.microprofile.rest.client.annotation.RegisterProviders; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import org.jboss.jandex.DotName; +import org.jboss.resteasy.reactive.client.SseEventFilter; import io.quarkus.rest.client.reactive.ClientExceptionMapper; import io.quarkus.rest.client.reactive.ClientFormParam; @@ -41,6 +42,8 @@ public class DotNames { static final DotName METHOD = DotName.createSimple(Method.class.getName()); + public static final DotName SSE_EVENT_FILTER = DotName.createSimple(SseEventFilter.class); + private DotNames() { } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 49ee3402e3daf..22a4b76f9b69e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -64,6 +64,7 @@ import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; @@ -371,6 +372,42 @@ void registerCompressionInterceptors(BuildProducer ref } } + @BuildStep + void handleSseEventFilter(BuildProducer reflectiveClasses, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) { + var index = beanArchiveIndexBuildItem.getIndex(); + Collection instances = index.getAnnotations(DotNames.SSE_EVENT_FILTER); + if (instances.isEmpty()) { + return; + } + + List filterClassNames = new ArrayList<>(instances.size()); + for (AnnotationInstance instance : instances) { + if (instance.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + if (instance.value() == null) { + continue; // can't happen + } + Type filterType = instance.value().asClass(); + DotName filterClassName = filterType.name(); + ClassInfo filterClassInfo = index.getClassByName(filterClassName.toString()); + if (filterClassInfo == null) { + log.warn("Unable to find class '" + filterType.name() + "' in index"); + } else if (!filterClassInfo.hasNoArgsConstructor()) { + throw new RestClientDefinitionException( + "Classes used in @SseEventFilter must have a no-args constructor. Offending class is '" + + filterClassName + "'"); + } else { + filterClassNames.add(filterClassName.toString()); + } + } + reflectiveClasses.produce(ReflectiveClassBuildItem + .builder(filterClassNames.toArray(new String[0])) + .constructors(true) + .build()); + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) void addRestClientBeans(Capabilities capabilities, diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java index a6978b93d2dc7..bcbee51c809dc 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEvent.java @@ -5,11 +5,38 @@ */ public interface SseEvent { + /** + * Get event identifier. + *

+ * Contains value of SSE {@code "id"} field. This field is optional. Method may return {@code null}, if the event + * identifier is not specified. + * + * @return event id. + */ String id(); + /** + * Get event name. + *

+ * Contains value of SSE {@code "event"} field. This field is optional. Method may return {@code null}, if the event + * name is not specified. + * + * @return event name, or {@code null} if not set. + */ String name(); + /** + * Get a comment string that accompanies the event. + *

+ * Contains value of the comment associated with SSE event. This field is optional. Method may return {@code null}, if + * the event comment is not specified. + * + * @return comment associated with the event. + */ String comment(); + /** + * Get event data. + */ T data(); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEventFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEventFilter.java new file mode 100644 index 0000000000000..d9419dca5dfdb --- /dev/null +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/SseEventFilter.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.client; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Predicate; + +/** + * Used when not all SSE events streamed from the server should be included in the event stream returned by the client. + *

+ * IMPORTANT: implementations MUST contain a no-args constructor + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SseEventFilter { + + /** + * Predicate which decides whether an event should be included in the event stream returned by the client. + */ + Class>> value(); +} diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java index e483baa0ce357..4459e66000227 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java @@ -2,16 +2,19 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import org.jboss.resteasy.reactive.client.SseEvent; +import org.jboss.resteasy.reactive.client.SseEventFilter; import org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl; import org.jboss.resteasy.reactive.common.util.RestMediaType; @@ -45,8 +48,8 @@ public Multi get(GenericType responseType) { /** * We need this class to work around a bug in Mutiny where we can register our cancel listener - * after the subscription is cancelled and we never get notified - * See https://github.com/smallrye/smallrye-mutiny/issues/417 + * after the subscription is cancelled, and we never get notified + * See ... */ static class MultiRequest { @@ -127,9 +130,11 @@ public Multi method(String name, Entity entity, GenericType respons if (!emitter.isCancelled()) { if (response.getStatus() == 200 && MediaType.SERVER_SENT_EVENTS_TYPE.isCompatible(response.getMediaType())) { - registerForSse(multiRequest, responseType, response, vertxResponse, + registerForSse( + multiRequest, responseType, vertxResponse, (String) restClientRequestContext.getProperties() - .get(RestClientRequestContext.DEFAULT_CONTENT_TYPE_PROP)); + .get(RestClientRequestContext.DEFAULT_CONTENT_TYPE_PROP), + restClientRequestContext.getInvokedMethod()); } else if (response.getStatus() == 200 && RestMediaType.APPLICATION_STREAM_JSON_TYPE.isCompatible(response.getMediaType())) { registerForJsonStream(multiRequest, restClientRequestContext, responseType, response, @@ -156,14 +161,16 @@ private boolean isNewlineDelimited(ResponseImpl response) { @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerForSse(MultiRequest multiRequest, GenericType responseType, - Response response, - HttpClientResponse vertxResponse, String defaultContentType) { + HttpClientResponse vertxResponse, String defaultContentType, + Method invokedMethod) { boolean returnSseEvent = SseEvent.class.equals(responseType.getRawType()); GenericType responseTypeFirstParam = responseType.getType() instanceof ParameterizedType ? new GenericType(((ParameterizedType) responseType.getType()).getActualTypeArguments()[0]) : null; + Predicate> eventPredicate = createEventPredicate(invokedMethod); + // honestly, isn't reconnect contradictory with completion? // FIXME: Reconnect settings? // For now we don't want multi to reconnect @@ -172,8 +179,39 @@ private void registerForSse(MultiRequest multiRequest, multiRequest.onCancel(sseSource::close); sseSource.register(event -> { + + // TODO: we might want to cut down on the allocations here... + + if (eventPredicate != null) { + boolean keep = eventPredicate.test(new SseEvent<>() { + @Override + public String id() { + return event.getId(); + } + + @Override + public String name() { + return event.getName(); + } + + @Override + public String comment() { + return event.getComment(); + } + + @Override + public String data() { + return event.readData(); + } + }); + if (!keep) { + return; + } + } + // DO NOT pass the response mime type because it's SSE: let the event pick between the X-SSE-Content-Type header or // the content-type SSE field + if (returnSseEvent) { multiRequest.emit((R) new SseEvent() { @Override @@ -212,6 +250,23 @@ public Object data() { sseSource.registerAfterRequest(vertxResponse); } + private Predicate> createEventPredicate(Method invokedMethod) { + if (invokedMethod == null) { + return null; // should never happen + } + + SseEventFilter filterAnnotation = invokedMethod.getAnnotation(SseEventFilter.class); + if (filterAnnotation == null) { + return null; + } + + try { + return filterAnnotation.value().getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + private void registerForChunks(MultiRequest multiRequest, RestClientRequestContext restClientRequestContext, GenericType responseType, From 5b4342d8203979a0da9301ce465811c95205148b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 21 Nov 2023 12:18:26 +0200 Subject: [PATCH 048/212] Document SSE usage in REST Client (cherry picked from commit 6f41d71bf861bb64c3404c85c1ce7856b4c6f62d) --- .../main/asciidoc/rest-client-reactive.adoc | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index 4d1af8480e4d3..b3c063bccdda2 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -883,6 +883,107 @@ If you use a `CompletionStage`, you would need to call the service's method to r This difference comes from the laziness aspect of Mutiny and its subscription protocol. More details about this can be found in https://smallrye.io/smallrye-mutiny/latest/reference/uni-and-multi/[the Mutiny documentation]. +=== Server-Sent Event (SSE) support + +Consuming SSE events is possible simply by declaring the result type as a `io.smallrye.mutiny.Multi`. + +The simplest example is: + +[source, java] +---- +package org.acme.rest.client; + +import io.smallrye.mutiny.Multi; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/sse") +@RegisterRestClient(configKey = "some-api") +public interface SseClient { + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + Multi get(); +} +---- + +[NOTE] +==== +All the IO involved in streaming the SSE results is done in a non-blocking manner. +==== + +Results are not limited to strings - for example when the server returns JSON payload for each event, Quarkus automatically deserializes it into the generic type used in the `Multi`. + +[TIP] +==== +Users can also access the entire SSE event by using the `org.jboss.resteasy.reactive.client.SseEvent` type. + +A simple example where the event payloads are `Long` values is the following: + +[source, java] +---- +package org.acme.rest.client; + +import io.smallrye.mutiny.Uni; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.client.SseEvent; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; + +@Path("/sse") +@RegisterRestClient(configKey = "some-api") +public interface SseClient { + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + Multi> get(); +} +---- +==== + +==== Filtering out events + +On occasion, the stream of SSE events may contain some events that should not be returned by the client - an example of this is having the server send heartbeat events in order to keep the underlying TCP connection open. +The REST Client supports filtering out such events by providing the `@org.jboss.resteasy.reactive.client.SseEventFilter`. + +Here is an example of filtering out heartbeat events: + +[source,java] +---- +package org.acme.rest.client; + +import io.smallrye.mutiny.Uni; +import java.util.function.Predicate; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.client.SseEvent; +import org.jboss.resteasy.reactive.client.SseEventFilter; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; + +@Path("/sse") +@RegisterRestClient(configKey = "some-api") +public interface SseClient { + + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseEventFilter(HeartbeatFilter.class) + Multi> get(); + + + class HeartbeatFilter implements Predicate> { + + @Override + public boolean test(SseEvent event) { + return !"heartbeat".equals(event.id()); + } + } +} +---- + == Custom headers support There are a few ways in which you can specify custom headers for your REST calls: From 279e38559e546058a13f79e6b28ba06d666b989d Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Tue, 21 Nov 2023 11:05:46 +0100 Subject: [PATCH 049/212] Updates Infinispan to 14.0.21.Final (cherry picked from commit d2bb831548d019cf342886170ff2d437806612b0) --- 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 8faa6ff92cfd5..055ec2d25c619 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -138,7 +138,7 @@ 2.2 5.10.0 1.5.0 - 14.0.20.Final + 14.0.21.Final 4.6.5.Final 3.1.5 4.1.100.Final From da6ec38723dcd17a3c7b93ff8d72f71ddc64e80c Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 21 Nov 2023 12:57:34 +0200 Subject: [PATCH 050/212] Support Docker Desktop for building native executables Treat Docker Desktop as "rootless" since the way it binds mounts does not transparently map the host user ID and GID see https://docs.docker.com/desktop/faqs/linuxfaqs/#how-do-i-enable-file-sharing Closes https://github.com/quarkusio/quarkus/issues/37193 (cherry picked from commit 81818c79e4229d1de8579dd5ca0037715417e7d8) --- .../java/io/quarkus/runtime/util/ContainerRuntimeUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index ed538474b8b57..607ead4f24980 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -191,7 +191,10 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { final Predicate stringPredicate; // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " if (containerRuntime == ContainerRuntime.DOCKER) { - stringPredicate = line -> line.trim().equals("rootless"); + // We also treat Docker Desktop as "rootless" since the way it binds mounts does not + // transparently map the host user ID and GID + // see https://docs.docker.com/desktop/faqs/linuxfaqs/#how-do-i-enable-file-sharing + stringPredicate = line -> line.trim().equals("rootless") || line.contains("desktop-linux"); } else { stringPredicate = line -> line.trim().equals("rootless: true"); } From 6a237394e9b02140b34c1d2db1e003b617b1906d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 21 Nov 2023 10:38:52 +0100 Subject: [PATCH 051/212] Build cache - Only store if the access key is around (cherry picked from commit e45dec61524df2cd1f6c640a933e2bd35123ffaf) --- .mvn/gradle-enterprise.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/gradle-enterprise.xml b/.mvn/gradle-enterprise.xml index 0f9d7558936b5..27ac5553de6a2 100644 --- a/.mvn/gradle-enterprise.xml +++ b/.mvn/gradle-enterprise.xml @@ -27,7 +27,7 @@ true - #{env['CI'] != null} + #{env['CI'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != ''} From 50810260533f1e6aad0db139b1433f7506f36da6 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 20 Nov 2023 15:22:35 +0100 Subject: [PATCH 052/212] Qute: dev mode - no-restart-templates - fix javadoc in QuteDevModeConfig - improve NoRestartTemplatesDevModeTest (cherry picked from commit 2ff97152d6c07d8b3b4a7effaa98e37f6c2e79f0) --- .../deployment/devmode/NoRestartRoute.java | 13 +++++++-- .../NoRestartTemplatesDevModeTest.java | 28 ++++++++++++++----- .../qute/runtime/QuteDevModeConfig.java | 4 +-- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartRoute.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartRoute.java index 454868cd93d71..43f088655cfda 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartRoute.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartRoute.java @@ -6,6 +6,7 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; +import io.quarkus.qute.Location; import io.quarkus.qute.Template; import io.quarkus.vertx.web.Route; import io.vertx.ext.web.RoutingContext; @@ -15,12 +16,20 @@ public class NoRestartRoute { private String id; - @Inject + @Location("foo/norestart") Template norestart; + @Inject + Template bar; + @Route(path = "norestart") public void test(RoutingContext ctx) { - ctx.end(norestart.data("foo", id).render()); + ctx.end(norestart.data("id", id).render()); + } + + @Route(path = "bar") + public void testBar(RoutingContext ctx) { + ctx.end(bar.data("id", id).render()); } @PostConstruct diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartTemplatesDevModeTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartTemplatesDevModeTest.java index c7d9dca8e6717..5636fa2b77782 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartTemplatesDevModeTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/NoRestartTemplatesDevModeTest.java @@ -18,10 +18,13 @@ public class NoRestartTemplatesDevModeTest { .withApplicationRoot(root -> root .addClass(NoRestartRoute.class) .addAsResource(new StringAsset( - "Hello {foo}!"), - "templates/norestart.html") + "Hello {id}!"), + "templates/foo/norestart.html") .addAsResource(new StringAsset( - "quarkus.qute.dev-mode.no-restart-templates=templates/norestart.html"), + "Hi {id}!"), + "templates/bar.html") + .addAsResource(new StringAsset( + "quarkus.qute.dev-mode.no-restart-templates=templates/.+"), "application.properties")); @Test @@ -29,14 +32,25 @@ public void testNoRestartTemplates() { Response resp = given().get("norestart"); resp.then() .statusCode(200); - String val = resp.getBody().asString(); - assertTrue(val.startsWith("Hello ")); + String val1 = resp.getBody().asString(); + assertTrue(val1.startsWith("Hello ")); + + resp = given().get("bar"); + resp.then() + .statusCode(200); + String val2 = resp.getBody().asString(); + assertTrue(val2.startsWith("Hi ")); - config.modifyResourceFile("templates/norestart.html", t -> t.concat("!!")); + config.modifyResourceFile("templates/foo/norestart.html", t -> t.concat("!!")); + config.modifyResourceFile("templates/bar.html", t -> t.concat("!!")); resp = given().get("norestart"); resp.then().statusCode(200); - assertEquals(val + "!!", resp.getBody().asString()); + assertEquals(val1 + "!!", resp.getBody().asString()); + + resp = given().get("bar"); + resp.then().statusCode(200); + assertEquals(val2 + "!!", resp.getBody().asString()); } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteDevModeConfig.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteDevModeConfig.java index be0787fc88113..843a08b3eadcf 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteDevModeConfig.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteDevModeConfig.java @@ -15,8 +15,8 @@ public class QuteDevModeConfig { * This regular expression can be used to specify the templates for which the application is not restarted. * I.e. the templates are reloaded and only runtime validations are performed. *

- * The matched input is the template path relative from the {@code templates} directory and the - * {@code /} is used as a path separator. For example, {@code templates/foo.html}. + * The matched input is the template path that starts with a template root, and the {@code /} is used as a path separator. + * For example, {@code templates/foo.html}. */ @ConfigItem public Optional noRestartTemplates; From 738f27fcd724a92b86e34453bf5d250505ac4d49 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 21 Nov 2023 11:27:38 +0100 Subject: [PATCH 053/212] Dev mode: RuntimeUpdatesProcessor - never watch directories (cherry picked from commit f90ca79c153fb444384e057e123ef24a6411ae59) --- .../io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index 0a92c1909a8fe..24a1c175a3059 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -1128,7 +1128,9 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map // First find all matching paths from all roots try (final Stream walk = Files.walk(root)) { walk.forEach(path -> { - if (path.equals(root)) { + if (path.equals(root) + // Never watch directories + || Files.isDirectory(path)) { return; } // Use the relative path to match the watched file From 927a5e7cf2f69356a926f5643bc4bea3c03682cf Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Mon, 20 Nov 2023 18:55:12 +0100 Subject: [PATCH 054/212] Add classes from additional JPA model build items to pre-generate proxies (cherry picked from commit c48339d0f9d84bd0770ad942a20448ed543bf388) --- .../hibernate/orm/deployment/HibernateOrmProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 8a45d4ceca3c5..7d0101630b26a 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -416,6 +416,7 @@ public BytecodeRecorderConstantDefinitionBuildItem pregenProxies( JpaModelIndexBuildItem indexBuildItem, TransformedClassesBuildItem transformedClassesBuildItem, List persistenceUnitDescriptorBuildItems, + List additionalJpaModelBuildItems, BuildProducer generatedClassBuildItemBuildProducer, LiveReloadBuildItem liveReloadBuildItem) { Set managedClassAndPackageNames = new HashSet<>(jpaModel.getEntityClassNames()); @@ -426,6 +427,11 @@ public BytecodeRecorderConstantDefinitionBuildItem pregenProxies( // is used for packages too, and it relies (indirectly) on getManagedClassNames(). managedClassAndPackageNames.addAll(pud.getManagedClassNames()); } + + for (AdditionalJpaModelBuildItem additionalJpaModelBuildItem : additionalJpaModelBuildItems) { + managedClassAndPackageNames.add(additionalJpaModelBuildItem.getClassName()); + } + PreGeneratedProxies proxyDefinitions = generatedProxies(managedClassAndPackageNames, indexBuildItem.getIndex(), transformedClassesBuildItem, generatedClassBuildItemBuildProducer, liveReloadBuildItem); From 01e8d18d3196f33598d38b72e3311654a2c8fa9a Mon Sep 17 00:00:00 2001 From: Jonathan Kolberg Date: Tue, 21 Nov 2023 13:51:10 +0100 Subject: [PATCH 055/212] Update kindcontainer to 1.4.4 (cherry picked from commit 8596fc96deb8a043f0465e0fc740b250f1375cdd) --- 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 055ec2d25c619..0b50b2d09ee88 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -210,7 +210,7 @@ 3.3.3 2.0.0 - 1.3.0 + 1.4.4 2.7 2.4 2.4.0 From ad8ebe7391ab78ad8b86fdc27fc44f648e774a28 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 21 Nov 2023 12:28:56 +0000 Subject: [PATCH 056/212] Always execute a JPA password action (cherry picked from commit 2c29d55a7cadc4fcd4d792aeee9e160f54961ab9) --- .../common/deployment/JpaSecurityIdentityUtil.java | 13 ++++++++++--- .../jpa/common/runtime/JpaIdentityProviderUtil.java | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java b/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java index 5501ea4444353..054fcb33d6e69 100644 --- a/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java +++ b/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java @@ -45,18 +45,21 @@ public static void buildIdentity(Index index, JpaSecurityDefinition jpaSecurityD PanacheEntityPredicateBuildItem panacheEntityPredicate, FieldDescriptor passwordProviderField, MethodCreator outerMethod, ResultHandle userVar, BytecodeCreator innerMethod) { // if(user == null) throw new AuthenticationFailedException(); + + PasswordType passwordType = passwordTypeValue != null ? PasswordType.valueOf(passwordTypeValue.asEnum()) + : PasswordType.MCF; + try (BytecodeCreator trueBranch = innerMethod.ifNull(userVar).trueBranch()) { + ResultHandle exceptionInstance = trueBranch .newInstance(MethodDescriptor.ofConstructor(AuthenticationFailedException.class)); + trueBranch.invokeStaticMethod(passwordActionMethod(), trueBranch.load(passwordType)); trueBranch.throwException(exceptionInstance); } // :pass = user.pass | user.getPass() ResultHandle pass = jpaSecurityDefinition.password.readValue(innerMethod, userVar); - PasswordType passwordType = passwordTypeValue != null ? PasswordType.valueOf(passwordTypeValue.asEnum()) - : PasswordType.MCF; - if (passwordType == PasswordType.CUSTOM && passwordProviderValue == null) { throw new RuntimeException("Missing password provider for password type: " + passwordType); } @@ -245,4 +248,8 @@ private static MethodDescriptor getUtilMethod(String passwordProviderMethod) { return MethodDescriptor.ofMethod(JpaIdentityProviderUtil.class, passwordProviderMethod, org.wildfly.security.password.Password.class, String.class); } + + private static MethodDescriptor passwordActionMethod() { + return MethodDescriptor.ofMethod(JpaIdentityProviderUtil.class, "passwordAction", void.class, PasswordType.class); + } } diff --git a/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java b/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java index a65f771596a5d..15a3c4710d1c8 100644 --- a/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java +++ b/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java @@ -2,6 +2,7 @@ import java.security.spec.InvalidKeySpecException; import java.util.List; +import java.util.UUID; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; @@ -10,9 +11,11 @@ import org.wildfly.security.password.util.ModularCrypt; import org.wildfly.security.provider.util.ProviderUtil; +import io.quarkus.elytron.security.common.BcryptUtil; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.identity.request.TrustedAuthenticationRequest; import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.security.jpa.PasswordType; import io.quarkus.security.runtime.QuarkusPrincipal; import io.quarkus.security.runtime.QuarkusSecurityIdentity; @@ -70,4 +73,13 @@ public static Password getMcfPassword(String pass) { throw new RuntimeException(e); } } + + public static void passwordAction(PasswordType type) { + String uuid = UUID.randomUUID().toString(); + if (type == PasswordType.CLEAR) { + ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, uuid.toCharArray()); + } else { + BcryptUtil.bcryptHash(uuid); + } + } } From db8054a3232711b75d9576265c8619aa88bf66f0 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 22 Nov 2023 17:41:18 +0100 Subject: [PATCH 057/212] Docs: add keywords to Vert.x guides - so that it's possible to filter guides via "vertx" instead of "vert.x" (cherry picked from commit 470d96fa8de36b360b389565bb2fdd4ec292a261) --- docs/src/main/asciidoc/reactive-event-bus.adoc | 1 + docs/src/main/asciidoc/vertx-reference.adoc | 1 + docs/src/main/asciidoc/vertx.adoc | 1 + 3 files changed, 3 insertions(+) diff --git a/docs/src/main/asciidoc/reactive-event-bus.adoc b/docs/src/main/asciidoc/reactive-event-bus.adoc index b1c03416d0770..d0c2f3bc2ab52 100644 --- a/docs/src/main/asciidoc/reactive-event-bus.adoc +++ b/docs/src/main/asciidoc/reactive-event-bus.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc = Using the event bus include::_attributes.adoc[] :categories: messaging +:keywords: vertx vert.x :summary: This guide explains how different beans can interact using the event bus. :topics: messaging,event-bus,vert.x :extensions: io.quarkus:quarkus-vertx diff --git a/docs/src/main/asciidoc/vertx-reference.adoc b/docs/src/main/asciidoc/vertx-reference.adoc index eed2eadb4d316..a069688101698 100644 --- a/docs/src/main/asciidoc/vertx-reference.adoc +++ b/docs/src/main/asciidoc/vertx-reference.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc = Vert.x Reference Guide include::_attributes.adoc[] :categories: miscellaneous +:keywords: vertx event verticle :summary: This reference guide provides advanced details about the usage and the configuration of the Vert.x instance used by Quarkus. https://vertx.io[Vert.x] is a toolkit for building reactive applications. diff --git a/docs/src/main/asciidoc/vertx.adoc b/docs/src/main/asciidoc/vertx.adoc index 09fb4a2a4b706..c5bbb73681ebd 100644 --- a/docs/src/main/asciidoc/vertx.adoc +++ b/docs/src/main/asciidoc/vertx.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc = Using Eclipse Vert.x API from a Quarkus Application include::_attributes.adoc[] :categories: miscellaneous +:keywords: vertx event verticle :summary: This guide explains how to use Vert.x in Quarkus to build reactive applications. https://vertx.io[Vert.x] is a toolkit for building reactive applications. From 60933cecc0ea6d17f449586b151dee560f8a8793 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 22 Nov 2023 17:20:42 +0100 Subject: [PATCH 058/212] Vert.x: report exception for blocking message consumer methods - fixes #37222 (cherry picked from commit 9917e6bfe277c5d777e9d29ef0a6f36fcf2668f3) --- .../vertx/deployment/MessageConsumerFailureTest.java | 12 +++++++++--- .../vertx/runtime/VertxEventBusConsumerRecorder.java | 4 +++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java index c282c6c1bb49a..888acb58d3d07 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java @@ -58,6 +58,12 @@ public void testFailure() throws InterruptedException { @Test public void testFailureNoReplyHandler() throws InterruptedException { + verifyFailureNoReply("foo", "Foo is dead", IllegalStateException.class); + verifyFailureNoReply("foo-blocking", "Red is dead", IllegalStateException.class); + } + + void verifyFailureNoReply(String address, String expectedMessage, Class expectedException) + throws InterruptedException { Handler oldHandler = vertx.exceptionHandler(); try { BlockingQueue synchronizer = new LinkedBlockingQueue<>(); @@ -71,10 +77,10 @@ public void handle(Throwable event) { } } }); - eventBus.send("foo", "bar"); + eventBus.send(address, "hello"); Object ret = synchronizer.poll(2, TimeUnit.SECONDS); - assertTrue(ret instanceof IllegalStateException); - assertEquals("Foo is dead", ((IllegalStateException) ret).getMessage()); + assertTrue(expectedException.isAssignableFrom(ret.getClass())); + assertEquals(expectedMessage, ((Throwable) ret).getMessage()); } finally { vertx.exceptionHandler(oldHandler); } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxEventBusConsumerRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxEventBusConsumerRecorder.java index 7a3edda524766..b5f60a42f2246 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxEventBusConsumerRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxEventBusConsumerRecorder.java @@ -28,6 +28,7 @@ import io.smallrye.common.vertx.VertxContext; import io.vertx.core.AsyncResult; import io.vertx.core.Context; +import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.eventbus.EventBus; @@ -138,7 +139,7 @@ public void run() { } }); } else { - dup.executeBlocking(new Callable() { + Future future = dup.executeBlocking(new Callable() { @Override public Void call() { try { @@ -154,6 +155,7 @@ public Void call() { return null; } }, invoker.isOrdered()); + future.onFailure(context::reportException); } } else { // Will run on the context used for the consumer registration. From 0aa55d575badfe99d19467f6cfb233feeafc3d79 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Thu, 23 Nov 2023 18:51:56 +0100 Subject: [PATCH 059/212] Bump testcontainers to 1.19.3 and use docker-java-bom (cherry picked from commit 12c2bbe539c67be36a9416cac29885c2c02b4411) --- bom/application/pom.xml | 48 +++++------------------------------------ 1 file changed, 5 insertions(+), 43 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 0b50b2d09ee88..ce0740f2a0634 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -206,8 +206,8 @@ 1.11.3 2.4.14.Final 0.1.18.Final - 1.19.1 - 3.3.3 + 1.19.3 + 3.3.4 2.0.0 1.4.4 @@ -3304,48 +3304,10 @@ com.github.docker-java - docker-java - ${docker-java.version} - - - com.github.docker-java - docker-java-api - ${docker-java.version} - - - com.github.docker-java - docker-java-core - ${docker-java.version} - - - com.github.docker-java - docker-java-transport - ${docker-java.version} - - - com.github.docker-java - docker-java-transport-httpclient5 - ${docker-java.version} - - - com.github.docker-java - docker-java-transport-jersey - ${docker-java.version} - - - com.github.docker-java - docker-java-transport-netty - ${docker-java.version} - - - com.github.docker-java - docker-java-transport-okhttp - ${docker-java.version} - - - com.github.docker-java - docker-java-transport-zerodep + docker-java-bom ${docker-java.version} + pom + import com.google.cloud.functions From b546555f29ab4f0591057043ae10e2d62ce7438c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:22:59 +0000 Subject: [PATCH 060/212] Bump org.jetbrains.kotlin:kotlin-gradle-plugin-api in /devtools/gradle Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin-api](https://github.com/JetBrains/kotlin) from 1.9.20 to 1.9.21. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.20...v1.9.21) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 463e0c6d8f1f904af95043bc7f8b596d1686106d) --- devtools/gradle/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index 9902875d78e44..5bb10bc0b84fc 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -2,7 +2,7 @@ plugin-publish = "1.2.1" # updating Kotlin here makes QuarkusPluginTest > shouldNotFailOnProjectDependenciesWithoutMain(Path) fail -kotlin = "1.9.20" +kotlin = "1.9.21" smallrye-config = "3.4.4" junit5 = "5.10.1" From d88f2643c4731b3dbcd285137f57b61490fd9f79 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 24 Nov 2023 07:55:34 +0100 Subject: [PATCH 061/212] Fix the major version of Java 21. 66 is Java 22. 65 is Java 21. (cherry picked from commit 3d815e7f09e4d1f2bdad4b86fc4c6ad6fd20964d) --- .../deployment/pkg/builditem/CompiledJavaVersionBuildItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java index 88dadbcc63dd4..ee2ab28b42d4d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/CompiledJavaVersionBuildItem.java @@ -76,7 +76,7 @@ final class Known implements JavaVersion { private static final int JAVA_11_MAJOR = 55; private static final int JAVA_17_MAJOR = 61; private static final int JAVA_19_MAJOR = 63; - private static final int JAVA_21_MAJOR = 66; + private static final int JAVA_21_MAJOR = 65; private final int determinedMajor; From ad0525f4ff56d36028f3afa1f16ff29817a37114 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 21 Nov 2023 14:34:17 +0000 Subject: [PATCH 062/212] Add a test showing how OIDC ID token can be propagated (cherry picked from commit 1e8c767ea5e8776d34947a1f964c429018eee6cb) --- .../quarkus/it/keycloak/FrontendResource.java | 12 ++++++++++ .../keycloak/IdTokenPropagationService.java | 20 ++++++++++++++++ .../IdTokenRequestReactiveFilter.java | 23 +++++++++++++++++++ .../it/keycloak/ProtectedResource.java | 8 +++---- .../src/main/resources/application.properties | 1 + .../OidcTokenReactivePropagationTest.java | 5 +++- 6 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenPropagationService.java create mode 100644 integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenRequestReactiveFilter.java diff --git a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java index 64d9d13c35a6e..58690fa283fd1 100644 --- a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java +++ b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java @@ -16,6 +16,10 @@ public class FrontendResource { @RestClient AccessTokenPropagationService accessTokenPropagationService; + @Inject + @RestClient + IdTokenPropagationService idTokenPropagationService; + @Inject @RestClient ServiceWithoutToken serviceWithoutToken; @@ -28,6 +32,14 @@ public Uni userNameAccessTokenPropagation() { return accessTokenPropagationService.getUserName(); } + @GET + @Path("id-token-propagation") + @Produces("text/plain") + @RolesAllowed("user") + public Uni userNameIdTokenPropagation() { + return idTokenPropagationService.getUserName(); + } + @GET @Path("service-without-token") @Produces("text/plain") diff --git a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenPropagationService.java b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenPropagationService.java new file mode 100644 index 0000000000000..8fe9e0e928cdd --- /dev/null +++ b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenPropagationService.java @@ -0,0 +1,20 @@ +package io.quarkus.it.keycloak; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.smallrye.mutiny.Uni; + +@RegisterRestClient +@RegisterProvider(IdTokenRequestReactiveFilter.class) +@Path("/") +public interface IdTokenPropagationService { + + @GET + @Produces("text/plain") + Uni getUserName(); +} diff --git a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenRequestReactiveFilter.java b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenRequestReactiveFilter.java new file mode 100644 index 0000000000000..82186e45db0c6 --- /dev/null +++ b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/IdTokenRequestReactiveFilter.java @@ -0,0 +1,23 @@ +package io.quarkus.it.keycloak; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.core.HttpHeaders; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; + +import io.quarkus.oidc.IdTokenCredential; + +@Priority(Priorities.AUTHENTICATION) +public class IdTokenRequestReactiveFilter implements ResteasyReactiveClientRequestFilter { + + @Inject + IdTokenCredential idToken; + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + requestContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, "Bearer " + idToken.getToken()); + } +} diff --git a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java index c4cf5320e129a..3b3a2a78883e2 100644 --- a/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java +++ b/integration-tests/oidc-token-propagation-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -1,13 +1,13 @@ package io.quarkus.it.keycloak; -import java.security.Principal; - import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import org.eclipse.microprofile.jwt.JsonWebToken; + import io.quarkus.security.Authenticated; import io.smallrye.mutiny.Uni; @@ -16,12 +16,12 @@ public class ProtectedResource { @Inject - Principal principal; + JsonWebToken jwt; @GET @Produces("text/plain") @RolesAllowed("user") public Uni principalName() { - return Uni.createFrom().item(principal.getName()); + return Uni.createFrom().item(jwt.getClaim("typ") + ":" + jwt.getName()); } } diff --git a/integration-tests/oidc-token-propagation-reactive/src/main/resources/application.properties b/integration-tests/oidc-token-propagation-reactive/src/main/resources/application.properties index 321847db72236..36253fa1c4c16 100644 --- a/integration-tests/oidc-token-propagation-reactive/src/main/resources/application.properties +++ b/integration-tests/oidc-token-propagation-reactive/src/main/resources/application.properties @@ -1,4 +1,5 @@ io.quarkus.it.keycloak.AccessTokenPropagationService/mp-rest/uri=http://localhost:8081/protected +io.quarkus.it.keycloak.IdTokenPropagationService/mp-rest/uri=http://localhost:8081/protected io.quarkus.it.keycloak.ServiceWithoutToken/mp-rest/uri=http://localhost:8081/protected quarkus.oidc.application-type=web-app diff --git a/integration-tests/oidc-token-propagation-reactive/src/test/java/io/quarkus/it/keycloak/OidcTokenReactivePropagationTest.java b/integration-tests/oidc-token-propagation-reactive/src/test/java/io/quarkus/it/keycloak/OidcTokenReactivePropagationTest.java index df2184d15568a..7a4cb646a36f0 100644 --- a/integration-tests/oidc-token-propagation-reactive/src/test/java/io/quarkus/it/keycloak/OidcTokenReactivePropagationTest.java +++ b/integration-tests/oidc-token-propagation-reactive/src/test/java/io/quarkus/it/keycloak/OidcTokenReactivePropagationTest.java @@ -29,8 +29,11 @@ public void testGetUserNameWithAccessTokenPropagation() throws Exception { loginForm.getInputByName("password").setValueAttribute("alice"); TextPage textPage = loginForm.getInputByName("login").click(); + assertEquals("Bearer:alice", textPage.getContent()); + + textPage = webClient.getPage("http://localhost:8081/frontend/id-token-propagation"); + assertEquals("ID:alice", textPage.getContent()); - assertEquals("alice", textPage.getContent()); webClient.getCookieManager().clearCookies(); } } From 82db5e6083e894b6821a9abb844e1aa05ef57b75 Mon Sep 17 00:00:00 2001 From: Martin Kofoed Date: Wed, 22 Nov 2023 15:15:42 +0100 Subject: [PATCH 063/212] When skipping @Provider auto-discovery for REST clients, take client filters into consideration (cherry picked from commit 42f23d8767b18329394f2bafbe648391a8297d47) --- .../RestClientReactiveProcessor.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 22a4b76f9b69e..5f5a7c34939f2 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -9,6 +9,8 @@ import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_QUERY_PARAM; import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_QUERY_PARAMS; import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_REDIRECT_HANDLER; +import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_REQUEST_FILTER; +import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_RESPONSE_FILTER; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS; @@ -297,18 +299,10 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, } } - List providerInterfaceNames = providerClass.interfaceNames(); - // don't register server specific types - if (providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_REQUEST_FILTER) - || providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_RESPONSE_FILTER) - || providerInterfaceNames.contains(ResteasyReactiveDotNames.EXCEPTION_MAPPER)) { + if (skipAutoDiscoveredProvider(providerClass.interfaceNames())) { continue; } - if (providerInterfaceNames.contains(ResteasyReactiveDotNames.FEATURE)) { - continue; // features should not be automatically registered for the client, see javadoc for Feature - } - DotName providerDotName = providerClass.name(); int priority = getAnnotatedPriority(index, providerDotName.toString(), Priorities.USER); @@ -580,6 +574,29 @@ && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER, Set.of(AP } } + /** + * Based on a list of interfaces implemented by @Provider class, determine if registration + * should be skipped or not. Server-specific types should be omitted unless implementation + * of a ClientRequestFilter exists on the same class explicitly. + * Features should always be omitted. + */ + private boolean skipAutoDiscoveredProvider(List providerInterfaceNames) { + if (providerInterfaceNames.contains(ResteasyReactiveDotNames.FEATURE)) { + return true; + } + if (providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_REQUEST_FILTER) + || providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_RESPONSE_FILTER) + || providerInterfaceNames.contains(ResteasyReactiveDotNames.EXCEPTION_MAPPER)) { + if (providerInterfaceNames.contains(CLIENT_REQUEST_FILTER) + || providerInterfaceNames.contains(CLIENT_RESPONSE_FILTER)) { + return false; + } else { + return true; + } + } + return false; + } + private Map populateClientExceptionMapperFromAnnotations( BuildProducer generatedClasses, BuildProducer reflectiveClasses, IndexView index) { From ecc0b51594e5631bcdbb3927fb14b6ff70f55bb8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 24 Nov 2023 13:53:49 +0100 Subject: [PATCH 064/212] Prepare docs/sync-web-site.sh for automated releases (cherry picked from commit 37fefb380dd1a537fdc2905d8f828cf72358c5cd) --- docs/sync-web-site.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index 1041831b12477..353e45c2c58e0 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -36,7 +36,11 @@ if [ -z $TARGET_DIR ]; then if [[ "$QUARKUS_WEB_SITE_PUSH" != "true" ]]; then GIT_OPTIONS="--depth=1" fi - git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} + if [ -n "${RELEASE_GITHUB_TOKEN}" ]; then + git clone -b develop --single-branch $GIT_OPTIONS https://${RELEASE_GITHUB_TOKEN}:@github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} + else + git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} + fi fi if [ $BRANCH == "main" ] && [ "$QUARKUS_RELEASE" == "true" ]; then From 396da834e1da8873de37239b9529ba15f388e8d0 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 16 Nov 2023 15:53:26 +0000 Subject: [PATCH 065/212] Use the default tenant resolver if the custom one does not resolve a tenant (cherry picked from commit 21fbe9dc129a4fac3dd998d95bd4e6ac58ed7153) --- .../security-openid-connect-multitenancy.adoc | 83 ++++++++++--------- .../runtime/DefaultTenantConfigResolver.java | 5 +- .../it/keycloak/CustomTenantResolver.java | 46 +--------- 3 files changed, 49 insertions(+), 85 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index 9622829047e1a..7c9bb2dd06daf 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -586,48 +586,12 @@ user `alice` exists in both tenants, for the application they are distinct users When you set multiple tenant configurations in the `application.properties` file, you only need to specify how the tenant identifier gets resolved. To configure the resolution of the tenant identifier, use one of the following options: -* <> * <> +* <> * <> -[[default-tenant-resolver]] -=== Default resolution - -The default resolution for a tenant identifier is convention based, whereby the authentication request must include the tenant identifier in the last segment of the request path. - -The following `application.properties` example shows how you can configure two tenants named `google` and `github`: - -[source,properties] ----- -# Tenant 'google' configuration -quarkus.oidc.google.provider=google -quarkus.oidc.google.client-id=${google-client-id} -quarkus.oidc.google.credentials.secret=${google-client-secret} -quarkus.oidc.google.authentication.redirect-path=/signed-in - -# Tenant 'github' configuration -quarkus.oidc.github.provider=google -quarkus.oidc.github.client-id=${github-client-id} -quarkus.oidc.github.credentials.secret=${github-client-secret} -quarkus.oidc.github.authentication.redirect-path=/signed-in ----- - -In this example, both tenants configure OIDC `web-app` applications to use an authorization code flow to authenticate users and also require session cookies to get generated after the authentication has taken place. -After either Google or GitHub authenticates the current user, the user gets returned to the `/signed-in` area for authenticated users, for example, a secured resource path on the JAX-RS endpoint. - -Finally, to complete the default tenant resolution, set the following configuration property: - -[source,properties] ----- -quarkus.http.auth.permission.login.paths=/google,/github -quarkus.http.auth.permission.login.policy=authenticated ----- - -If the endpoint is running on `http://localhost:8080`, you can also provide UI options for users to log in to either `http://localhost:8080/google` or `http://localhost:8080/github`, without having to add specific`/google` or `/github` JAX-RS resource paths. -Tenant identifiers are also recorded in the session cookie names after the authentication is completed. -Therefore, authenticated users can access the secured application area without requiring either the `google` or `github` path values to be included in the secured URL. - -Default resolution can also work for Bearer token authentication but it might be less practical in this case because a tenant identifier will always need to be set as the last path segment value. +These tenant resolution options will be tried in turn, in the order they are listed, until the tenant id gets resolved. +If the tenant id remains unresolved (`null`) in the end then the default (unnamed) tenant configuration will be selected. [[tenant-resolver]] === Resolve with `TenantResolver` @@ -672,6 +636,45 @@ public class CustomTenantResolver implements TenantResolver { In this example, the value of the last request path segment is a tenant ID, but if required, you can implement a more complex tenant identifier resolution logic. +[[default-tenant-resolver]] +=== Default resolution + +The default resolution for a tenant identifier is convention based, whereby the authentication request must include the tenant identifier in the last segment of the request path. + +The following `application.properties` example shows how you can configure two tenants named `google` and `github`: + +[source,properties] +---- +# Tenant 'google' configuration +quarkus.oidc.google.provider=google +quarkus.oidc.google.client-id=${google-client-id} +quarkus.oidc.google.credentials.secret=${google-client-secret} +quarkus.oidc.google.authentication.redirect-path=/signed-in + +# Tenant 'github' configuration +quarkus.oidc.github.provider=google +quarkus.oidc.github.client-id=${github-client-id} +quarkus.oidc.github.credentials.secret=${github-client-secret} +quarkus.oidc.github.authentication.redirect-path=/signed-in +---- + +In this example, both tenants configure OIDC `web-app` applications to use an authorization code flow to authenticate users and also require session cookies to get generated after the authentication has taken place. +After either Google or GitHub authenticates the current user, the user gets returned to the `/signed-in` area for authenticated users, for example, a secured resource path on the JAX-RS endpoint. + +Finally, to complete the default tenant resolution, set the following configuration property: + +[source,properties] +---- +quarkus.http.auth.permission.login.paths=/google,/github +quarkus.http.auth.permission.login.policy=authenticated +---- + +If the endpoint is running on `http://localhost:8080`, you can also provide UI options for users to log in to either `http://localhost:8080/google` or `http://localhost:8080/github`, without having to add specific`/google` or `/github` JAX-RS resource paths. +Tenant identifiers are also recorded in the session cookie names after the authentication is completed. +Therefore, authenticated users can access the secured application area without requiring either the `google` or `github` path values to be included in the secured URL. + +Default resolution can also work for Bearer token authentication but it might be less practical in this case because a tenant identifier will always need to be set as the last path segment value. + [[annotations-tenant-resolver]] === Resolve with annotations @@ -775,6 +778,8 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { The `OidcTenantConfig` returned from this method is the same used to parse the `oidc` namespace configuration from the `application.properties`. You can populate it using any of the settings supported by the `quarkus-oidc` extension. +If the dynamic tenant resolver returns `null` then a <> will be attempted next. + === Tenant resolution for OIDC `web-app` applications The simplest option for resolving OIDC `web-app` application configuration is to follow the steps described in the <> section. diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java index 1448c112aa928..ef07097a85bbc 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java @@ -152,9 +152,12 @@ private TenantConfigContext getStaticTenantContext(RoutingContext context) { if (tenantId == null && context.get(CURRENT_STATIC_TENANT_ID_NULL) == null) { if (tenantResolver.isResolvable()) { tenantId = tenantResolver.get().resolve(context); - } else if (tenantConfigBean.getStaticTenantsConfig().size() > 0) { + } + + if (tenantId == null && tenantConfigBean.getStaticTenantsConfig().size() > 0) { tenantId = defaultStaticTenantResolver.resolve(context); } + if (tenantId == null) { tenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE); } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java index 34ffd429732e1..2fe6cca9790dc 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java @@ -17,54 +17,10 @@ public String resolve(RoutingContext context) { if (path.endsWith("code-flow") || path.endsWith("code-flow/logout")) { return "code-flow"; } - if (path.endsWith("code-flow-encrypted-id-token-jwk")) { - return "code-flow-encrypted-id-token-jwk"; - } - if (path.endsWith("code-flow-encrypted-id-token-pem")) { - return "code-flow-encrypted-id-token-pem"; - } if (path.endsWith("code-flow-form-post") || path.endsWith("code-flow-form-post/front-channel-logout")) { return "code-flow-form-post"; } - if (path.endsWith("code-flow-user-info-only")) { - return "code-flow-user-info-only"; - } - if (path.endsWith("code-flow-user-info-github")) { - return "code-flow-user-info-github"; - } - if (path.endsWith("bearer-user-info-github-service")) { - return "bearer-user-info-github-service"; - } - if (path.endsWith("code-flow-user-info-github-cached-in-idtoken")) { - return "code-flow-user-info-github-cached-in-idtoken"; - } - if (path.endsWith("code-flow-token-introspection")) { - return "code-flow-token-introspection"; - } - if (path.endsWith("bearer")) { - return "bearer"; - } - if (path.endsWith("bearer-id")) { - return "bearer-id"; - } - if (path.endsWith("bearer-required-algorithm")) { - return "bearer-required-algorithm"; - } - if (path.endsWith("bearer-azure")) { - return "bearer-azure"; - } - if (path.endsWith("bearer-no-introspection")) { - return "bearer-no-introspection"; - } - if (path.endsWith("bearer-role-claim-path")) { - return "bearer-role-claim-path"; - } - if (path.endsWith("bearer-key-without-kid-thumbprint")) { - return "bearer-key-without-kid-thumbprint"; - } - if (path.endsWith("bearer-wrong-role-path")) { - return "bearer-wrong-role-path"; - } + return null; } } From 7ec0c0a302943e4de50e1ea41ee2072b2b38af86 Mon Sep 17 00:00:00 2001 From: Andreas Eberle Date: Mon, 6 Nov 2023 15:38:30 +0100 Subject: [PATCH 066/212] Update Kotlin to version 1.9.21, Mockito to 5.7.0 (cherry picked from commit b206484ff53f565671067172671439783bd2ad5e) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 4 ++-- .../src/test/java/io/quarkus/gradle/QuarkusPluginTest.java | 2 +- independent-projects/arc/pom.xml | 6 +++--- .../conditional-dependencies-kotlin/build.gradle.kts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index ce0740f2a0634..2ff092d816b04 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -157,7 +157,7 @@ 2.14.0 2.2.0 1.0.0 - 1.9.10 + 1.9.21 1.7.3 0.27.0 1.6.0 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index cfe5555cab915..ebbd89b7700dd 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -20,8 +20,8 @@ 3.11.0 - 1.9.10 - 1.9.0 + 1.9.21 + 1.9.10 2.13.8 4.8.1 diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 580777c029f6d..862ad0a62abb6 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -105,7 +105,7 @@ public void shouldReturnMultipleOutputSourceDirectories() { @Test public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException { - var kotlinVersion = System.getProperty("kotlin_version", "1.9.10"); + var kotlinVersion = System.getProperty("kotlin_version", "1.9.21"); var settingFile = testProjectDir.resolve("settings.gradle.kts"); var mppProjectDir = testProjectDir.resolve("mpp"); var quarkusProjectDir = testProjectDir.resolve("quarkus"); diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 5219daa4e1bce..0bc692b18aed0 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -55,9 +55,9 @@ 3.24.2 5.10.0 - 1.9.10 - 1.7.1 - 5.4.0 + 1.9.21 + 1.7.3 + 5.7.0 1.7.0.Final 2.0.1 diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts index e5d23bacc50de..5e3808937ce3e 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.9.10" - kotlin("plugin.allopen") version "1.9.10" + kotlin("jvm") version "1.9.21" + kotlin("plugin.allopen") version "1.9.21" id("io.quarkus") } From 58fc317ef6fcec3e2de553321b4554da3c3669af Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 25 Nov 2023 11:58:13 +0100 Subject: [PATCH 067/212] Use batch mode for update-version.sh (cherry picked from commit 0685076e9a835c4eb29b52daa3ef2e1db773e923) --- update-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update-version.sh b/update-version.sh index 3eb07d975cbd4..1ebbaf2382826 100755 --- a/update-version.sh +++ b/update-version.sh @@ -9,7 +9,7 @@ if [ $# -eq 0 ]; then fi VERSION=$1 -./mvnw -Dscan=false -Dgradle.cache.local.enabled=false versions:set -Dtcks -DnewVersion="${VERSION}" -DgenerateBackupPoms=false -DprocessAllModules -Prelocations +./mvnw -e -B -Dscan=false -Dgradle.cache.local.enabled=false versions:set -Dtcks -DnewVersion="${VERSION}" -DgenerateBackupPoms=false -DprocessAllModules -Prelocations if [ -f devtools/gradle/gradle.properties ]; then sed -i -r "s/^version( ?= ?).*$/version\1${VERSION}/" devtools/gradle/gradle.properties From ae706c3d2e4b66dcfdda299884332ae6c8996e7c Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 25 Nov 2023 11:49:18 +0100 Subject: [PATCH 068/212] Avoid asking for GPG passphrase on CI (cherry picked from commit 86673f962548e140636261add0490c03553334ba) --- independent-projects/parent/pom.xml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/independent-projects/parent/pom.xml b/independent-projects/parent/pom.xml index be3075bf1548b..33c243cd1c009 100644 --- a/independent-projects/parent/pom.xml +++ b/independent-projects/parent/pom.xml @@ -831,5 +831,30 @@ + + ci + + + env.CI + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + + --pinentry-mode + loopback + + + + + + + From 45f1a67172c85e34303070d6dd15d7fd2bdc9916 Mon Sep 17 00:00:00 2001 From: Sebastian Schuster Date: Thu, 23 Nov 2023 08:48:36 +0100 Subject: [PATCH 069/212] 37279 bump mssql jdbc driver to 12.4.2 (cherry picked from commit 306c8733b4e6ea0c5735d1d2cdc83d7e4c4eacd4) --- 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 2ff092d816b04..c4bd155f1bbbb 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -128,7 +128,7 @@ 42.6.0 3.2.0 8.0.33 - 12.4.0.jre11 + 12.4.2.jre11 1.6.7 23.3.0.23.09 10.14.2.0 From 80321f67e96ab2bb5ccef1b3327fee1379cf9768 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 28 Nov 2023 09:58:48 +0200 Subject: [PATCH 070/212] Runtime (re)initialize Netty's PlatformDependent classes Closes https://github.com/quarkusio/quarkus/issues/17839 (cherry picked from commit 76c02780328546fb5e60088e7a7447f35a0ef48a) --- .../main/java/io/quarkus/netty/deployment/NettyProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index 766d2f8c00800..5de637d124c8c 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -162,6 +162,9 @@ NativeImageConfigBuildItem build( log.debug("Not registering Netty native kqueue classes as they were not found"); } + builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent"); + builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent0"); + return builder //TODO: make configurable .build(); } From 8bf1f64e5d905350a39606e120c02493460cb4be Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 28 Nov 2023 10:56:23 +0100 Subject: [PATCH 071/212] Unlist quarkus-resteasy-qute and quarkus-resteasy-reactive-qute (cherry picked from commit 5e8e7e6db2b9707b33a401a450acc0fa75712b3f) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index d0391e67d3f8b..0da36a3fd354e 100644 --- a/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -2,6 +2,7 @@ artifact: ${project.groupId}:${project.artifactId}:${project.version} name: "RESTEasy Classic Qute" metadata: + unlisted: true keywords: - "templating" - "templates" diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 9f0006b6bf2fa..eec8b27d0eb38 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,6 +1,7 @@ name: "RESTEasy Reactive Qute" artifact: ${project.groupId}:${project.artifactId}:${project.version} metadata: + unlisted: true keywords: - "templating" - "templates" From c5727582c19d1c10e96704b0af4768cba79726eb Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 28 Nov 2023 10:32:09 -0600 Subject: [PATCH 072/212] Fix incorrect log dependency (cherry picked from commit 8e22ed9f2e9914885b1a620219422404840e037f) --- test-framework/junit5-component/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-framework/junit5-component/pom.xml b/test-framework/junit5-component/pom.xml index fafa7e91c4d8b..8d6bb92298438 100644 --- a/test-framework/junit5-component/pom.xml +++ b/test-framework/junit5-component/pom.xml @@ -61,7 +61,7 @@ org.jboss.logmanager - jboss-logmanager-embedded + jboss-logmanager test From 8910702a00f229e75c8d540be9887071fae7897a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 28 Nov 2023 13:38:37 +0100 Subject: [PATCH 073/212] Update Boucycastle to 1.77 and Boucycastle FIPS to 1.0.2.4 (cherry picked from commit bf0829f31ed7e00afaf371631770c2895d714f0b) --- bom/application/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index c4bd155f1bbbb..07102cf90be9a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -15,8 +15,8 @@ 2.0.1 - 1.76 - 1.0.2.3 + 1.77 + 1.0.2.4 1.0.17 5.0.0 3.0.2 From d6935c948a4c57be6d3e57fa84149e434e489e96 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 28 Nov 2023 17:01:05 +0000 Subject: [PATCH 074/212] Remove SecureRandom providerDefaultRandom from BouncyCastleFipsProvider substitution (cherry picked from commit e9da9c8b630c5b523068bbe9a61f50dccd562eca) --- .../security/runtime/graal/BouncyCastleSubstitutions.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java index b647e93b36ccc..7fad7acc4698d 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java @@ -95,10 +95,6 @@ final class Target_org_bouncycastle_jcajce_provider_BouncyCastleFipsProvider { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private SecureRandom entropySource; - - @Alias - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // - private SecureRandom providerDefaultRandom; } @com.oracle.svm.core.annotate.TargetClass(className = "org.bouncycastle.math.ec.ECPoint", onlyWith = BouncyCastleCryptoFips.class) From 6cbe4f2589a13b19fa5e2b828483d2d45e66fd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 28 Nov 2023 22:32:22 +0100 Subject: [PATCH 075/212] Docs: list runtime form auth properties (cherry picked from commit de807d8666ffa77f47febbb07842c9fab0d6835a) --- docs/src/main/asciidoc/security-authentication-mechanisms.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc index 108657ce55dc0..fe736a13f3146 100644 --- a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc +++ b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc @@ -173,6 +173,7 @@ public Response logout() { The following properties can be used to configure form-based authentication: include::{generated-dir}/config/quarkus-vertx-http-config-group-form-auth-config.adoc[opts=optional, leveloffset=+1] +include::{generated-dir}/config/quarkus-vertx-http-config-group-auth-runtime-config.adoc[opts=optional, leveloffset=+1] [[mutual-tls]] === Mutual TLS authentication From 7d5ec9e0c1ce501dc3d7115164c88afa1c30180b Mon Sep 17 00:00:00 2001 From: mert18 Date: Tue, 28 Nov 2023 06:09:50 +0300 Subject: [PATCH 076/212] dev-v1 deprecated url changed to dev-ui in documentation (cherry picked from commit 1d85a445c4408569105ed55f69e17ab3889f1751) --- .../src/main/resources/META-INF/resources/index.entry.qute.html | 2 +- docs/src/main/asciidoc/dev-mode-differences.adoc | 2 +- docs/src/main/asciidoc/grpc-service-implementation.adoc | 2 +- docs/src/main/asciidoc/security-keycloak-authorization.adoc | 2 +- .../security-oidc-bearer-token-authentication-tutorial.adoc | 2 +- docs/src/main/asciidoc/security-openid-connect-client.adoc | 2 +- .../src/main/asciidoc/security-openid-connect-dev-services.adoc | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html index 4f3acc377dc7f..07f8b05dacbc2 100644 --- a/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html +++ b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html @@ -1,3 +1,3 @@ {#include index-entry} -{#body}
› It can be tested in the Dev UI (available in dev mode only). +{#body}
› It can be tested in the Dev UI (available in dev mode only). {/include} \ No newline at end of file diff --git a/docs/src/main/asciidoc/dev-mode-differences.adoc b/docs/src/main/asciidoc/dev-mode-differences.adoc index b49ee27d3ca6b..30db2b5d0323c 100644 --- a/docs/src/main/asciidoc/dev-mode-differences.adoc +++ b/docs/src/main/asciidoc/dev-mode-differences.adoc @@ -50,7 +50,7 @@ Examples of such operations are: ==== A new Dev UI has been implemented in Quarkus 3.x. Not all the features are available yet. -You can still access the previous version of the Dev UI using: http://localhost:8080/q/dev-v1/. +You can still access the previous version of the Dev UI using: http://localhost:8080/q/dev-ui/. ==== === Error pages diff --git a/docs/src/main/asciidoc/grpc-service-implementation.adoc b/docs/src/main/asciidoc/grpc-service-implementation.adoc index 2d870aed2c917..8878d03c29b28 100644 --- a/docs/src/main/asciidoc/grpc-service-implementation.adoc +++ b/docs/src/main/asciidoc/grpc-service-implementation.adoc @@ -359,7 +359,7 @@ public class HelloServiceTest implements Greeter { == Trying out your services manually In the dev mode, you can try out your gRPC services in the Quarkus Dev UI. -Just go to http://localhost:8080/q/dev-v1 and click on _Services_ under the gRPC tile. +Just go to http://localhost:8080/q/dev-ui and click on _Services_ under the gRPC tile. Please note that your application needs to expose the "normal" HTTP port for the Dev UI to be accessible. If your application does not expose any HTTP endpoints, you can create a dedicated profile with a dependency on `quarkus-vertx-http`: [source,xml] diff --git a/docs/src/main/asciidoc/security-keycloak-authorization.adoc b/docs/src/main/asciidoc/security-keycloak-authorization.adoc index 6f58b3c0681c1..8d6c6a063ac92 100644 --- a/docs/src/main/asciidoc/security-keycloak-authorization.adoc +++ b/docs/src/main/asciidoc/security-keycloak-authorization.adoc @@ -235,7 +235,7 @@ include::{includes}/devtools/dev.adoc[] xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] will launch a Keycloak container and import a `quarkus-realm.json`. -Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-v1[/q/dev-v1] and click on a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. +Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click on a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. You will be asked to log in into a `Single Page Application` provided by `OpenID Connect Dev UI`: diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc index 79b64542b456d..1be0d80000013 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc @@ -263,7 +263,7 @@ For more information, see the link:{url-quarkusio-guides}security-keycloak-admin include::{includes}/devtools/dev.adoc[] ==== * link:{quarkusio-guides}/security-openid-connect-dev-services[Dev Services for Keycloak] will start a Keycloak container and import a `quarkus-realm.json`. -. Open a link:{url-quarkusio-guides}dev-ui[Dev UI], which you can find at http://localhost:8080/q/dev-v1[/q/dev-v1], then click a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. +. Open a link:{url-quarkusio-guides}dev-ui[Dev UI], which you can find at http://localhost:8080/q/dev-ui[/q/dev-ui], then click a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. . When prompted to log in to a `Single Page Application` provided by `OpenID Connect Dev UI`, do the following steps: * Log in as `alice` (password: `alice`), who has a `user` role. diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index 62a63de2f71a4..bcce8989a88da 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -363,7 +363,7 @@ include::{includes}/devtools/dev.adoc[] xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] will launch a Keycloak container and import a `quarkus-realm.json`. -Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-v1[/q/dev-v1] and click on a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. +Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click on a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. You will be asked to log in into a `Single Page Application` provided by `OpenID Connect Dev UI`: diff --git a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc index 3e4f7fc20672e..ee38209a69427 100644 --- a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc @@ -419,7 +419,7 @@ Please follow the xref:dev-ui.adoc[Dev UI] tutorial as well as check the `extens == Non Application Root Path Considerations -This document refers to the `http://localhost:8080/q/dev-v1` Dev UI URL in several places where `q` is a default non application root path. If you customize `quarkus.http.root-path` and/or `quarkus.http.non-application-root-path` properties then replace `q` accordingly, please see https://quarkus.io/blog/path-resolution-in-quarkus/[Path Resolution in Quarkus] for more information. +This document refers to the `http://localhost:8080/q/dev-ui` Dev UI URL in several places where `q` is a default non application root path. If you customize `quarkus.http.root-path` and/or `quarkus.http.non-application-root-path` properties then replace `q` accordingly, please see https://quarkus.io/blog/path-resolution-in-quarkus/[Path Resolution in Quarkus] for more information. == References From 36421ae188f6fc2a6b85f63e6bf6096527a97653 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 28 Nov 2023 22:19:25 +0200 Subject: [PATCH 077/212] Improve Docker Desktop detection During some experiments I noticed that when setting DOCKER_HOST, `docker info` no longer reports the context as `desktop-linux`. Looking for "Docker Desktop" as the docker server operating system seems more reliable. I am keeping the `desktop-linux` filter as a fallback nevertheless. Improves https://github.com/quarkusio/quarkus/pull/37242 (cherry picked from commit 5336193a00f08f7a787a4ec6fc46ae66b4d2cf5b) --- .../java/io/quarkus/runtime/util/ContainerRuntimeUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index 607ead4f24980..42cc489ec9988 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -194,7 +194,8 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { // We also treat Docker Desktop as "rootless" since the way it binds mounts does not // transparently map the host user ID and GID // see https://docs.docker.com/desktop/faqs/linuxfaqs/#how-do-i-enable-file-sharing - stringPredicate = line -> line.trim().equals("rootless") || line.contains("desktop-linux"); + stringPredicate = line -> line.trim().equals("rootless") || line.contains("Docker Desktop") + || line.contains("desktop-linux"); } else { stringPredicate = line -> line.trim().equals("rootless: true"); } From e164f6f8ba84e9049923c4f80a695f8189c6a78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 28 Nov 2023 23:20:12 +0100 Subject: [PATCH 078/212] Docs: fix OIDC credentials reference to secret key (cherry picked from commit d7d6fef9b569579c8d5ff152a5afdb3dbbc793f6) --- .../java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index 45b1923d5d805..f3810d610a001 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -180,7 +180,7 @@ public static enum Method { } /** - * The client secret value - it will be ignored if 'secret.key' is set + * The client secret value - it will be ignored if 'credentials.secret' is set */ @ConfigItem public Optional value = Optional.empty(); From 3b6724ddcbf2e2d1c31a2e9d718365db6d9cf4d8 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 28 Nov 2023 19:05:16 +0000 Subject: [PATCH 079/212] Do not fail the request in OidcClient filters if OidcClient is disabled (cherry picked from commit ec9c312fb8d04576de1890d14d01443d34df3d30) --- .../filter/runtime/AbstractOidcClientRequestFilter.java | 4 ++-- .../runtime/AbstractOidcClientRequestReactiveFilter.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java index 5b2a0f25f67ac..458fa15740778 100644 --- a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java +++ b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java @@ -25,8 +25,8 @@ public void filter(ClientRequestContext requestContext) throws IOException { final String accessToken = getAccessToken(); requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_SCHEME_WITH_SPACE + accessToken); } catch (DisabledOidcClientException ex) { - LOG.debug("Client is disabled, aborting the request"); - throw ex; + LOG.debug("Client is disabled, acquiring and propagating the token is not necessary"); + return; } catch (Exception ex) { LOG.debugf("Access token is not available, cause: %s, aborting the request", ex.getMessage()); throw (ex instanceof RuntimeException) ? (RuntimeException) ex : new RuntimeException(ex); diff --git a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java index 9c266fb892baf..5bff7d719403d 100644 --- a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java +++ b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java @@ -39,11 +39,12 @@ public void accept(Tokens tokens) { @Override public void accept(Throwable t) { if (t instanceof DisabledOidcClientException) { - LOG.debug("Client is disabled, aborting the request"); + LOG.debug("Client is disabled, acquiring and propagating the token is not necessary"); + requestContext.resume(); } else { LOG.debugf("Access token is not available, cause: %s, aborting the request", t.getMessage()); + requestContext.resume((t instanceof RuntimeException) ? t : new RuntimeException(t)); } - requestContext.resume((t instanceof RuntimeException) ? t : new RuntimeException(t)); } }); } From 08f5751dbe0e9075fb8f9f7600f53541ce4d8bbb Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 28 Nov 2023 09:43:26 +0100 Subject: [PATCH 080/212] Upgrade to Jandex 3.1.6 (cherry picked from commit 6986f8076b2ecf1a95188f8c05e938f76c2c8fef) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/junit5-virtual-threads/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 07102cf90be9a..3c37f3131f632 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -20,7 +20,7 @@ 1.0.17 5.0.0 3.0.2 - 3.1.5 + 3.1.6 1.3.2 1 1.1.5 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ebbd89b7700dd..ac589a8b8e299 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -33,7 +33,7 @@ ${version.surefire.plugin} - 3.1.5 + 3.1.6 1.0.0 2.5.10 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 0bc692b18aed0..9fcf219e160dd 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -48,7 +48,7 @@ 2.0.1 1.7.0 - 3.1.5 + 3.1.6 3.5.3.Final 2.5.1 1.6.Final diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 0db20683a71fe..766e2e9e5d7ad 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -40,7 +40,7 @@ 3.11.0 3.2.1 3.1.2 - 3.1.5 + 3.1.6 3.24.2 diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index 4b35067e4ac1e..d30d68dcb74ef 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -44,7 +44,7 @@ 3.11.0 3.2.1 3.1.2 - 3.1.5 + 3.1.6 2.23.0 1.9.0 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index af92986637614..ca6c9c5db9f07 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -43,7 +43,7 @@ 11 5.10.0 3.24.2 - 3.1.5 + 3.1.6 1.7.0 3.5.3.Final 3.11.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 0b5bf88744fa0..4b60545273467 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -48,7 +48,7 @@ 11 4.0.1 - 3.1.5 + 3.1.6 1.12.12 5.10.0 3.9.5 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 48814923bfe7a..ac055703db66e 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -62,7 +62,7 @@ 3.1.2 ${project.version} 25 - 3.1.5 + 3.1.6 2.0.2 4.2.0
From 4690f4eea2dc3be4d56e77f697e43d910d1b7968 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 29 Nov 2023 14:42:08 +0200 Subject: [PATCH 081/212] Stop disabling unsafe in netty at native-executable runtime Resolves issues unveiled with https://github.com/quarkusio/quarkus/pull/37347 (cherry picked from commit de181ea16dad09e4f74d298895db7d1dfda63dbf) --- .../infinispan/client/deployment/InfinispanClientProcessor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index 58201a89f0b4f..7e11774606f90 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -205,7 +205,6 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(InfinispanClientName.class).build()); additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(Remote.class).build()); - systemProperties.produce(new SystemPropertyBuildItem("io.netty.noUnsafe", "true")); hotDeployment .produce(new HotDeploymentWatchedFileBuildItem(META_INF + File.separator + DEFAULT_HOTROD_CLIENT_PROPERTIES)); From a13fa0161fcb7c74cbbf6e9ac046aaceeaca0eed Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 29 Nov 2023 11:38:26 +0100 Subject: [PATCH 082/212] Update MAX_LTS_SUPPORTED_BY_KOTLIN to 21 Kotlin 1.9.20 supports Java 21. (cherry picked from commit 01505b4840be56ac7f43d1ad17f2cdf5f3e73bf5) --- .../src/main/java/io/quarkus/devtools/project/JavaVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java index ffa49e92acafb..109133f72a509 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/JavaVersion.java @@ -64,7 +64,7 @@ public String toString() { // ordering is important here, so let's keep them ordered public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17, 21)); public static final int DEFAULT_JAVA_VERSION = 11; - public static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 17; + public static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 21; public static final String DETECT_JAVA_RUNTIME_VERSION = "<>"; public static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\..*)?"); From d948b487dd27e189938a7a720a0435ba9998d5ca Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 29 Nov 2023 16:07:24 +0100 Subject: [PATCH 083/212] Disable new Http2RSTFloodProtectionConfigTest on Windows We already disabled Http2RSTFloodProtectionTest on Windows and this new tests suffers from the same issues. (cherry picked from commit 041a3cb57354b244fa4a42042a161f78007ef5f3) --- .../vertx/http/http2/Http2RSTFloodProtectionConfigTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java index 5f4091a2b7097..c3abccb5d5512 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/http2/Http2RSTFloodProtectionConfigTest.java @@ -14,6 +14,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusUnitTest; @@ -29,6 +31,7 @@ /** * Configuration of the RST flood protection (CVE-2023-44487) */ +@DisabledOnOs(OS.WINDOWS) public class Http2RSTFloodProtectionConfigTest { @TestHTTPResource(value = "/ping", ssl = true) From 41544630e9ebd29221c18338a691e196ef2e2343 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 29 Nov 2023 18:26:04 +0100 Subject: [PATCH 084/212] Revert "Build cache - Upload quarkus-ide-launcher-999-SNAPSHOT.jar" This reverts commit b259b9a468bfab3fdcfe0cfd6d3584a98396b4cc. (cherry picked from commit c4e6eb753a943a1cff6021f968f19065277330e7) --- .github/workflows/ci-actions-incremental.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 92b758f0d09ef..c4e8f1af7e5a2 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -398,12 +398,6 @@ jobs: uses: gradle/github-actions/maven-build-scan/save@v1-beta with: job-name: "JVM Tests - JDK ${{matrix.java.name}}" - - name: Upload quarkus-ide-launcher jar - uses: actions/upload-artifact@v3 - with: - name: "quarkus-ide-launcher-999-SNAPSHOT.jar - JDK ${{matrix.java.name}}" - path: | - core/launcher/target/quarkus-ide-launcher-999-SNAPSHOT.jar maven-tests: name: Maven Tests - JDK ${{matrix.java.name}} From 27370c66ff488d5b53ea4b97e81c8d37841fe617 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 29 Nov 2023 19:03:29 +0100 Subject: [PATCH 085/212] Only download the builder image once for a given CI job We don't cache the images from job to job so we can download it once at the beginning using a `missing` policy and then avoid downloading it. (cherry picked from commit 92b51f35ca75497a891d5889e386c53831c0a98f) --- integration-tests/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 8e027683779b5..1ad49073f3b20 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -470,6 +470,17 @@ + + native-ci + + + env.CI + + + + missing + + From a6baf92fa1f212a4010500af169510bb399526e7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 29 Nov 2023 19:04:26 +0100 Subject: [PATCH 086/212] Retry downloading the builder image once after 5 seconds Might help with the current Quay.io reliability that we have, be it due to GitHub Actions or Quay.io network issues. (cherry picked from commit 4be6bb26494c5f8bfa453ab7d82c91ff6789e4d8) --- .../NativeImageBuildContainerRunner.java | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java index d826c15c68113..61a93dd0e55c2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java @@ -49,8 +49,8 @@ public void setup(boolean processInheritIODisabled) { // will appear to block and no output will be shown String effectiveBuilderImage = nativeConfig.builderImage().getEffectiveImage(); var builderImagePull = nativeConfig.builderImage().pull(); - log.infof("Checking status of builder image '%s'", effectiveBuilderImage); if (builderImagePull != NativeConfig.ImagePullStrategy.ALWAYS) { + log.infof("Checking status of builder image '%s'", effectiveBuilderImage); Process imageInspectProcess = null; try { final ProcessBuilder pb = new ProcessBuilder( @@ -82,20 +82,37 @@ public void setup(boolean processInheritIODisabled) { } } } - Process pullProcess = null; + try { - final ProcessBuilder pb = new ProcessBuilder( - Arrays.asList(containerRuntime.getExecutableName(), "pull", effectiveBuilderImage)); - pullProcess = ProcessUtil.launchProcess(pb, processInheritIODisabled); - if (pullProcess.waitFor() != 0) { - throw new RuntimeException("Failed to pull builder image '" + effectiveBuilderImage + "'"); - } - } catch (IOException | InterruptedException e) { - throw new RuntimeException("Failed to pull builder image '" + effectiveBuilderImage + "'", e); - } finally { - if (pullProcess != null) { - pullProcess.destroy(); + log.infof("Pulling builder image '%s'", effectiveBuilderImage); + pull(effectiveBuilderImage, processInheritIODisabled); + } catch (Exception e) { + log.infof("Retrying in 5 seconds"); + try { + Thread.sleep(5_000L); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); } + log.infof("Pulling builder image '%s' (take 2)", effectiveBuilderImage); + pull(effectiveBuilderImage, processInheritIODisabled); + } + } + } + + private void pull(String effectiveBuilderImage, boolean processInheritIODisabled) { + Process pullProcess = null; + try { + final ProcessBuilder pb = new ProcessBuilder( + Arrays.asList(containerRuntime.getExecutableName(), "pull", effectiveBuilderImage)); + pullProcess = ProcessUtil.launchProcess(pb, processInheritIODisabled); + if (pullProcess.waitFor() != 0) { + throw new RuntimeException("Failed to pull builder image '" + effectiveBuilderImage + "'"); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Failed to pull builder image '" + effectiveBuilderImage + "'"); + } finally { + if (pullProcess != null) { + pullProcess.destroy(); } } } From 39fcc661f592edb47658008e61f2f5d94fd7ece2 Mon Sep 17 00:00:00 2001 From: "alexey.kovynev" Date: Wed, 29 Nov 2023 18:24:58 +0200 Subject: [PATCH 087/212] Update Gradle to 8.5 (cherry picked from commit fa96ddba9d5d46423b6c7cc1d7b578b599b116b6) --- build-parent/pom.xml | 2 +- devtools/gradle/gradle/wrapper/gradle-wrapper.properties | 4 ++-- independent-projects/bootstrap/pom.xml | 2 +- .../devtools-testing/src/main/resources/fake-catalog.json | 2 +- independent-projects/tools/pom.xml | 2 +- .../gradle/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ac589a8b8e299..1866362b5538f 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -64,7 +64,7 @@ 3.9.5 3.2.0 - 8.4 + 8.5 ${project.version} ${project.version} 3.8.1 diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties index 82b3bd91387cb..80f3d5675f491 100644 --- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists # https://gradle.org/release-checksums/ -distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionSha256Sum=c16d517b50dd28b3f5838f0e844b7520b8f1eb610f2f29de7e4e04a1b7c9c79b +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 766e2e9e5d7ad..d966792760d95 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -78,7 +78,7 @@ 3.5.1 2.1.2 1.3.2 - 8.4 + 8.5 0.0.9 0.1.3 2.23.0 diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index 3b970eaec4e5a..281043c650de9 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -390,7 +390,7 @@ "recommended-java-version": "17", "proposed-maven-version": "3.9.5", "maven-wrapper-version": "3.2.0", - "gradle-wrapper-version": "8.4" + "gradle-wrapper-version": "8.5" } }, "codestarts-artifacts": [ diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index ac055703db66e..e991dd082c987 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -42,7 +42,7 @@ 3.9.5 3.2.0 - 8.4 + 8.5 3.11.0 diff --git a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties index 82b3bd91387cb..80f3d5675f491 100644 --- a/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/integration-tests/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists # https://gradle.org/release-checksums/ -distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionSha256Sum=c16d517b50dd28b3f5838f0e844b7520b8f1eb610f2f29de7e4e04a1b7c9c79b +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 2e0ca065a243651851792c7b8da09b3a1f6196fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gro=C3=9Fewinkelmann?= Date: Thu, 30 Nov 2023 11:49:12 +0100 Subject: [PATCH 088/212] Fix typo configMapRefKey -> configMapKeyRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Großewinkelmann (cherry picked from commit 4787a8a012d87fccea25ca37387a9eb5282dea10) --- docs/src/main/asciidoc/deploying-to-kubernetes.adoc | 2 +- docs/src/main/asciidoc/deploying-to-openshift.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc index 2b85072b421e2..b634f56f08b47 100644 --- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc +++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc @@ -506,7 +506,7 @@ This would generate the following in the `env` section of your container: - env: - name: FOO valueFrom: - configMapRefKey: + configMapKeyRef: key: keyName name: my-configmap optional: false diff --git a/docs/src/main/asciidoc/deploying-to-openshift.adoc b/docs/src/main/asciidoc/deploying-to-openshift.adoc index 268d699a4b74a..93496d901bb5e 100644 --- a/docs/src/main/asciidoc/deploying-to-openshift.adoc +++ b/docs/src/main/asciidoc/deploying-to-openshift.adoc @@ -367,7 +367,7 @@ This would generate the following in the `env` section of your container: - env: - name: FOO valueFrom: - configMapRefKey: + configMapKeyRef: key: keyName name: my-configmap optional: false From fe6f3dfd7dca838c92666a76b55cd265eaf5d79e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:12:42 +0100 Subject: [PATCH 089/212] quarkus-update - Improve cleanup of log lines (cherry picked from commit 30e10ff9f9bbcdd9c64aab08e504b3679fbc9c9a) --- .../project/update/rewrite/QuarkusUpdateCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index b7ea74069b464..9c2bea126433f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -351,13 +351,13 @@ private String clean(String line) { return line; } - String pattern = "[" + name() + "]"; + String pattern = "[" + name() + "] "; - if (line.length() < pattern.length()) { + if (!line.startsWith(pattern)) { return line; } - return line.substring(pattern.length()).trim(); + return line.substring(pattern.length()); } private boolean matches(String line) { From 81c217f28b1c1c79bd4b68b5e4a47455d5ab9640 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:13:11 +0100 Subject: [PATCH 090/212] quarkus update - Remove two unused methods (cherry picked from commit 397ca66e6aca443a950ed97f047bdaef24f816d2) --- .../project/update/rewrite/QuarkusUpdateCommand.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index 9c2bea126433f..0fafb6b0adcaa 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -311,14 +311,6 @@ public static boolean isWindows() { return OS.contains("win"); } - static boolean hasGradle(Path dir) { - return Files.exists(dir.resolve("build.gradle")); - } - - private static boolean hasMaven(Path dir) { - return Files.exists(dir.resolve("pom.xml")); - } - private enum LogLevel { ERROR, From f5347f78cf6a4b67a84270e4bb0582ab69bf9a38 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:15:53 +0100 Subject: [PATCH 091/212] quarkus update - Fix Windows detection (cherry picked from commit e3ba23c573222508462679b72cf5939fe992b0f6) --- independent-projects/tools/devtools-common/pom.xml | 4 ++++ .../project/update/rewrite/QuarkusUpdateCommand.java | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/independent-projects/tools/devtools-common/pom.xml b/independent-projects/tools/devtools-common/pom.xml index a8dfd837041f7..2e5832fddb6af 100644 --- a/independent-projects/tools/devtools-common/pom.xml +++ b/independent-projects/tools/devtools-common/pom.xml @@ -50,6 +50,10 @@ io.smallrye.common smallrye-common-version
+ + io.smallrye.common + smallrye-common-os + io.fabric8 maven-model-helper diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index 0fafb6b0adcaa..c8a5ae63fb8f1 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -24,6 +24,7 @@ import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; import io.quarkus.qute.Qute; +import io.smallrye.common.os.OS; public class QuarkusUpdateCommand { @@ -305,10 +306,8 @@ private static boolean isExecutable(Path file) { return false; } - private static String OS = System.getProperty("os.name").toLowerCase(); - public static boolean isWindows() { - return OS.contains("win"); + return OS.WINDOWS.isCurrent(); } private enum LogLevel { From f96b56d015e9633421f5a5e36848eaf07898b9ce Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:17:30 +0100 Subject: [PATCH 092/212] quarkus update - Add a clean in mvn process-sources We need to clean the project before the next compilation as things have changed a lot (e.g. in the case of a Quarkus 2.x -> 3.x update). (cherry picked from commit c6ad1fb15c51da32e9ccf242c5628c65ba05221c) --- .../update/rewrite/QuarkusUpdateCommand.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index c8a5ae63fb8f1..cde516b239d60 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -58,6 +58,16 @@ public static void handle(MessageWriter log, BuildTool buildTool, Path baseDir, } } + private static void runMavenUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, + Path recipe, + boolean dryRun) { + final String mvnBinary = findMvnBinary(baseDir); + executeCommand(baseDir, getMavenUpdateCommand(mvnBinary, rewritePluginVersion, recipesGAV, recipe, dryRun), log); + + // format the sources + executeCommand(baseDir, getMavenProcessSourcesCommand(mvnBinary), log); + } + private static void runGradleUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, Path recipe, boolean dryRun) { Path tempInit = null; @@ -120,19 +130,10 @@ private static void propagateSystemPropertyIfSet(String name, List comma } } - private static void runMavenUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, - Path recipe, - boolean dryRun) { - final String mvnBinary = findMvnBinary(baseDir); - executeCommand(baseDir, getMavenUpdateCommand(mvnBinary, rewritePluginVersion, recipesGAV, recipe, dryRun), log); - - // format the sources - executeCommand(baseDir, getMavenProcessSourcesCommand(mvnBinary), log); - } - private static List getMavenProcessSourcesCommand(String mvnBinary) { List command = new ArrayList<>(); command.add(mvnBinary); + command.add("clean"); command.add("process-sources"); final String mavenSettings = getMavenSettingsArg(); if (mavenSettings != null) { From 04c784fe2955475997577035ebe86627fba986dd Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 14:41:29 +0100 Subject: [PATCH 093/212] quarkus update - Execute Maven commands in batch mode (cherry picked from commit 553c361aa8559f3aca563ff6c2bee8bfa1b4dd11) --- .../devtools/project/update/rewrite/QuarkusUpdateCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index cde516b239d60..f53c3def02dd4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -133,6 +133,7 @@ private static void propagateSystemPropertyIfSet(String name, List comma private static List getMavenProcessSourcesCommand(String mvnBinary) { List command = new ArrayList<>(); command.add(mvnBinary); + command.add("-B"); command.add("clean"); command.add("process-sources"); final String mavenSettings = getMavenSettingsArg(); @@ -148,6 +149,7 @@ private static List getMavenUpdateCommand(String mvnBinary, String rewri boolean dryRun) { final List command = new ArrayList<>(); command.add(mvnBinary); + command.add("-B"); command.add("-e"); command.add( String.format("%s:%s:%s:%s", MAVEN_REWRITE_PLUGIN_GROUP, MAVEN_REWRITE_PLUGIN_ARTIFACT, rewritePluginVersion, From 092fb9c8137b37acbc9d0a8b9ded620a08a426cf Mon Sep 17 00:00:00 2001 From: Alexei Bratuhin Date: Wed, 29 Nov 2023 15:33:15 +0100 Subject: [PATCH 094/212] #37390 = respect comma in property value for @RolesAllowed using double-escape (cherry picked from commit b045e662139dd8a1281763091aa2e29e725ec271) --- .../rolesallowed/RolesAllowedExpressionTest.java | 12 +++++++++++- .../security/runtime/SecurityCheckRecorder.java | 12 +++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java index 7af69cfa4c78e..f29bc1f0c6f4e 100644 --- a/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java +++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/rolesallowed/RolesAllowedExpressionTest.java @@ -40,7 +40,8 @@ public class RolesAllowedExpressionTest { "%test.test-profile-admin=admin\n" + "missing-profile-profile-admin=superman\n" + "%missing-profile.missing-profile-profile-admin=admin\n" + - "all-roles=Administrator,Software,Tester,User\n"; + "all-roles=Administrator,Software,Tester,User\n" + + "ldap-roles=cn=Administrator\\\\,ou=Software\\\\,dc=Tester\\\\,dc=User\n"; @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() @@ -90,6 +91,10 @@ public void shouldRestrictAccessToSpecificRole() { assertSuccess(() -> bean.list(), "list", new AuthData(Set.of("Administrator", "Software", "Tester", "User"), false, "list")); assertFailureFor(() -> bean.list(), ForbiddenException.class, ADMIN); + + // property expression with escaped collection separator should not be treated as list + assertSuccess(() -> bean.ldap(), "ldap", + new AuthData(Set.of("cn=Administrator,ou=Software,dc=Tester,dc=User"), false, "ldap")); } @Singleton @@ -141,6 +146,11 @@ public final String list() { return "list"; } + @RolesAllowed("${ldap-roles}") + public final String ldap() { + return "ldap"; + } + } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java index ee6639d2ef495..8661d06f3f4bd 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java @@ -98,15 +98,17 @@ public String[] get() { // @RolesAllowed({"${my.roles}"}) => my.roles=one,two <=> @RolesAllowed({"one", "two"}) if (strVal != null && strVal.contains(",")) { var strArr = StringUtil.split(strVal); - if (strArr.length > 1) { + if (strArr.length >= 1) { // role order is irrelevant as logical operator between them is OR - // first role will go to the original place + // first role will go to the original place, double escaped comma will be parsed correctly strVal = strArr[0]; - // the rest of the roles will be appended at the end - for (int i1 = 1; i1 < strArr.length; i1++) { - roles.add(strArr[i1]); + if (strArr.length > 1) { + // the rest of the roles will be appended at the end + for (int i1 = 1; i1 < strArr.length; i1++) { + roles.add(strArr[i1]); + } } } } From b9964f513eea74baaf129c7a785137cde0dc8b9c Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 30 Nov 2023 10:44:12 +0100 Subject: [PATCH 095/212] Scheduler: register ApplicationNotRunning as bean even if quartz is used - fixes #37417 (cherry picked from commit 3b44cde7a3ab5136115ca13eeb00a71cf69dd2bf) --- .../ApplicationNotRunningPredicateTest.java | 57 +++++++++++++++++++ .../deployment/SchedulerProcessor.java | 3 +- .../ApplicationNotRunningPredicateTest.java | 10 ++-- 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ApplicationNotRunningPredicateTest.java diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ApplicationNotRunningPredicateTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ApplicationNotRunningPredicateTest.java new file mode 100644 index 0000000000000..8cb4bcda0915d --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ApplicationNotRunningPredicateTest.java @@ -0,0 +1,57 @@ +package io.quarkus.quartz.test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.enterprise.event.Observes; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.scheduler.FailedExecution; +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.SuccessfulExecution; +import io.quarkus.test.QuarkusUnitTest; + +public class ApplicationNotRunningPredicateTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().withApplicationRoot((jar) -> jar.addClasses(Jobs.class)); + + static final CountDownLatch SUCCESS_LATCH = new CountDownLatch(1); + static volatile FailedExecution failedExecution; + + @Test + public void testTriggerErrorStatus() throws InterruptedException { + assertTrue(SUCCESS_LATCH.await(5, TimeUnit.SECONDS)); + assertNull(failedExecution); + } + + void observeSuccessfulExecution(@Observes SuccessfulExecution successfulExecution) { + SUCCESS_LATCH.countDown(); + } + + void observeFailedExecution(@Observes FailedExecution failedExecution) { + ApplicationNotRunningPredicateTest.failedExecution = failedExecution; + } + + static class Jobs { + + volatile boolean started; + + void started(@Observes StartupEvent event) { + started = true; + } + + @Scheduled(every = "0.2s", skipExecutionIf = Scheduled.ApplicationNotRunning.class) + void scheduleAfterStarted() { + if (!started) { + throw new IllegalStateException(); + } + } + } +} diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java index 60196c55e4190..ad9c8176b9ca3 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java @@ -98,8 +98,9 @@ public class SchedulerProcessor { @BuildStep void beans(Capabilities capabilities, BuildProducer additionalBeans) { + additionalBeans.produce(new AdditionalBeanBuildItem(Scheduled.ApplicationNotRunning.class)); if (capabilities.isMissing(Capability.QUARTZ)) { - additionalBeans.produce(new AdditionalBeanBuildItem(SimpleScheduler.class, Scheduled.ApplicationNotRunning.class)); + additionalBeans.produce(new AdditionalBeanBuildItem(SimpleScheduler.class)); } } diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ApplicationNotRunningPredicateTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ApplicationNotRunningPredicateTest.java index 6318d8ecb0d31..c7b246ebe2b0b 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ApplicationNotRunningPredicateTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ApplicationNotRunningPredicateTest.java @@ -6,9 +6,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.annotation.Priority; import jakarta.enterprise.event.Observes; -import jakarta.interceptor.Interceptor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -43,15 +41,15 @@ void observeFailedExecution(@Observes FailedExecution failedExecution) { static class Jobs { - volatile boolean preStart; + volatile boolean started; - void started(@Observes @Priority(Interceptor.Priority.PLATFORM_BEFORE) StartupEvent event) { - preStart = true; + void started(@Observes StartupEvent event) { + started = true; } @Scheduled(every = "0.2s", skipExecutionIf = Scheduled.ApplicationNotRunning.class) void scheduleAfterStarted() { - if (!preStart) { + if (!started) { throw new IllegalStateException(); } } From 703c9f8b735873c1454db885ba8840aaf1f8c0ba Mon Sep 17 00:00:00 2001 From: Michael Rasmussen Date: Fri, 1 Dec 2023 05:02:28 +0200 Subject: [PATCH 096/212] Add Content-Range header to 206 Partial Content file response (cherry picked from commit 1a9e2e7d321ed6f62a8648d2ec98b96412f0936b) --- .../server/test/providers/FileTestCase.java | 20 ++++++++++++++++ .../serialisers/ServerFileBodyHandler.java | 23 +++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java index 80f99ddf1105d..a7feae8824879 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/FileTestCase.java @@ -45,17 +45,37 @@ public void testFiles() throws Exception { .then() .statusCode(206) .header(HttpHeaders.CONTENT_LENGTH, "10") + .header("Content-Range", "bytes 0-9/" + contentLength) .body(Matchers.equalTo(content.substring(0, 10))); RestAssured.given().header("Range", "bytes=10-19").get("/providers/file/file") .then() .statusCode(206) .header(HttpHeaders.CONTENT_LENGTH, "10") + .header("Content-Range", "bytes 10-19/" + contentLength) .body(Matchers.equalTo(content.substring(10, 20))); RestAssured.given().header("Range", "bytes=10-").get("/providers/file/file") .then() .statusCode(206) .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(content.length() - 10)) + .header("Content-Range", "bytes 10-" + (content.length() - 1) + "/" + contentLength) .body(Matchers.equalTo(content.substring(10))); + RestAssured.given().header("Range", "bytes=-10").get("/providers/file/file") + .then() + .statusCode(206) + .header(HttpHeaders.CONTENT_LENGTH, "10") + .header("Content-Range", + "bytes " + (content.length() - 10) + "-" + (content.length() - 1) + "/" + contentLength) + .body(Matchers.equalTo(content.substring((content.length() - 10)))); + RestAssured.given().header("Range", "bytes=" + (content.length() + 1) + "-").get("/providers/file/file") + .then() + .statusCode(200) + .header(HttpHeaders.CONTENT_LENGTH, contentLength) + .body(Matchers.equalTo(content)); + RestAssured.given().header("Range", "bytes=0-1, 3-4").get("/providers/file/file") + .then() + .statusCode(200) + .header(HttpHeaders.CONTENT_LENGTH, contentLength) + .body(Matchers.equalTo(content)); RestAssured.get("/providers/file/file-partial") .then() .statusCode(200) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java index bd33ed659fda6..31158a025c3b4 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/providers/serialisers/ServerFileBodyHandler.java @@ -42,16 +42,25 @@ static void sendFile(File file, ServerRequestContext context) { ResteasyReactiveRequestContext ctx = ((ResteasyReactiveRequestContext) context); Object rangeObj = ctx.getHeader("Range", true); ByteRange byteRange = rangeObj == null ? null : ByteRange.parse(rangeObj.toString()); + long fileLength = file.length(); if ((byteRange != null) && (byteRange.ranges.size() == 1)) { ByteRange.Range range = byteRange.ranges.get(0); - long length = range.getEnd() == -1 ? Long.MAX_VALUE : range.getEnd() - range.getStart() + 1; - context.serverResponse() - .setStatusCode(Response.Status.PARTIAL_CONTENT.getStatusCode()) - .sendFile(file.getAbsolutePath(), range.getStart(), length); - } else { - context.serverResponse().sendFile(file.getAbsolutePath(), 0, file.length()); + ByteRange.Range fileRange = (range.getStart() == -1) + ? new ByteRange.Range(fileLength - range.getEnd(), fileLength - 1) + : new ByteRange.Range(range.getStart(), Math.min(fileLength - 1, range.getEnd())); + + if ((fileRange.getStart() >= 0) && (fileRange.getStart() <= fileRange.getEnd())) { + String contentRange = "bytes " + fileRange.getStart() + "-" + fileRange.getEnd() + "/" + fileLength; + long length = fileRange.getEnd() - fileRange.getStart() + 1; + context.serverResponse() + .setStatusCode(Response.Status.PARTIAL_CONTENT.getStatusCode()) + .setResponseHeader("Content-Range", contentRange) + .sendFile(file.getAbsolutePath(), fileRange.getStart(), length); + return; + } } + context.serverResponse().sendFile(file.getAbsolutePath(), 0, fileLength); } /** @@ -138,7 +147,7 @@ public static ByteRange parse(String rangeHeader) { if (index + 1 < part.length()) { end = Long.parseLong(part.substring(index + 1)); } else { - end = -1; + end = Long.MAX_VALUE; } ranges.add(new Range(start, end)); } From cdbc6d66d8df1abe0e244406fdde0daad0c53175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Dec 2023 14:32:12 +0100 Subject: [PATCH 097/212] Test access to fields of @Immutable embeddables (cherry picked from commit 79e3b19ec13360b305fa6941516559e325faa3dd) --- .../ImmutableEmbeddableFieldAccessTest.java | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java new file mode 100644 index 0000000000000..123a97aa63ef5 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java @@ -0,0 +1,193 @@ +package io.quarkus.hibernate.orm.applicationfieldaccess; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Immutable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Checks that access to record fields or record getters by the application works correctly. + */ +public class ImmutableEmbeddableFieldAccessTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(MyEntity.class) + .addClasses(MyImmutableEmbeddableWithFieldAccess.class) + .addClasses(MyImmutableEmbeddableWithAccessors.class) + .addClass(AccessDelegate.class)) + .withConfigurationResource("application.properties"); + + @Inject + EntityManager em; + + @Test + public void immutableEmbeddableWithoutAdditionalGetters_field() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithFieldAccess(); + embedded.value = value; + entity.embeddedWithoutAccessors = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithoutAccessors == null ? null : entity.embeddedWithoutAccessors.value; + } + }); + } + + @Test + public void immutableEmbeddableWithAdditionalGetters_field() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithAccessors(); + // Assuming this is changed only once on initialization, + // which is the only way the @Immutable annotation would make sense. + embedded.value = value; + entity.embeddedWithFieldAccess = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithFieldAccess == null ? null : entity.embeddedWithFieldAccess.value; + } + }); + } + + @Test + public void immutableEmbeddableWithAccessors() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithAccessors(); + // Assuming this is changed only once on initialization, + // which is the only way the @Immutable annotation would make sense. + embedded.setValue(value); + entity.embeddedWithFieldAccess = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithFieldAccess == null ? null : entity.embeddedWithFieldAccess.getValue(); + } + }); + } + + // Ideally we'd make this a @ParameterizedTest and pass the access delegate as parameter, + // but we cannot do that due to JUnit using a different classloader than the test. + private void doTestFieldAccess(AccessDelegate delegate) { + Long id = QuarkusTransaction.disallowingExisting().call(() -> { + var entity = new MyEntity(); + em.persist(entity); + return entity.id; + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.find(MyEntity.class, id); + assertThat(delegate.getValue(entity)) + .as("Loaded value before update") + .isNull(); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.getReference(MyEntity.class, id); + // Since field access is replaced with accessor calls, + // we expect this change to be detected by dirty tracking and persisted. + delegate.setValue(entity, 42L); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.find(MyEntity.class, id); + // We're working on an initialized entity. + assertThat(entity) + .as("find() should return uninitialized entity") + .returns(true, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + assertThat(delegate.getValue(entity)) + .as("Loaded value after update") + .isEqualTo(42L); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.getReference(MyEntity.class, id); + // We're working on an uninitialized entity. + assertThat(entity) + .as("getReference() should return uninitialized entity") + .returns(false, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + assertThat(delegate.getValue(entity)) + .as("Lazily loaded value after update") + .isEqualTo(42L); + // Accessing the value should trigger initialization of the entity. + assertThat(entity) + .as("Getting the value should initialize the entity") + .returns(true, Hibernate::isInitialized); + }); + } + + @Entity(name = "myentity") + public static class MyEntity { + @Id + @GeneratedValue + public long id; + @Embedded + @AttributeOverride(name = "value", column = @Column(name = "value1")) + public MyImmutableEmbeddableWithAccessors embeddedWithFieldAccess; + @Embedded + @AttributeOverride(name = "value", column = @Column(name = "value2")) + public MyImmutableEmbeddableWithFieldAccess embeddedWithoutAccessors; + } + + @Immutable + @Embeddable + public static class MyImmutableEmbeddableWithFieldAccess { + public Long value; + + public MyImmutableEmbeddableWithFieldAccess() { + } + } + + @Immutable + @Embeddable + public static class MyImmutableEmbeddableWithAccessors { + private Long value; + + // For Hibernate ORM instantiation + protected MyImmutableEmbeddableWithAccessors() { + } + + public Long getValue() { + return value; + } + + // For Hibernate ORM instantiation + protected void setValue(Long value) { + this.value = value; + } + } + + private interface AccessDelegate { + void setValue(MyEntity entity, Long value); + + Long getValue(MyEntity entity); + } +} From 78a823c441bb23c46f0e2b84de1e4732b743eebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Dec 2023 14:41:10 +0100 Subject: [PATCH 098/212] Fix Panache bytecode enhancement for @Embeddable records (cherry picked from commit 1b426fc415832a026d0f3806f685f50034bf6359) --- .../deployment/PanacheHibernateCommonResourceProcessor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java b/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java index 93037abea56c9..6127afcc2955b 100644 --- a/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java +++ b/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java @@ -157,9 +157,12 @@ private EntityModel createEntityModel(ClassInfo classInfo) { // so we need to be careful when we enhance private fields, // because the corresponding `$_hibernate_{read/write}_*()` methods // will only be generated for classes mapped through *annotations*. - boolean willBeEnhancedByHibernateOrm = classInfo.hasAnnotation(DOTNAME_ENTITY) + boolean isManaged = classInfo.hasAnnotation(DOTNAME_ENTITY) || classInfo.hasAnnotation(DOTNAME_MAPPED_SUPERCLASS) || classInfo.hasAnnotation(DOTNAME_EMBEDDABLE); + boolean willBeEnhancedByHibernateOrm = isManaged + // Records are immutable, thus never enhanced + && !classInfo.isRecord(); for (FieldInfo fieldInfo : classInfo.fields()) { String name = fieldInfo.name(); if (!Modifier.isStatic(fieldInfo.flags()) From 83a3e5110ff153b8c7e03392f34ea95d6dc78231 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 11 Oct 2023 15:07:28 +0300 Subject: [PATCH 099/212] Make Truffle from GraalVM 23.1 work in all Quarkus modes Fixes: #36242 (cherry picked from commit 738a24d70ed9e7ec3cdeddc3cb8fb7a2d589b75d) --- .../SetClassPathSystemPropBuildItem.java | 12 +++++ .../steps/ClassPathSystemPropBuildStep.java | 53 +++++++++++++++++++ .../ClassPathSystemPropertyRecorder.java | 11 ++++ 3 files changed, 76 insertions(+) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/SetClassPathSystemPropBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SetClassPathSystemPropBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SetClassPathSystemPropBuildItem.java new file mode 100644 index 0000000000000..7461e22f7a73f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SetClassPathSystemPropBuildItem.java @@ -0,0 +1,12 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A marker build item to make Quarkus set the {@code java.class.path} system property. + * This system property is used in rare by libraries (Truffle for example) to create their own ClassLoaders. + * The value of the system property is simply best effort, as there is no way to faithfully represent + * the Quarkus ClassLoader hierarchies in a system property value. + */ +public final class SetClassPathSystemPropBuildItem extends MultiBuildItem { +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java new file mode 100644 index 0000000000000..feda25cbade14 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassPathSystemPropBuildStep.java @@ -0,0 +1,53 @@ +package io.quarkus.deployment.steps; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.SetClassPathSystemPropBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.runtime.ClassPathSystemPropertyRecorder; + +public class ClassPathSystemPropBuildStep { + + @BuildStep + public void produce(BuildProducer producer, CurateOutcomeBuildItem curateOutcome) { + boolean truffleUsed = curateOutcome.getApplicationModel().getDependencies().stream() + .anyMatch(d -> d.getGroupId().equals("org.graalvm.polyglot")); + if (truffleUsed) { + producer.produce(new SetClassPathSystemPropBuildItem()); + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + public void set(List setCPItems, + CurateOutcomeBuildItem curateOutcome, + ClassPathSystemPropertyRecorder recorder) { + if (setCPItems.isEmpty()) { + return; + } + Collection runtimeDependencies = curateOutcome.getApplicationModel().getRuntimeDependencies(); + List parentFirst = new ArrayList<>(); + List regular = new ArrayList<>(); + for (ResolvedDependency dependency : runtimeDependencies) { + if (dependency.isClassLoaderParentFirst()) { + parentFirst.addAll(dependency.getContentTree().getRoots()); + } else { + regular.addAll(dependency.getContentTree().getRoots()); + + } + } + String classPathValue = Stream.concat(parentFirst.stream(), regular.stream()).map(p -> p.toAbsolutePath().toString()) + .collect(Collectors.joining(":")); + recorder.set(classPathValue); + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java new file mode 100644 index 0000000000000..fdca4fcb0cb65 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ClassPathSystemPropertyRecorder.java @@ -0,0 +1,11 @@ +package io.quarkus.runtime; + +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class ClassPathSystemPropertyRecorder { + + public void set(String value) { + System.setProperty("java.class.path", value); + } +} From 0719a47df6c6c98efb77769f1e06fbc8fb7f7ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sol=C3=B3rzano?= Date: Fri, 1 Dec 2023 12:49:20 +0100 Subject: [PATCH 100/212] Update Apache Maven to 3.9.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jorge Solórzano (cherry picked from commit 51ac094d2cb67670c95d76b395816d2f57ef0272) --- build-parent/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 8 ++++---- independent-projects/enforcer-rules/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 4 ++-- independent-projects/resteasy-reactive/pom.xml | 2 +- .../devtools-testing/src/main/resources/fake-catalog.json | 2 +- independent-projects/tools/pom.xml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 1866362b5538f..14eeb01c05c7a 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -62,7 +62,7 @@ [3.8.2,) - 3.9.5 + 3.9.6 3.2.0 8.5 ${project.version} diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index d966792760d95..42aa44afb02e2 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -47,10 +47,10 @@ 0.9.5 3.5.3.Final 5.10.0 - 3.9.5 - 0.3.5 - 3.7.1 - 1.9.13 + 3.9.6 + 0.9.0.M2 + 3.10.2 + 1.9.18 3.3.4 3.5.3 4.4.16 diff --git a/independent-projects/enforcer-rules/pom.xml b/independent-projects/enforcer-rules/pom.xml index 3be3e3b40c112..b257ed3a2aebf 100644 --- a/independent-projects/enforcer-rules/pom.xml +++ b/independent-projects/enforcer-rules/pom.xml @@ -40,7 +40,7 @@ 3.0.0-M3 3.6.0 - 3.9.5 + 3.9.6 - 3.9.5 + 3.9.6 3.2.0 8.5 From 0469ec8c84ae8356f4dd8a3c5ed31f67d75f1e0a Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 29 Nov 2023 18:07:59 +0000 Subject: [PATCH 101/212] Print correct profiles in native image (cherry picked from commit 0a6c90239b218a47e5f3e802b3824ad630f92a6a) --- .../java/io/quarkus/deployment/steps/MainClassBuildStep.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index de47b02e92602..d181e33c5a1f7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -268,8 +268,6 @@ void build(List staticInitTasks, startupContext, mv.getMethodParam(0)); mv.invokeStaticMethod(CONFIGURE_STEP_TIME_ENABLED); - ResultHandle profiles = mv - .invokeStaticMethod(ofMethod(ConfigUtils.class, "getProfiles", List.class)); tryBlock = mv.tryBlock(); tryBlock.invokeStaticMethod(CONFIGURE_STEP_TIME_START); @@ -298,7 +296,7 @@ void build(List staticInitTasks, tryBlock.load(applicationInfo.getVersion()), tryBlock.load(Version.getVersion()), featuresHandle, - profiles, + tryBlock.invokeStaticMethod(ofMethod(ConfigUtils.class, "getProfiles", List.class)), tryBlock.load(LaunchMode.DEVELOPMENT.equals(launchMode.getLaunchMode())), tryBlock.load(launchMode.isAuxiliaryApplication())); From 58db82b31f50b9123b8c4e2c7f363cbc94c4ebd0 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 1 Dec 2023 15:03:51 +0100 Subject: [PATCH 102/212] Config: detect injected config value mismatch for missing values - resolves #37444 - follow-up of https://github.com/quarkusio/quarkus/pull/36281 (cherry picked from commit bd98d02bd6f3bfb81023f95842d3f9e73cc8df73) --- ...onfigInjectionMissingValueFailureTest.java | 58 +++++++++++++++++++ .../ConfigStaticInitCheckInterceptor.java | 3 +- .../arc/runtime/ConfigStaticInitValues.java | 5 +- 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionMissingValueFailureTest.java diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionMissingValueFailureTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionMissingValueFailureTest.java new file mode 100644 index 0000000000000..76ee3650b6ee7 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/staticinit/StaticInitConfigInjectionMissingValueFailureTest.java @@ -0,0 +1,58 @@ +package io.quarkus.arc.test.config.staticinit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Optional; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class StaticInitConfigInjectionMissingValueFailureTest { + + static final String PROPERTY_NAME = "static.init.missing.apfelstrudel"; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(StaticInitBean.class)) + .assertException(t -> { + assertThat(t).isInstanceOf(IllegalStateException.class) + .hasMessageContainingAll( + "A runtime config property value differs from the value that was injected during the static intialization phase", + "the runtime value of '" + PROPERTY_NAME + + "' is [gizmo] but the value [null] was injected into io.quarkus.arc.test.config.staticinit.StaticInitConfigInjectionMissingValueFailureTest$StaticInitBean#value"); + }); + + @Test + public void test() { + fail(); + } + + @Singleton + public static class StaticInitBean { + + @ConfigProperty(name = PROPERTY_NAME) + Optional value; + + // bean is instantiated during STATIC_INIT + void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) { + System.setProperty(PROPERTY_NAME, "gizmo"); + } + + } + + @AfterAll + static void afterAll() { + System.clearProperty(PROPERTY_NAME); + } +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheckInterceptor.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheckInterceptor.java index e02621937e7b8..dc06f683503a5 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheckInterceptor.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitCheckInterceptor.java @@ -69,8 +69,7 @@ static void recordConfigValue(InjectionPoint injectionPoint, ConfigStaticInitVal value = getDefaultValue(injectionPoint, configProperty); } if (value == null) { - LOG.debugf("No config value found for %s", propertyName); - return; + LOG.debugf("No config value found for %s - recording value", propertyName); } if (configValues == null) { configValues = Arc.container().instance(ConfigStaticInitValues.class).get(); diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java index f464416e7603f..821f458c0a265 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigStaticInitValues.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import jakarta.annotation.Priority; import jakarta.enterprise.event.Observes; @@ -43,7 +44,9 @@ void onStart(@Observes @Priority(Integer.MIN_VALUE) StartupEvent event) { List mismatches = new ArrayList<>(); for (InjectedValue injectedValue : injectedValues) { ConfigValue currentValue = config.getConfigValue(injectedValue.name); - if (currentValue.getValue() != null && !injectedValue.value.equals(currentValue.getValue())) { + if (currentValue.getValue() != null + && !Objects.equals(currentValue.getValue(), injectedValue.value)) { + // Config property is set at runtime and the value differs from the value injected during STATIC_INIT bootstrap phase mismatches.add( " - the runtime value of '" + injectedValue.name + "' is [" + currentValue.getValue() + "] but the value [" + injectedValue.value From d89dde3c5c365ab858c76cb5ecb0b2500591ff85 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 4 Dec 2023 13:19:56 +0100 Subject: [PATCH 103/212] Revert "Unlist quarkus-resteasy-qute and quarkus-resteasy-reactive-qute" This reverts commit 5e8e7e6db2b9707b33a401a450acc0fa75712b3f. This has been done too fast: - we haven't documented it at all - there are still references to these extensions in our documentation (https://quarkus.io/guides/qute-reference#resteasy_integration) and people can't find the extensions anymore (see https://github.com/quarkusio/quarkus/issues/37478) We need to finalize the documentation before actually unlisting these extensions. I would advise to target 3.7 for that. (cherry picked from commit 1db3889cd476ae1ed4c89cc0fc236dca8e143e0e) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 - .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 0da36a3fd354e..d0391e67d3f8b 100644 --- a/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -2,7 +2,6 @@ artifact: ${project.groupId}:${project.artifactId}:${project.version} name: "RESTEasy Classic Qute" metadata: - unlisted: true keywords: - "templating" - "templates" diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index eec8b27d0eb38..9f0006b6bf2fa 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,7 +1,6 @@ name: "RESTEasy Reactive Qute" artifact: ${project.groupId}:${project.artifactId}:${project.version} metadata: - unlisted: true keywords: - "templating" - "templates" From b324b35253fd6a5151dadd2b53f334b0241d32b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 23:06:05 +0000 Subject: [PATCH 104/212] Bump org.apache.commons:commons-compress from 1.24.0 to 1.25.0 Bumps org.apache.commons:commons-compress from 1.24.0 to 1.25.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-compress dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit 39e7c93136ebb1ce6a8249dff972f7846e693133) --- bom/application/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 3c37f3131f632..b0d2c7308462a 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -197,7 +197,7 @@ 2.1 4.7.5 1.1.0 - 1.24.0 + 1.25.0 1.11.0 2.10.1 1.1.1.Final diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 04c84fd510066..6aeadfa39d310 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -55,7 +55,7 @@ 2.15.3 4.0.1 5.10.0 - 1.24.0 + 1.25.0 3.5.3.Final 5.3.1 3.2.1 From b225ff4b6ee22c0b9646c0c1cbba82b67c9d8fc4 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 23 Nov 2023 21:00:11 +0000 Subject: [PATCH 105/212] Bump Keycloak version to 23.0.0 (cherry picked from commit 9c9f35a71f83f3aa34c32ec11c79a0e674cbc164) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- .../src/main/asciidoc/security-openid-connect-dev-services.adoc | 2 +- .../oidc/deployment/devservices/keycloak/DevServicesConfig.java | 2 +- .../devservices/keycloak/KeycloakDevServicesProcessor.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index b0d2c7308462a..1f5d4fb4c7d12 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -188,7 +188,7 @@ 5.8.0 4.10.1 2.0.2.Final - 22.0.5 + 23.0.0 1.15.1 3.38.0 2.22.0 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 14eeb01c05c7a..ae8c28c5ea334 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -101,7 +101,7 @@ - 22.0.5 + 23.0.0 19.0.3 quay.io/keycloak/keycloak:${keycloak.version} quay.io/keycloak/keycloak:${keycloak.wildfly.version}-legacy diff --git a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc index ee38209a69427..0d99374a5b348 100644 --- a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc @@ -225,7 +225,7 @@ For more information, see xref:security-oidc-bearer-token-authentication.adoc#in [[keycloak-initialization]] === Keycloak Initialization -The `quay.io/keycloak/keycloak:22.0.5` image which contains a Keycloak distribution powered by Quarkus is used to start a container by default. +The `quay.io/keycloak/keycloak:23.0.0` image which contains a Keycloak distribution powered by Quarkus is used to start a container by default. `quarkus.keycloak.devservices.image-name` can be used to change the Keycloak image name. For example, set it to `quay.io/keycloak/keycloak:19.0.3-legacy` to use a Keycloak distribution powered by WildFly. Note that only a Quarkus based Keycloak distribution is available starting from Keycloak `20.0.0`. diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java index 05a69f6df1015..fd3ee7e96b0a9 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java @@ -35,7 +35,7 @@ public class DevServicesConfig { * string. * Set 'quarkus.keycloak.devservices.keycloak-x-image' to override this check. */ - @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:22.0.5") + @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:23.0.0") public String imageName; /** diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 23ffebaa2e6a2..019a3e4fad943 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -108,7 +108,7 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_QUARKUS_HOSTNAME = "KC_HOSTNAME"; private static final String KEYCLOAK_QUARKUS_ADMIN_PROP = "KEYCLOAK_ADMIN"; private static final String KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP = "KEYCLOAK_ADMIN_PASSWORD"; - private static final String KEYCLOAK_QUARKUS_START_CMD = "start --storage=chm --http-enabled=true --hostname-strict=false --hostname-strict-https=false"; + private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false --hostname-strict-https=false"; private static final String JAVA_OPTS = "JAVA_OPTS"; private static final String OIDC_USERS = "oidc.users"; From f8912ab46c9a1ae907c8d9ee0d0db298daf1397f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 22:53:16 +0000 Subject: [PATCH 106/212] Bump org.jetbrains.kotlinx:kotlinx-serialization-json Bumps [org.jetbrains.kotlinx:kotlinx-serialization-json](https://github.com/Kotlin/kotlinx.serialization) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/Kotlin/kotlinx.serialization/releases) - [Changelog](https://github.com/Kotlin/kotlinx.serialization/blob/master/CHANGELOG.md) - [Commits](https://github.com/Kotlin/kotlinx.serialization/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-serialization-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 8d4b5c5e75d8790f6b3f49b63af2f6553b24c32f) --- 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 1f5d4fb4c7d12..c07efa0abb479 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -160,7 +160,7 @@ 1.9.21 1.7.3 0.27.0 - 1.6.0 + 1.6.1 4.0.3 3.2.0 4.2.0 From c912aad78a53148c380175857039f50a7fb9fcac Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 17 Nov 2023 15:39:27 +0100 Subject: [PATCH 107/212] Update Kotlinx Serialization JsonConfig (cherry picked from commit 1ce610cc3544c2255dd7387bbf4ce0cba47ae344) --- .../kotlin/serialization/common/runtime/JsonConfig.java | 7 +++++++ .../kotlin/serialization/common/runtime/JsonProducer.kt | 1 + 2 files changed, 8 insertions(+) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonConfig.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonConfig.java index df47f1c32e7c0..bc64347ff3d76 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonConfig.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/java/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonConfig.java @@ -129,6 +129,12 @@ public class JsonConfig { @ConfigItem(defaultValue = "false") public boolean decodeEnumsCaseInsensitive = false; + /** + * Specifies if trailing comma is allowed. + */ + @ConfigItem(defaultValue = "false") + public boolean allowTrailingComma = false; + @Override public String toString() { return new StringJoiner(", ", JsonConfig.class.getSimpleName() + "[", "]") @@ -144,6 +150,7 @@ public String toString() { .add("allowSpecialFloatingPointValues=" + allowSpecialFloatingPointValues) .add("useAlternativeNames=" + useAlternativeNames) .add("decodeEnumsCaseInsensitive=" + decodeEnumsCaseInsensitive) + .add("allowTrailingComma=" + allowTrailingComma) .toString(); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonProducer.kt b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonProducer.kt index 45ea46cde67a8..89dc0289b1104 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonProducer.kt +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-kotlin-serialization-common/runtime/src/main/kotlin/io/quarkus/resteasy/reactive/kotlin/serialization/common/runtime/JsonProducer.kt @@ -38,6 +38,7 @@ class JsonProducer { useAlternativeNames = configuration.json.useAlternativeNames useArrayPolymorphism = configuration.json.useArrayPolymorphism decodeEnumsCaseInsensitive = configuration.json.decodeEnumsCaseInsensitive + allowTrailingComma = configuration.json.allowTrailingComma configuration.json.namingStrategy.ifPresent { strategy -> loadStrategy(this, strategy, this@JsonProducer) From 3d849d91b0466561b32c6f4d647dbd25f99214b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:02:04 +0000 Subject: [PATCH 108/212] Bump org.junit:junit-bom from 5.10.0 to 5.10.1 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit af1868de6548e320d59d2902ef4d7906cbd22125) --- bom/application/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index c07efa0abb479..c5728e0800937 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -136,7 +136,7 @@ 1.2.6 5.3.2 2.2 - 5.10.0 + 5.10.1 1.5.0 14.0.21.Final 4.6.5.Final diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 9fcf219e160dd..5cf3128c08d0c 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -54,7 +54,7 @@ 1.6.Final 3.24.2 - 5.10.0 + 5.10.1 1.9.21 1.7.3 5.7.0 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index ca6c9c5db9f07..f618d9bee11b4 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -41,7 +41,7 @@ 11 11 11 - 5.10.0 + 5.10.1 3.24.2 3.1.6 1.7.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 5089c255879c7..5ce2d22175a19 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -50,7 +50,7 @@ 4.0.1 3.1.6 1.12.12 - 5.10.0 + 5.10.1 3.9.6 3.24.2 3.5.3.Final From b15e73798054361941aafca6d7c848a79c9adfa0 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 30 Nov 2023 10:20:40 +0100 Subject: [PATCH 109/212] Repeat the @Test annotation, otherwise @Disabled is ignored. this is a change in behavior of Junit. (cherry picked from commit b7c27fe0ed93c338c7b0ba88e0fd10b9bec70a07) --- .../id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java index 2ff9f955a727c..fcc9da07c9937 100644 --- a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/mapping/id/optimizer/optimizer/IdOptimizerDefaultNoneTest.java @@ -2,6 +2,7 @@ import org.hibernate.id.enhanced.NoopOptimizer; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.hibernate.reactive.SchemaUtil; @@ -22,6 +23,7 @@ public class IdOptimizerDefaultNoneTest extends AbstractIdOptimizerDefaultTest { .overrideConfigKey("quarkus.hibernate-orm.mapping.id.optimizer.default", "none"); @Override + @Test @Disabled("The 'none' optimizer will produce a different stream of IDs (1 then 51 then 101 then ...)") public void ids(UniAsserter asserter) { super.ids(asserter); From dcc8b690da73888f3469dd1c28721496757bd8cb Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 4 Dec 2023 16:45:20 +0100 Subject: [PATCH 110/212] Save pathParamValues encoded and perform decoding when requested Fixes #35960 Signed-off-by: rmartinc (cherry picked from commit aad19cac6d6a67fdd65885899acd0c96675280d7) --- .../core/ResteasyReactiveRequestContext.java | 41 ++++++++----------- .../LocatableResourcePathParamExtractor.java | 2 +- .../core/parameters/PathParamExtractor.java | 14 ++++--- .../reactive/server/jaxrs/UriInfoImpl.java | 2 +- .../server/mapping/RequestMapper.java | 4 +- .../vertx/test/matching/RegexMatchTest.java | 4 ++ 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index dedaf7dd127cd..2248a8f7763ac 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -226,18 +226,20 @@ public void setMaxPathParams(int maxPathParams) { } } - public String getPathParam(int index) { - return doGetPathParam(index, pathParamValues); + public String getPathParam(int index, boolean encoded) { + return doGetPathParam(index, pathParamValues, encoded); } - private String doGetPathParam(int index, Object pathParamValues) { + private String doGetPathParam(int index, Object pathParamValues, boolean encoded) { if (pathParamValues instanceof String[]) { - return ((String[]) pathParamValues)[index]; + String pathParam = ((String[]) pathParamValues)[index]; + return encoded ? pathParam : Encode.decodePath(pathParam); } if (index > 1) { throw new IndexOutOfBoundsException(); } - return (String) pathParamValues; + String pathParam = (String) pathParamValues; + return encoded ? pathParam : Encode.decodePath(pathParam); } public ResteasyReactiveRequestContext setPathParamValue(int index, String value) { @@ -926,18 +928,11 @@ public String getPathParameter(String name, boolean encoded) { Integer index = target.getPathParameterIndexes().get(name); String value; if (index != null) { - value = getPathParam(index); - } else { - // Check previous resources if the path is not defined in the current target - value = getResourceLocatorPathParam(name); - } - - // It's possible to inject a path param that's not defined, return null in this case - if (encoded && value != null) { - return Encode.encodeQueryParam(value); + return getPathParam(index, encoded); } - return value; + // Check previous resources if the path is not defined in the current target + return getResourceLocatorPathParam(name, encoded); } @Override @@ -996,8 +991,8 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { public abstract Runnable registerTimer(long millis, Runnable task); - public String getResourceLocatorPathParam(String name) { - return getResourceLocatorPathParam(name, (PreviousResource) getProperty(PreviousResource.PROPERTY_KEY)); + public String getResourceLocatorPathParam(String name, boolean encoded) { + return getResourceLocatorPathParam(name, (PreviousResource) getProperty(PreviousResource.PROPERTY_KEY), encoded); } public FormData getFormData() { @@ -1009,7 +1004,7 @@ public ResteasyReactiveRequestContext setFormData(FormData formData) { return this; } - private String getResourceLocatorPathParam(String name, PreviousResource previousResource) { + private String getResourceLocatorPathParam(String name, PreviousResource previousResource, boolean encoded) { if (previousResource == null) { return null; } @@ -1020,13 +1015,13 @@ private String getResourceLocatorPathParam(String name, PreviousResource previou for (URITemplate.TemplateComponent component : classPath.components) { if (component.name != null) { if (component.name.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } index++; } else if (component.names != null) { for (String nm : component.names) { if (nm.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } } index++; @@ -1036,19 +1031,19 @@ private String getResourceLocatorPathParam(String name, PreviousResource previou for (URITemplate.TemplateComponent component : previousResource.locatorTarget.getPath().components) { if (component.name != null) { if (component.name.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } index++; } else if (component.names != null) { for (String nm : component.names) { if (nm.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } } index++; } } - return getResourceLocatorPathParam(name, previousResource.prev); + return getResourceLocatorPathParam(name, previousResource.prev, encoded); } public abstract boolean resumeExternalProcessing(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java index 835be0b515340..e478141b7c45c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java @@ -12,7 +12,7 @@ public LocatableResourcePathParamExtractor(String name) { @Override public Object extractParameter(ResteasyReactiveRequestContext context) { - return context.getResourceLocatorPathParam(name); + return context.getResourceLocatorPathParam(name, false); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java index ccfa99edca5c3..d56f72b856bf5 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java @@ -1,6 +1,8 @@ package org.jboss.resteasy.reactive.server.core.parameters; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.jboss.resteasy.reactive.common.util.Encode; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; @@ -19,14 +21,14 @@ public PathParamExtractor(int index, boolean encoded, boolean single) { @Override public Object extractParameter(ResteasyReactiveRequestContext context) { - String pathParam = context.getPathParam(index); - if (encoded) { - pathParam = Encode.encodeQueryParam(pathParam); - } + String pathParam = context.getPathParam(index, true); if (single) { - return pathParam; + return encoded ? pathParam : Encode.decodePath(pathParam); } else { - return List.of(pathParam.split("/")); + return encoded + ? List.of(pathParam.split("/")) + : Arrays.stream(pathParam.split("/")).map(Encode::decodePath) + .collect(Collectors.toList()); } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java index 7cbd13cb272a6..987bbf2838113 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java @@ -152,7 +152,7 @@ public MultivaluedMap getPathParameters(boolean decode) { RuntimeResource target = currentRequest.getTarget(); if (target != null) { // a target can be null if this happens in a filter that runs before the target is set for (Entry pathParam : target.getPathParameterIndexes().entrySet()) { - pathParams.add(pathParam.getKey(), currentRequest.getPathParam(pathParam.getValue())); + pathParams.add(pathParam.getKey(), currentRequest.getPathParam(pathParam.getValue(), false)); } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java index 5d8d5fc931191..7fd7cb535f518 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java @@ -8,8 +8,6 @@ import java.util.Map; import java.util.regex.Matcher; -import org.jboss.resteasy.reactive.common.util.URIDecoder; - public class RequestMapper { private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -111,7 +109,7 @@ private RequestMatch mapFromPathMatcher(String path, PathMatcher.PathMatch Date: Tue, 5 Dec 2023 17:36:12 +0200 Subject: [PATCH 111/212] Fix != expression in @PreAuthorize check Fixes: #37526 (cherry picked from commit 4616d52b43d44b86c373bcfaf3e6d75ce4190ea1) --- .../deployment/SpringSecurityProcessor.java | 8 ++++++- .../deployment/SpringPreAuthorizeTest.java | 10 ++++++++ .../deployment/springapp/SpringComponent.java | 5 ++++ .../interceptor/SpringSecurityRecorder.java | 5 ++-- ...lNameFromParameterObjectSecurityCheck.java | 23 +++++++++++++++---- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java index 70d16090e0090..d5d70eb2fbace 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java @@ -48,6 +48,7 @@ import io.quarkus.spring.security.runtime.interceptor.SpringPreauthorizeInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecuredInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecurityRecorder; +import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterObjectSecurityCheck; import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterSecurityCheck; class SpringSecurityProcessor { @@ -466,13 +467,18 @@ void addSpringPreAuthorizeSecurityCheck(CombinedIndexBuildItem index, propertyName, index.getIndex(), part); + PrincipalNameFromParameterObjectSecurityCheck.CheckType checkType = part.contains("==") + ? PrincipalNameFromParameterObjectSecurityCheck.CheckType.EQ + : PrincipalNameFromParameterObjectSecurityCheck.CheckType.NEQ; + securityChecks.add(springSecurityRecorder.principalNameFromParameterObjectSecurityCheck( parameterNameAndIndex.getIndex(), stringPropertyAccessorData.getMatchingParameterClassInfo().name().toString(), StringPropertyAccessorGenerator .getAccessorClassName( stringPropertyAccessorData.getMatchingParameterClassInfo().name()), - stringPropertyAccessorData.getMatchingParameterFieldInfo().name())); + stringPropertyAccessorData.getMatchingParameterFieldInfo().name(), + checkType)); } } else if (part.matches(SpringSecurityProcessorUtil.BASIC_BEAN_METHOD_INVOCATION_REGEX)) { diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java index eaab4280b3647..c7dc3deff1f77 100644 --- a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java @@ -116,6 +116,16 @@ public void testPrincipalNameFromObject() { assertSuccess(() -> springComponent.principalNameFromObject(new Person("user")), "user", USER); } + @Test + public void testPrincipalNameFromObjectIsNot() { + assertFailureFor(() -> springComponent.principalNameFromObjectIsNot(new Person("whatever")), + UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> springComponent.principalNameFromObjectIsNot(new Person("whatever")), "whatever", USER); + assertFailureFor(() -> springComponent.principalNameFromObjectIsNot(new Person("user")), ForbiddenException.class, + USER); + } + @Test public void testNotSecured() { assertSuccess(() -> springComponent.notSecured(), "notSecured", ANONYMOUS); diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java index 0d86e8406fcf3..68a8af6552c6d 100644 --- a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java @@ -37,6 +37,11 @@ public String principalNameFromObject(Person person) { return person.getName(); } + @PreAuthorize("#person.name != authentication.principal.username") + public String principalNameFromObjectIsNot(Person person) { + return person.getName(); + } + public String notSecured() { return "notSecured"; } diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java index 8101bdd0991b6..591c08d8bd892 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java @@ -70,8 +70,9 @@ public SecurityCheck fromGeneratedClass(String generatedClassName) { } public SecurityCheck principalNameFromParameterObjectSecurityCheck(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) { + String stringPropertyAccessorClass, String propertyName, + PrincipalNameFromParameterObjectSecurityCheck.CheckType checkType) { return PrincipalNameFromParameterObjectSecurityCheck.of(index, expectedParameterClass, stringPropertyAccessorClass, - propertyName); + propertyName, checkType); } } diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java index c34e97b6d5f7b..63da575e48187 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java @@ -23,22 +23,24 @@ public class PrincipalNameFromParameterObjectSecurityCheck implements SecurityCh private final Class expectedParameterClass; private final Class stringPropertyAccessorClass; private final String propertyName; + private final CheckType checkType; private PrincipalNameFromParameterObjectSecurityCheck(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) throws ClassNotFoundException { + String stringPropertyAccessorClass, String propertyName, CheckType checkType) throws ClassNotFoundException { this.index = index; this.expectedParameterClass = Class.forName(expectedParameterClass, false, Thread.currentThread().getContextClassLoader()); this.stringPropertyAccessorClass = (Class) Class.forName(stringPropertyAccessorClass, false, Thread.currentThread().getContextClassLoader()); this.propertyName = propertyName; + this.checkType = checkType; } public static PrincipalNameFromParameterObjectSecurityCheck of(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) { + String stringPropertyAccessorClass, String propertyName, CheckType checkType) { try { return new PrincipalNameFromParameterObjectSecurityCheck(index, expectedParameterClass, stringPropertyAccessorClass, - propertyName); + propertyName, checkType); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } @@ -70,8 +72,14 @@ private void doApply(SecurityIdentity identity, Object[] parameters, String clas } String name = identity.getPrincipal().getName(); - if (!name.equals(parameterValueStr)) { - throw new ForbiddenException(); + if (checkType == CheckType.EQ) { + if (!name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } + } else if (checkType == CheckType.NEQ) { + if (name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } } } @@ -84,4 +92,9 @@ private IllegalStateException genericNotApplicableException(String className, St "PrincipalNameFromParameterObjectSecurityCheck with index " + index + " cannot be applied to '" + className + "#" + methodName + "'"); } + + public enum CheckType { + EQ, + NEQ + } } From 31f352f026e96b434682442ac37920a4cfa98dd5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 5 Dec 2023 16:29:37 +0100 Subject: [PATCH 112/212] Fix Snappy note about native I went there to fix a typo (Uses -> Use) but in the end we don't support GraalVM 21 anymore so we can simplify all that. (cherry picked from commit ed014f601a8c2e17f5d732a8c13b67800a4e4fdb) --- docs/src/main/asciidoc/kafka.adoc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 819d70d996dbe..b559acfa4d85d 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -1952,10 +1952,8 @@ mp.messaging.outgoing.fruit-out.compression.type=snappy ---- In JVM mode, it will work out of the box. -However, to compile your application to a native executable, you need to: - -1. Uses GraalVM 21.+ -2. Add `quarkus.kafka.snappy.enabled=true` to your `application.properties` +However, to compile your application to a native executable, you need to +add `quarkus.kafka.snappy.enabled=true` to your `application.properties`. In native mode, Snappy is disabled by default as the use of Snappy requires embedding a native library and unpacking it when the application starts. From d6f62b329802f98c0ceef15753a262c3f69a1dd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:33:02 +0000 Subject: [PATCH 113/212] Bump org.jboss.logmanager:log4j2-jboss-logmanager Bumps [org.jboss.logmanager:log4j2-jboss-logmanager](https://github.com/jboss-logging/log4j2-jboss-logmanager) from 1.1.1.Final to 1.1.2.Final. - [Release notes](https://github.com/jboss-logging/log4j2-jboss-logmanager/releases) - [Commits](https://github.com/jboss-logging/log4j2-jboss-logmanager/compare/1.1.1.Final...1.1.2.Final) --- updated-dependencies: - dependency-name: org.jboss.logmanager:log4j2-jboss-logmanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 19f086b5bebde1f36f6802117c2340d7d54a83b2) --- 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 c5728e0800937..1df83b6f15332 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -200,7 +200,7 @@ 1.25.0 1.11.0 2.10.1 - 1.1.1.Final + 1.1.2.Final 2.20.0 1.3.0.Final 1.11.3 From 6a4a79a1927853f61970f81d4551467d88ddc981 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 5 Dec 2023 11:28:13 +0000 Subject: [PATCH 114/212] Properly match unknown config files for Windows (cherry picked from commit d0f48bf6123e7b2b5c6d24b199e7a88a4a8952d1) --- .../java/io/quarkus/runtime/configuration/ConfigDiagnostic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java index 6a28d740a1713..41d698b81a3d1 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -184,7 +184,7 @@ public static Set configFiles(Path configFilesLocation) throws IOExcepti Set configFiles = new HashSet<>(); try (DirectoryStream candidates = Files.newDirectoryStream(configFilesLocation, CONFIG_FILES_FILTER)) { for (Path candidate : candidates) { - configFiles.add(candidate.toString()); + configFiles.add(candidate.toUri().getPath()); } } return configFiles; From 77a607dd4e4c8cb7135b919207430efaa480e1f6 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 6 Dec 2023 10:02:05 +0100 Subject: [PATCH 115/212] Make docs/sync-web-site.sh recoverable (cherry picked from commit 763de410a20496b3669ffa9821cacafd158e4c6a) --- docs/sync-web-site.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index 353e45c2c58e0..6f310b98c61dd 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -32,6 +32,7 @@ fi if [ -z $TARGET_DIR ]; then TARGET_DIR=target/web-site + rm -rf ${TARGET_DIR} GIT_OPTIONS="" if [[ "$QUARKUS_WEB_SITE_PUSH" != "true" ]]; then GIT_OPTIONS="--depth=1" From baba317849b713afe6f850e4fce246f15f8e046f Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 6 Dec 2023 13:40:30 +0100 Subject: [PATCH 116/212] ArC: prevent duplicate bean discovery & detect duplicate bean identifiers A bean class may occur in the bean archive index multiple times, because the index is in fact a composite of multiple indices and there's nothing preventing duplicity in the composition. Fortunately, all the additional customizations (such as default scope or unremovability from `AdditionalBeanBuildItem`) are passed around outside of the bean archive index (for example, as annotation transformations or as `BeanProcessor` configuration), so they are not lost. (cherry picked from commit 5b2a51491b8f935245c6dd2f49c61fd06e0c4dc4) --- .../quarkus/arc/processor/BeanDeployment.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index f64a96a1c42f5..8b22177b9da75 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -953,8 +953,14 @@ private List findBeans(Collection beanDefiningAnnotations, Li .map(StereotypeInfo::getName) .collect(Collectors.toSet()); + Set seenClasses = new HashSet<>(); + // If needed use the specialized immutable index to discover beans for (ClassInfo beanClass : beanArchiveImmutableIndex.getKnownClasses()) { + if (!seenClasses.add(beanClass.name())) { + // avoid discovering the same bean twice + continue; + } if (Modifier.isInterface(beanClass.flags()) || Modifier.isAbstract(beanClass.flags()) || beanClass.isAnnotation() || beanClass.isEnum()) { @@ -1579,6 +1585,29 @@ private void validateBeans(List errors, Consumer } } } + + List>> duplicateBeanIds = beans.stream() + .collect(Collectors.groupingBy(BeanInfo::getIdentifier)) + .entrySet() + .stream() + .filter(entry -> entry.getValue().size() > 1) + .collect(Collectors.toList()); + if (!duplicateBeanIds.isEmpty()) { + String separator = "===================="; + StringBuilder error = new StringBuilder("\n") + .append(separator).append(separator).append(separator).append(separator).append("\n") + .append("Multiple beans with the same identifier found!\n") + .append("----------------------------------------------\n") + .append("This is an internal error. Please report a bug and attach the following listing.\n\n"); + for (Map.Entry> entry : duplicateBeanIds) { + error.append(entry.getKey()).append(" -> ").append(entry.getValue().size()).append(" beans:\n"); + for (BeanInfo bean : entry.getValue()) { + error.append("- ").append(bean).append("\n"); + } + } + error.append(separator).append(separator).append(separator).append(separator).append("\n"); + errors.add(new DeploymentException(error.toString())); + } } private void findNamespaces(BeanInfo bean, Set namespaces) { From 449266be6ef4a030ef0ad652842b7748a447176c Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 7 Dec 2023 17:41:03 +0100 Subject: [PATCH 117/212] Update the import.cypher script to use newer syntax (cherry picked from commit f9056684bef0e113bef7f71555f8fe0dfc1cd7e5) --- docs/src/main/asciidoc/native-reference.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/native-reference.adoc b/docs/src/main/asciidoc/native-reference.adoc index 5aaf0176fef95..4880f74a2051b 100644 --- a/docs/src/main/asciidoc/native-reference.adoc +++ b/docs/src/main/asciidoc/native-reference.adoc @@ -846,8 +846,8 @@ we need the following cypher script which will import the data within the CSV fi [source,cypher] ---- -CREATE CONSTRAINT unique_vm_id ON (v:VM) ASSERT v.vmId IS UNIQUE; -CREATE CONSTRAINT unique_method_id ON (m:Method) ASSERT m.methodId IS UNIQUE; +CREATE CONSTRAINT unique_vm_id FOR (v:VM) REQUIRE v.vmId IS UNIQUE; +CREATE CONSTRAINT unique_method_id FOR (m:Method) REQUIRE m.methodId IS UNIQUE; LOAD CSV WITH HEADERS FROM 'file:///reports/call_tree_vm.csv' AS row MERGE (v:VM {vmId: row.Id, name: row.Name}) From 05809169e61927d75f91c6f63e5637c3a5766f08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 22:12:13 +0000 Subject: [PATCH 118/212] Bump org.junit.jupiter:junit-jupiter from 5.10.0 to 5.10.1 Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 9e5e4e90dd5136bc929e57fc4976f802f2915d8a) --- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/junit5-virtual-threads/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 42aa44afb02e2..cc366b7e952a8 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -46,7 +46,7 @@ 3.24.2 0.9.5 3.5.3.Final - 5.10.0 + 5.10.1 3.9.6 0.9.0.M2 3.10.2 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 909d80325c1ff..3b7c4993340c4 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -44,7 +44,7 @@ 3.10.2 2.15.3 1.3.2 - 5.10.0 + 5.10.1 diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index d30d68dcb74ef..cdb450b45d62b 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -48,7 +48,7 @@ 2.23.0 1.9.0 - 5.10.0 + 5.10.1 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 6aeadfa39d310..59b0e0ff0ff5c 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -54,7 +54,7 @@ 3.24.2 2.15.3 4.0.1 - 5.10.0 + 5.10.1 1.25.0 3.5.3.Final 5.3.1 From 14d8960cbbabfeff937fa8ca1d4bef08a9631b31 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 6 Dec 2023 11:45:48 +1100 Subject: [PATCH 119/212] Register custom filters for reflection Signed-off-by: Phillip Kruger (cherry picked from commit 3f878064de49f24b34189bbc8df919db39e03c74) --- .../smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 851e82a60e179..f73cfa65aeeeb 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -244,6 +244,7 @@ void registerAutoSecurityFilter(BuildProducer syntheticB @BuildStep @Record(ExecutionTime.STATIC_INIT) void registerAnnotatedUserDefinedRuntimeFilters(BuildProducer syntheticBeans, + BuildProducer reflectiveClass, OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem, OpenApiRecorder recorder) { Config config = ConfigProvider.getConfig(); @@ -256,6 +257,7 @@ void registerAnnotatedUserDefinedRuntimeFilters(BuildProducer Date: Tue, 5 Dec 2023 22:12:57 +0000 Subject: [PATCH 120/212] Bump io.quarkus:quarkus-platform-bom-maven-plugin from 0.0.99 to 0.0.100 Bumps [io.quarkus:quarkus-platform-bom-maven-plugin](https://github.com/quarkusio/quarkus-platform-bom-generator) from 0.0.99 to 0.0.100. - [Release notes](https://github.com/quarkusio/quarkus-platform-bom-generator/releases) - [Commits](https://github.com/quarkusio/quarkus-platform-bom-generator/compare/0.0.99...0.0.100) --- updated-dependencies: - dependency-name: io.quarkus:quarkus-platform-bom-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit dae933804668f299a5636a318053d0a1bb931ccd) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 90c5862d3c077..3e14413b69ea7 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ jdbc:postgresql:hibernate_orm_test 4.5.1 - 0.0.99 + 0.0.100 false false From 62c4ac82ba67eb39c61b4abdf95d2265538f849d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 22:15:19 +0000 Subject: [PATCH 121/212] Bump commons-io:commons-io from 2.15.0 to 2.15.1 Bumps commons-io:commons-io from 2.15.0 to 2.15.1. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit b143f882a59c77a209bb08709d6dd5906d9f21cf) --- bom/application/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 1df83b6f15332..a419f7bb47ee0 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -84,7 +84,7 @@ 4.0.1 4.0.4 9.6 - 2.15.0 + 2.15.1 16.0.0.Final 3.0-alpha-2 2.1.0 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index cc366b7e952a8..ad9f2f590dadf 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -61,7 +61,7 @@ 4.0.1 2.0.1 1.16.0 - 2.15.0 + 2.15.1 3.13.0 32.1.3-jre 1.0.1 From ff736fda93ea814bd00a5bcb8556071df99c1b2c Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 8 Dec 2023 10:19:42 +0200 Subject: [PATCH 122/212] Fix type in HeartbeatFilter example of rest-client-reactive doc (cherry picked from commit 23356953d8639588cef663be2ce11931f6b679bc) --- docs/src/main/asciidoc/rest-client-reactive.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index b3c063bccdda2..1fea2b5a03736 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -974,7 +974,7 @@ public interface SseClient { Multi> get(); - class HeartbeatFilter implements Predicate> { + class HeartbeatFilter implements Predicate> { @Override public boolean test(SseEvent event) { From 84f0a81b10eb96e98386793e96eba456c3dfe354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:22:27 +0000 Subject: [PATCH 123/212] Bump org.jetbrains.kotlinx:kotlinx-serialization-json Bumps [org.jetbrains.kotlinx:kotlinx-serialization-json](https://github.com/Kotlin/kotlinx.serialization) from 1.6.1 to 1.6.2. - [Release notes](https://github.com/Kotlin/kotlinx.serialization/releases) - [Changelog](https://github.com/Kotlin/kotlinx.serialization/blob/master/CHANGELOG.md) - [Commits](https://github.com/Kotlin/kotlinx.serialization/compare/v1.6.1...v1.6.2) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-serialization-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit abb1de2c7f65a9f8224c624bdfe0bc30da00c405) --- 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 a419f7bb47ee0..a34ed97d75b3f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -160,7 +160,7 @@ 1.9.21 1.7.3 0.27.0 - 1.6.1 + 1.6.2 4.0.3 3.2.0 4.2.0 From 04fcd5e741ed1c7e94e58d6a20cb525a18064868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 8 Dec 2023 12:54:50 +0100 Subject: [PATCH 124/212] Fix configuration of custom port for Elasticsearch dev services (cherry picked from commit 442ab5cc1d48b04d27a71fda2c7b1524dea6542b) --- .../DevServicesElasticsearchProcessor.java | 2 +- ...lasticsearchDevModeCustomPortTestCase.java | 45 +++++++++++++++++++ .../lowlevel/runtime/TestResource.java | 7 +++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/DevServicesElasticsearchDevModeCustomPortTestCase.java diff --git a/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java b/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java index 065acd241b0c0..e36b279d99b4a 100644 --- a/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java +++ b/extensions/elasticsearch-rest-client-common/deployment/src/main/java/io/quarkus/elasticsearch/restclient/common/deployment/DevServicesElasticsearchProcessor.java @@ -220,7 +220,7 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch( container.withLabel(DEV_SERVICE_LABEL, config.serviceName); } if (config.port.isPresent()) { - container.setPortBindings(List.of(config.port.get() + ":" + config.port.get())); + container.setPortBindings(List.of(config.port.get() + ":" + ELASTICSEARCH_PORT)); } timeout.ifPresent(container::withStartupTimeout); diff --git a/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/DevServicesElasticsearchDevModeCustomPortTestCase.java b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/DevServicesElasticsearchDevModeCustomPortTestCase.java new file mode 100644 index 0000000000000..2f9a9e39a3ec9 --- /dev/null +++ b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/DevServicesElasticsearchDevModeCustomPortTestCase.java @@ -0,0 +1,45 @@ +package io.quarkus.elasticsearch.restclient.lowlevel.runtime; + +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class DevServicesElasticsearchDevModeCustomPortTestCase { + @RegisterExtension + static QuarkusDevModeTest test = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClass(TestResource.class) + .addAsResource(new StringAsset("quarkus.elasticsearch.devservices.port=19200"), "application.properties")); + + @Test + public void checkConfiguredPort() { + RestAssured + .when().get("/fruits/configured-hosts") + .then().body(endsWith(":19200")); + + } + + @Test + public void testDatasource() throws Exception { + var fruit = new TestResource.Fruit(); + fruit.id = "1"; + fruit.name = "banana"; + fruit.color = "yellow"; + + RestAssured + .given().body(fruit).contentType("application/json") + .when().post("/fruits") + .then().statusCode(204); + + RestAssured.when().get("/fruits/search?term=color&match=yellow") + .then() + .statusCode(200) + .body(equalTo("[{\"id\":\"1\",\"name\":\"banana\",\"color\":\"yellow\"}]")); + } +} diff --git a/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/TestResource.java b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/TestResource.java index 5a10c4ebb8d67..4ab00a0f385d7 100644 --- a/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/TestResource.java +++ b/extensions/elasticsearch-rest-client/deployment/src/test/java/io/quarkus/elasticsearch/restclient/lowlevel/runtime/TestResource.java @@ -11,6 +11,7 @@ import jakarta.ws.rs.QueryParam; import org.apache.http.util.EntityUtils; +import org.eclipse.microprofile.config.ConfigProvider; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; @@ -57,6 +58,12 @@ public List search(@QueryParam("term") String term, @QueryParam("match") return results; } + @GET + @Path("/configured-hosts") + public String configuredHosts() { + return ConfigProvider.getConfig().getConfigValue("quarkus.elasticsearch.hosts").getValue(); + } + public static class Fruit { public String id; public String name; From 07417d7f75bf591456de17b84277eec721c388f7 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 7 Dec 2023 14:43:08 +0100 Subject: [PATCH 125/212] Support using commas to add and remove extensions with CLI Actually, it was already supported when creating projects, just not when adding/removing extensions. Fixes #37564 (cherry picked from commit 09d2c52bcde6959d2a201f0f945451973099f459) --- .../io/quarkus/cli/ProjectExtensionsAdd.java | 2 +- .../quarkus/cli/ProjectExtensionsRemove.java | 2 +- .../test/java/io/quarkus/cli/CliDriver.java | 50 ++++++++++++++++- .../io/quarkus/cli/CliProjectMavenTest.java | 2 + .../src/main/resources/fake-catalog.json | 56 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java index 3b1266ecdae52..4d3e1572d771c 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java @@ -17,7 +17,7 @@ public class ProjectExtensionsAdd extends BaseBuildCommand implements Callable extensions; @Override diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java index adf82223bccfc..2affcc921bf42 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java @@ -17,7 +17,7 @@ public class ProjectExtensionsRemove extends BaseBuildCommand implements Callabl @CommandLine.Mixin RunModeOption runMode; - @CommandLine.Parameters(arity = "1", paramLabel = "EXTENSION", description = "Extension(s) to remove from this project.") + @CommandLine.Parameters(arity = "1", paramLabel = "EXTENSION", description = "Extension(s) to remove from this project.", split = ",") Set extensions; @Override diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java index 6bbbc63a26463..60af7b5aabb94 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java @@ -319,7 +319,7 @@ public static Result invokeExtensionRemoveQute(Path projectRoot, Path file) thro } public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) throws Exception { - // add the qute extension + // add amazon-lambda-http and jackson extensions Result result = execute(projectRoot, "extension", "add", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -345,7 +345,7 @@ public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) thr } public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) throws Exception { - // add the qute extension + // remove amazon-lambda-http and jackson extensions Result result = execute(projectRoot, "extension", "remove", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -370,6 +370,52 @@ public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) return result; } + public static Result invokeExtensionAddMultipleCommas(Path projectRoot, Path file) throws Exception { + Result result = execute(projectRoot, "extension", "add", + "quarkus-resteasy-reactive-jsonb,quarkus-resteasy-reactive-jackson", "-e", "-B", "--verbose"); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code. Result:\n" + result); + + result = invokeValidateExtensionList(projectRoot); + Assertions.assertTrue(result.stdout.contains("quarkus-qute"), + "Expected quarkus-qute to be in the list of extensions. Result:\n" + result); + Assertions.assertTrue(result.stdout.contains("quarkus-resteasy-reactive-jsonb"), + "Expected quarkus-resteasy-reactive-jsonb to be in the list of extensions. Result:\n" + result); + Assertions.assertTrue(result.stdout.contains("quarkus-resteasy-reactive-jackson"), + "Expected quarkus-resteasy-reactive-jackson to be in the list of extensions. Result:\n" + result); + + String content = CliDriver.readFileAsString(file); + Assertions.assertTrue(content.contains("quarkus-qute"), + "quarkus-qute should still be listed as a dependency. Result:\n" + content); + Assertions.assertTrue(content.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should be listed as a dependency. Result:\n" + content); + Assertions.assertTrue(content.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should be listed as a dependency. Result:\n" + content); + + return result; + } + + public static Result invokeExtensionRemoveMultipleCommas(Path projectRoot, Path file) throws Exception { + Result result = execute(projectRoot, "extension", "remove", + "quarkus-resteasy-reactive-jsonb,quarkus-resteasy-reactive-jackson", "-e", "-B", "--verbose"); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code. Result:\n" + result); + + result = invokeValidateExtensionList(projectRoot); + Assertions.assertFalse(result.stdout.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should not be in the list of extensions. Result:\n" + result); + Assertions.assertFalse(result.stdout.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should not be in the list of extensions. Result:\n" + result); + + String content = CliDriver.readFileAsString(file); + Assertions.assertFalse(content.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should not be listed as a dependency. Result:\n" + content); + Assertions.assertFalse(content.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should not be listed as a dependency. Result:\n" + content); + + return result; + } + public static Result invokeExtensionListInstallable(Path projectRoot) throws Exception { Result result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java index d8782c4f1b0d2..4653a21a73723 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java @@ -145,8 +145,10 @@ public void testExtensionList() throws Exception { CliDriver.invokeExtensionAddRedundantQute(project); CliDriver.invokeExtensionListInstallable(project); CliDriver.invokeExtensionAddMultiple(project, pom); + CliDriver.invokeExtensionAddMultipleCommas(project, pom); CliDriver.invokeExtensionRemoveQute(project, pom); CliDriver.invokeExtensionRemoveMultiple(project, pom); + CliDriver.invokeExtensionRemoveMultipleCommas(project, pom); CliDriver.invokeExtensionListInstallableSearch(project); CliDriver.invokeExtensionListFormatting(project); diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index b6e8a6310329c..89aa29ee4265a 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -75,6 +75,62 @@ "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" ] }, + { + "name" : "RESTEasy Reactive Jackson", + "description" : "Jackson serialization support for RESTEasy Reactive. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it", + "metadata" : { + "codestart" : { + "name" : "resteasy-reactive", + "kind" : "core", + "languages" : [ "java", "kotlin", "scala" ], + "artifact" : "io.quarkus:quarkus-project-core-extension-codestarts::jar:999-FAKE" + }, + "minimum-java-version" : "11", + "status" : "stable", + "config" : [ "quarkus.resteasy-reactive.", "quarkus.jackson." ], + "built-with-quarkus-core" : "999-FAKE", + "scm-url" : "https://github.com/quarkus-release/release", + "short-name" : "resteasy-reactive-jackson", + "capabilities" : { + "provides" : [ "io.quarkus.rest.jackson", "io.quarkus.resteasy.reactive.json.jackson" ] + }, + "categories" : [ "web", "reactive" ], + "extension-dependencies" : [ "io.quarkus:quarkus-resteasy-reactive", "io.quarkus:quarkus-resteasy-reactive-common", "io.quarkus:quarkus-mutiny", "io.quarkus:quarkus-smallrye-context-propagation", "io.quarkus:quarkus-vertx", "io.quarkus:quarkus-netty", "io.quarkus:quarkus-vertx-http", "io.quarkus:quarkus-core", "io.quarkus:quarkus-jsonp", "io.quarkus:quarkus-virtual-threads", "io.quarkus:quarkus-arc", "io.quarkus:quarkus-resteasy-reactive-jackson-common", "io.quarkus:quarkus-jackson" ], + "keywords" : [ "rest-jackson", "quarkus-resteasy-reactive-json", "jaxrs-json", "rest", "jaxrs", "json", "jackson", "jakarta-rest" ] + }, + "artifact" : "io.quarkus:quarkus-resteasy-reactive-jackson::jar:999-FAKE", + "origins": [ + "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" + ] + }, + { + "name" : "RESTEasy Reactive JSON-B", + "description" : "JSON-B serialization support for RESTEasy Reactive. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.", + "metadata" : { + "codestart" : { + "name" : "resteasy-reactive", + "kind" : "core", + "languages" : [ "java", "kotlin", "scala" ], + "artifact" : "io.quarkus:quarkus-project-core-extension-codestarts::jar:999-FAKE" + }, + "minimum-java-version" : "11", + "status" : "stable", + "config" : [ "quarkus.resteasy-reactive." ], + "built-with-quarkus-core" : "999-FAKE", + "scm-url" : "https://github.com/quarkus-release/release", + "short-name" : "resteasy-reactive-jsonb", + "capabilities" : { + "provides" : [ "io.quarkus.rest.jsonb", "io.quarkus.resteasy.reactive.json.jsonb" ] + }, + "categories" : [ "web", "reactive" ], + "extension-dependencies" : [ "io.quarkus:quarkus-resteasy-reactive", "io.quarkus:quarkus-resteasy-reactive-common", "io.quarkus:quarkus-mutiny", "io.quarkus:quarkus-smallrye-context-propagation", "io.quarkus:quarkus-vertx", "io.quarkus:quarkus-netty", "io.quarkus:quarkus-vertx-http", "io.quarkus:quarkus-core", "io.quarkus:quarkus-jsonp", "io.quarkus:quarkus-virtual-threads", "io.quarkus:quarkus-arc", "io.quarkus:quarkus-resteasy-reactive-jsonb-common", "io.quarkus:quarkus-jsonb" ], + "keywords" : [ "rest-jsonb", "resteasy-reactive-json", "jaxrs-json", "rest", "jaxrs", "json", "jsonb", "jakarta-rest" ] + }, + "artifact" : "io.quarkus:quarkus-resteasy-reactive-jsonb::jar:999-FAKE", + "origins": [ + "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" + ] + }, { "name": "YAML Configuration", "description": "Use YAML to configure your Quarkus application", From 010a045c7e3333042b54b0b637a50705c3af730e Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Sat, 9 Dec 2023 12:25:31 -0500 Subject: [PATCH 126/212] Logging docs include build time config reference (cherry picked from commit a9292339140b25e2181bfcb75df7eb3a50c8c7e6) --- docs/src/main/asciidoc/logging.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index a9329d517f4b1..81afed44a01c7 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -841,4 +841,5 @@ NOTE: If applicable, MDC data is stored in a _duplicated context_, which is an i [[loggingConfigurationReference]] == Logging configuration reference +include::{generated-dir}/config/quarkus-log-logging-log-build-time-config.adoc[opts=optional, leveloffset=+1] include::{generated-dir}/config/quarkus-log-logging-log-config.adoc[opts=optional, leveloffset=+1] From 9103b47b14288c31f8ec9ba87dd76192295fd9b8 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Wed, 6 Dec 2023 17:48:43 +0100 Subject: [PATCH 127/212] Prevent concurrently running Jacoco ReportCreators to avoid report corruption (cherry picked from commit 404ff079199a2ad8a874d872ca962df902a60983) --- .../java/io/quarkus/jacoco/runtime/ReportCreator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java index e118708a6c684..2d440c3762e13 100644 --- a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java +++ b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java @@ -37,6 +37,16 @@ public ReportCreator(ReportInfo reportInfo, JacocoConfig config) { @Override public void run() { + // Ugly workaround: + // Multiple ReportCreator shutdown hooks might run concurrently, possibly corrupting the report file(s) - e.g. when using @TestProfile. + // By locking on a class from the parent CL, all hooks are "serialized", one after another. + // In the long run there should only be as many hooks as there are different Jacoco configs...usually there will be only one config anyway! + synchronized (ExecFileLoader.class) { + doRun(); + } + } + + private void doRun() { File targetdir = new File(reportInfo.reportDir); targetdir.mkdirs(); try { From ea83a87ffe48951d1a333528ec7f5a975a0c42b0 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Sun, 10 Dec 2023 10:38:54 +0100 Subject: [PATCH 128/212] Handle expireAfterWrite properly in the redis-cache config Fix https://github.com/quarkusio/quarkus/issues/37479 (cherry picked from commit 26da86dfc79a431577590d6ba968deea4d3df49c) --- .../io/quarkus/cache/redis/runtime/RedisCacheInfoBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheInfoBuilder.java b/extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheInfoBuilder.java index 80113eee560ba..7fd99591c1ba9 100644 --- a/extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheInfoBuilder.java +++ b/extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheInfoBuilder.java @@ -31,7 +31,7 @@ public static Set build(Set cacheNames, RedisCachesBuild if (namedRuntimeConfig != null && namedRuntimeConfig.expireAfterWrite.isPresent()) { cacheInfo.expireAfterWrite = namedRuntimeConfig.expireAfterWrite; - } else if (defaultRuntimeConfig.expireAfterAccess.isPresent()) { + } else if (defaultRuntimeConfig.expireAfterWrite.isPresent()) { cacheInfo.expireAfterWrite = defaultRuntimeConfig.expireAfterWrite; } From 5c888877b3d13b70b53984ee71c18495e6bf0519 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 13 Nov 2023 11:27:49 +0200 Subject: [PATCH 129/212] Reg. methods of RESTeasy reactive param. containers for reflection Fixes https://github.com/quarkusio/quarkus/issues/36986 (cherry picked from commit a69b1247d79005a45decf71a1950c7cce78fe969) --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index b96c574255bfa..96fdcf0c65fb1 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -289,7 +289,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, reflectiveClassBuildItemBuildProducer.produce(ReflectiveClassBuildItem .builder(scannedParameterContainers.stream().map(name -> name.toString()).collect(Collectors.toSet()) .toArray(new String[0])) - .fields().build()); + .methods().fields().build()); if (resourceScanningResultBuildItem.isEmpty() || resourceScanningResultBuildItem.get().getResult().getClientInterfaces().isEmpty()) { From 2475e3999643554909b5d895012df8659bee5c9f Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 13 Nov 2023 11:35:05 +0200 Subject: [PATCH 130/212] Refactor: Simplify class names array creation (cherry picked from commit 94cf51115110b4f7d4449c8c8105d9e16d88db5c) --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 96fdcf0c65fb1..cea258ce2771c 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -47,7 +47,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; -import java.util.stream.Collectors; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.RuntimeType; @@ -287,8 +286,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, scannedParameterContainers.addAll(parameterContainersBuildItem.getClassNames()); } reflectiveClassBuildItemBuildProducer.produce(ReflectiveClassBuildItem - .builder(scannedParameterContainers.stream().map(name -> name.toString()).collect(Collectors.toSet()) - .toArray(new String[0])) + .builder(scannedParameterContainers.stream().map(DotName::toString).distinct().toArray(String[]::new)) .methods().fields().build()); if (resourceScanningResultBuildItem.isEmpty() From acb1b24d91da1469891b4485d49f28e7dbfda222 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Fri, 8 Dec 2023 14:45:18 +0100 Subject: [PATCH 131/212] SmallRye GraphQL 2.6.1 (cherry picked from commit 9d3f2eefc4920f3ab4e944e635ba08f5fd5979c4) --- 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 a34ed97d75b3f..aed0e6ca6abaf 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -54,7 +54,7 @@ 4.0.4 4.0.0 3.7.0 - 2.6.0 + 2.6.1 6.2.6 4.4.0 2.1.0 From d60949a606cad8951f341b945a50281df98d7f04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:46:07 +0000 Subject: [PATCH 132/212] Bump kafka3.version from 3.6.0 to 3.6.1 Bumps `kafka3.version` from 3.6.0 to 3.6.1. Updates `org.apache.kafka:kafka-clients` from 3.6.0 to 3.6.1 Updates `org.apache.kafka:kafka-streams` from 3.6.0 to 3.6.1 Updates `org.apache.kafka:kafka-streams-test-utils` from 3.6.0 to 3.6.1 Updates `org.apache.kafka:kafka_2.13` from 3.6.0 to 3.6.1 --- updated-dependencies: - dependency-name: org.apache.kafka:kafka-clients dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka-streams dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka-streams-test-utils dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka_2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit bda4e5d2f59da910d1a11a8787f1d4b7340df840) --- 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 aed0e6ca6abaf..b56712b057e54 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -146,7 +146,7 @@ 1.0.4 3.5.3.Final 2.5.1 - 3.6.0 + 3.6.1 1.8.0 1.1.10.5 0.100.0 From 305e32e4ada228b447f9f965a32b27b53dfd4220 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 8 Dec 2023 16:10:45 +0100 Subject: [PATCH 133/212] Add quarkus-funqy-amazon-lambda-deployment to the quarkus-bom (cherry picked from commit 37252eb8bf8c5023baa387fa932c3df7507968f0) --- bom/application/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index b56712b057e54..d699b3a03fe18 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -2207,6 +2207,11 @@ quarkus-funqy-amazon-lambda ${project.version} + + io.quarkus + quarkus-funqy-amazon-lambda-deployment + ${project.version} + io.quarkus quarkus-funqy-google-cloud-functions From 1645a2921627c62991f39fb20e05c83dbb1bc7c6 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Sat, 9 Dec 2023 11:29:02 -0500 Subject: [PATCH 134/212] Add scope tag to all injected MP Metrics (cherry picked from commit c7f92293a3fc735e4b8946584cfc4ad51e73fd07) --- .../mpmetrics/MetricRegistryAdapter.java | 29 +++++++++----- .../micrometer/mpmetrics/MessageResource.java | 28 ++++++++++++- .../micrometer/mpmetrics/MPMetricsTest.java | 39 +++++++++++++++++-- 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MetricRegistryAdapter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MetricRegistryAdapter.java index be354011e3b89..4bffa61ee7830 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MetricRegistryAdapter.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/mpmetrics/MetricRegistryAdapter.java @@ -90,13 +90,13 @@ public Counter counter(Metadata metadata, Tag... tags) { Counter interceptorCounter(Metadata metadata, String... tags) { return internalCounter(internalGetMetadata(metadata, MetricType.COUNTER), - new MetricDescriptor(metadata.getName(), tags)); + new MetricDescriptor(metadata.getName(), scopeTags(tags))); } Counter injectedCounter(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalCounter( internalGetMetadata(annotation.name(), MetricType.COUNTER).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } CounterAdapter internalCounter(MpMetadata metadata, MetricDescriptor id) { @@ -138,13 +138,13 @@ public ConcurrentGauge concurrentGauge(Metadata metadata, Tag... tags) { ConcurrentGaugeImpl interceptorConcurrentGauge(Metadata metadata, String... tags) { return internalConcurrentGauge(internalGetMetadata(metadata, MetricType.CONCURRENT_GAUGE), - new MetricDescriptor(metadata.getName(), tags)); + new MetricDescriptor(metadata.getName(), scopeTags(tags))); } ConcurrentGaugeImpl injectedConcurrentGauge(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalConcurrentGauge( internalGetMetadata(annotation.name(), MetricType.CONCURRENT_GAUGE).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } ConcurrentGaugeImpl internalConcurrentGauge(MpMetadata metadata, MetricDescriptor id) { @@ -276,7 +276,7 @@ public Histogram histogram(Metadata metadata, Tag... tags) { HistogramAdapter injectedHistogram(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalHistogram( internalGetMetadata(annotation.name(), MetricType.HISTOGRAM).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } HistogramAdapter internalHistogram(MpMetadata metadata, MetricDescriptor id) { @@ -319,7 +319,7 @@ public Meter meter(Metadata metadata, Tag... tags) { MeterAdapter injectedMeter(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalMeter( internalGetMetadata(annotation.name(), MetricType.METERED).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } MeterAdapter internalMeter(MpMetadata metadata, MetricDescriptor id) { @@ -363,12 +363,12 @@ public Timer timer(Metadata metadata, Tag... tags) { TimerAdapter injectedTimer(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalTimer( internalGetMetadata(annotation.name(), MetricType.TIMER).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } TimerAdapter interceptorTimer(Metadata metadata, String... tags) { return internalTimer(internalGetMetadata(metadata, MetricType.TIMER), - new MetricDescriptor(metadata.getName(), tags)); + new MetricDescriptor(metadata.getName(), scopeTags(tags))); } TimerAdapter internalTimer(MpMetadata metadata, MetricDescriptor id) { @@ -465,7 +465,7 @@ public Metadata getMetadata(String name) { TimerAdapter injectedSimpleTimer(org.eclipse.microprofile.metrics.annotation.Metric annotation) { return internalSimpleTimer( internalGetMetadata(annotation.name(), MetricType.SIMPLE_TIMER).merge(annotation), - new MetricDescriptor(annotation.name(), annotation.tags())); + new MetricDescriptor(annotation.name(), scopeTags(annotation.tags()))); } TimerAdapter internalSimpleTimer(MpMetadata metadata, MetricDescriptor id) { @@ -657,14 +657,23 @@ public Type getType() { return null; } + Tags scopeTags() { + return Tags.of("scope", this.type.getName()); + } + Tags scopeTags(Tag... tags) { - Tags out = Tags.of("scope", this.type.getName()); + Tags out = scopeTags(); for (Tag t : tags) { out = out.and(t.getTagName(), t.getTagValue()); } return out; } + Tags scopeTags(String... tags) { + Tags in = Tags.of(tags); + return scopeTags().and(in); + } + private MpMetadata internalGetMetadata(String name, MetricType type) { MpMetadata result = metadataMap.computeIfAbsent(name, k -> new MpMetadata(name, type)); if (result.type != type) { diff --git a/integration-tests/micrometer-mp-metrics/src/main/java/io/quarkus/it/micrometer/mpmetrics/MessageResource.java b/integration-tests/micrometer-mp-metrics/src/main/java/io/quarkus/it/micrometer/mpmetrics/MessageResource.java index 2105c7ef33831..6ce3276f3f970 100644 --- a/integration-tests/micrometer-mp-metrics/src/main/java/io/quarkus/it/micrometer/mpmetrics/MessageResource.java +++ b/integration-tests/micrometer-mp-metrics/src/main/java/io/quarkus/it/micrometer/mpmetrics/MessageResource.java @@ -1,34 +1,60 @@ package io.quarkus.it.micrometer.mpmetrics; +import java.util.Collection; +import java.util.Objects; + import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import org.eclipse.microprofile.metrics.Counter; +import org.eclipse.microprofile.metrics.annotation.Metric; + +import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.search.Search; @Path("/message") public class MessageResource { private final MeterRegistry registry; + private final Counter first; + private final Counter second; - public MessageResource(MeterRegistry registry) { + public MessageResource(MeterRegistry registry, + @Metric(name = "first-counter") final Counter first, + @Metric(name = "second-counter") final Counter second) { this.registry = registry; + this.first = Objects.requireNonNull(first); + this.second = Objects.requireNonNull(second); } @GET public String message() { + first.inc(); + second.inc(); return registry.getClass().getName(); } @GET @Path("fail") public String fail() { + first.inc(); throw new NullPointerException("Failed on purpose"); } @GET @Path("item/{id}") public String item(@PathParam("id") String id) { + second.inc(); return "return message with id " + id; } + + @GET + @Path("mpmetrics") + public String metrics() { + Collection meters = Search.in(registry).name(s -> s.contains("mpmetrics")).meters(); + meters.addAll(Search.in(registry).name(s -> s.endsWith("-counter")).meters()); + return meters.stream().allMatch(x -> x.getId().getTag("scope") != null) ? "OK" : "FAIL"; + } } diff --git a/integration-tests/micrometer-mp-metrics/src/test/java/io/quarkus/it/micrometer/mpmetrics/MPMetricsTest.java b/integration-tests/micrometer-mp-metrics/src/test/java/io/quarkus/it/micrometer/mpmetrics/MPMetricsTest.java index 7d47a0f85cba2..d7c2e9fc02344 100644 --- a/integration-tests/micrometer-mp-metrics/src/test/java/io/quarkus/it/micrometer/mpmetrics/MPMetricsTest.java +++ b/integration-tests/micrometer-mp-metrics/src/test/java/io/quarkus/it/micrometer/mpmetrics/MPMetricsTest.java @@ -70,7 +70,10 @@ void validateMetricsOutput_1() { "io_quarkus_it_micrometer_mpmetrics_PrimeResource_highestPrimeNumberSoFar2{scope=\"application\"} 887.0")) // the counter associated with a timed method should have been removed - .body(not(containsString("io_quarkus_it_micrometer_mpmetrics_PrimeResource_checkPrime"))); + .body(not(containsString("io_quarkus_it_micrometer_mpmetrics_PrimeResource_checkPrime"))) + + // no calls to /message + .body(not(containsString("/message"))); } @Test @@ -83,7 +86,7 @@ void callPrimeGen_4() { } @Test - @Order(8) + @Order(6) void callMessage() { given() .when().get("/message") @@ -91,6 +94,24 @@ void callMessage() { .statusCode(200); } + @Test + @Order(7) + void callMessageFail() { + given() + .when().get("/message/fail") + .then() + .statusCode(500); + } + + @Test + @Order(8) + void callMessageId() { + given() + .when().get("/message/item/35") + .then() + .statusCode(200); + } + @Test @Order(9) void validateMetricsOutput_2() { @@ -106,7 +127,10 @@ void validateMetricsOutput_2() { "highestPrimeNumberSoFar 887.0")) .body(containsString( "io_quarkus_it_micrometer_mpmetrics_InjectedInstance_notPrime_total{scope=\"application\"}")) - .body(not(containsString("/message"))); + .body(containsString( + "first_counter_total{scope=\"application\"}")) + .body(containsString( + "second_counter_total{scope=\"application\"}")); } @Test @@ -123,4 +147,13 @@ void validateJsonOutput() { Matchers.equalTo(887.0f)); } + @Test + @Order(11) + void meters() { + given() + .when().get("/message/mpmetrics") + .then() + .statusCode(200) + .log().body(); + } } From c912ab630bc04a6b53ede39304e310721a3c9c5d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 11 Dec 2023 15:04:31 +0200 Subject: [PATCH 135/212] Improve error message in REST Client when no matching readers found This would have made my life easier when debugging https://github.com/quarkiverse/quarkus-langchain4j/pull/125 (cherry picked from commit 2168a64b88a3ae66290c7195c370aef9afa500cf) --- .../client/impl/ClientReaderInterceptorContextImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java index 200f0c5bcc8eb..99fa3837fd647 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientReaderInterceptorContextImpl.java @@ -91,7 +91,8 @@ public Object proceed() throws IOException, WebApplicationException { } } - StringBuilder errorMessage = new StringBuilder("Response could not be mapped to type " + entityType); + StringBuilder errorMessage = new StringBuilder( + "Response could not be mapped to type " + entityType + " for response with media type " + mediaType); if (!contextualizers.isEmpty()) { var input = new MissingMessageBodyReaderErrorMessageContextualizer.Input() { @Override From 146ed3326aca6fa6408e5e6eb90910f537748b51 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 12 Dec 2023 09:53:46 +0100 Subject: [PATCH 136/212] Use standard URL when updating the website --- docs/sync-web-site.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index 6f310b98c61dd..171aaed2e66c6 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -38,7 +38,7 @@ if [ -z $TARGET_DIR ]; then GIT_OPTIONS="--depth=1" fi if [ -n "${RELEASE_GITHUB_TOKEN}" ]; then - git clone -b develop --single-branch $GIT_OPTIONS https://${RELEASE_GITHUB_TOKEN}:@github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} + git clone -b develop --single-branch $GIT_OPTIONS https://github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} else git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} fi From 181942dab97188fd400c28233f44fa97a0cdc071 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 6 Dec 2023 12:33:09 +0000 Subject: [PATCH 137/212] Consistent file URI for folders and jars (cherry picked from commit 2c3c3c19f37e657bedfb60c5eff77948bb238b11) --- .../java/io/quarkus/runtime/configuration/ConfigDiagnostic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java index 41d698b81a3d1..574ae74cf169e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -184,7 +184,7 @@ public static Set configFiles(Path configFilesLocation) throws IOExcepti Set configFiles = new HashSet<>(); try (DirectoryStream candidates = Files.newDirectoryStream(configFilesLocation, CONFIG_FILES_FILTER)) { for (Path candidate : candidates) { - configFiles.add(candidate.toUri().getPath()); + configFiles.add(candidate.toUri().toURL().toString()); } } return configFiles; From de9e90c46c087509c33284c64de9010bdc5cb6eb Mon Sep 17 00:00:00 2001 From: Bruno Caballero Date: Mon, 11 Dec 2023 11:19:11 +0100 Subject: [PATCH 138/212] Fix GrasalVM version checker in order to accept other implementations (cherry picked from commit b4eb722c7b15752a0310d68368726d2b3de9eece) --- .../java/io/quarkus/deployment/pkg/steps/GraalVM.java | 4 ++-- .../io/quarkus/deployment/pkg/steps/GraalVMTest.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java index d711928b67b39..6b9be123db6b3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java @@ -31,9 +31,9 @@ static final class VersionParseHelper { private static final String VENDOR_VERS = "(?.*)"; private static final String JDK_DEBUG = "[^\\)]*"; // zero or more of >anything not a ')'< - private static final String RUNTIME_NAME = "(?(?:OpenJDK|GraalVM) Runtime Environment) "; + private static final String RUNTIME_NAME = "(?(?:.*) Runtime Environment) "; private static final String BUILD_INFO = "(?.*)"; - private static final String VM_NAME = "(?(?:OpenJDK 64-Bit Server|Substrate) VM) "; + private static final String VM_NAME = "(?(?:.*) VM) "; private static final String FIRST_LINE_PATTERN = "native-image " + VSTR_FORMAT + " .*$"; private static final String SECOND_LINE_PATTERN = RUNTIME_NAME diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java index 7bb5a88fea6be..09b687e03d459 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java @@ -63,6 +63,16 @@ public void testGraalVMVersionDetected() { + "GraalVM Runtime Environment GraalVM CE (build 20+34-jvmci-23.0-b10)\n" + "Substrate VM GraalVM CE (build 20+34, serial gc)").split("\\n")))); + // Should also work for other unknown implementations of GraalVM + assertVersion(new Version("GraalVM 23.0", "23.0", GRAALVM), GRAALVM, + Version.of(Stream.of(("native-image 20 2023-07-30\n" + + "Foo Runtime Environment whatever (build 20+34-jvmci-23.0-b7)\n" + + "Foo VM whatever (build 20+34, serial gc)").split("\\n")))); + assertVersion(new Version("GraalVM 23.0", "23.0", GRAALVM), GRAALVM, + Version.of(Stream.of(("native-image 20 2023-07-30\n" + + "Another Runtime Environment whatever (build 20+34-jvmci-23.0-b7)\n" + + "Another VM whatever (build 20+34, serial gc)").split("\\n")))); + // Older version parsing assertVersion(new Version("GraalVM 20.1", "20.1", GRAALVM), GRAALVM, Version.of(Stream.of("GraalVM Version 20.1.0 (Java Version 11.0.7)"))); From eef1eda7ab1771beb4df8add21ff20517f3ad87e Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 11 Dec 2023 17:48:34 +0200 Subject: [PATCH 139/212] Add test for GraalVM EE dev builds (cherry picked from commit aefe8a3fb83d0e75f66946a93622a35a8231de27) --- .../quarkus/deployment/pkg/steps/GraalVMTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java index 09b687e03d459..2914dfe0ee7cb 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java @@ -140,6 +140,19 @@ public void testGraalVM22DevVersionParser() { assertThat(graalVM22Dev.javaVersion.update()).isEqualTo(0); } + @Test + public void testGraalVMEE22DevVersionParser() { + Version graalVMEE22Dev = Version.of(Stream.of(("native-image 22 2024-03-19\n" + + "Java(TM) SE Runtime Environment Oracle GraalVM 22-dev+25.1 (build 22+25-jvmci-b01)\n" + + "Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 22-dev+25.1 (build 22+25-jvmci-b01, mixed mode, sharing)") + .split("\\n"))); + assertThat(graalVMEE22Dev.distribution.name()).isEqualTo("GRAALVM"); + assertThat(graalVMEE22Dev.getVersionAsString()).isEqualTo("24.0-dev"); + assertThat(graalVMEE22Dev.javaVersion.toString()).isEqualTo("22+25-jvmci-b01"); + assertThat(graalVMEE22Dev.javaVersion.feature()).isEqualTo(22); + assertThat(graalVMEE22Dev.javaVersion.update()).isEqualTo(0); + } + @Test public void testGraalVMVersionsOlderThan() { assertOlderThan("GraalVM Version 19.3.6 CE", "GraalVM Version 20.2.0 (Java Version 11.0.9)"); From 8d5a34f712d4870197edb65410b7a4f2ded7d510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Mon, 11 Dec 2023 22:32:17 +0100 Subject: [PATCH 140/212] Fix DEV UI startup when OIDC Dev UI is disabled (cherry picked from commit 491dc36307c711ba7ab610cc3980c6a384e9d13d) --- .../oidc/runtime/devui/OidcDevJsonRpcService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java index e54c728dbe077..0c26e16a76d05 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java @@ -45,7 +45,15 @@ public OidcDevJsonRpcService(HttpConfiguration httpConfiguration, SmallRyeConfig // we must always produce it when in DEV mode because we can't check for 'KeycloakDevServicesConfigBuildItem' // due to circular reference: JSON RPC provider is additional bean and 'LoggingSetupBuildItem' used by // 'KeycloakDevServicesProcessor' is created with combined index - OidcDevUiRpcSvcPropertiesBean props = Arc.container().instance(OidcDevUiRpcSvcPropertiesBean.class).get(); + final var propsInstanceHandle = Arc.container().instance(OidcDevUiRpcSvcPropertiesBean.class); + final OidcDevUiRpcSvcPropertiesBean props; + if (propsInstanceHandle.isAvailable()) { + props = propsInstanceHandle.get(); + } else { + // OIDC Dev UI is disabled, but this RPC service still gets initialized by Quarkus DEV UI + props = new OidcDevUiRpcSvcPropertiesBean(null, null, null, null, Map.of(), Map.of(), null, null, null, false, null, + List.of(), false, false, null, null, false); + } this.httpPort = httpConfiguration.port; this.config = config; From 14ad405b2538cbeea221bff5a835abd6d5ff2760 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 12 Dec 2023 10:25:35 +0100 Subject: [PATCH 141/212] Do not use build cache when releasing (cherry picked from commit f166f7bca71efd533334bf53f8c0af2b154bf249) --- .mvn/gradle-enterprise.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.mvn/gradle-enterprise.xml b/.mvn/gradle-enterprise.xml index 27ac5553de6a2..62ed9dd2327a0 100644 --- a/.mvn/gradle-enterprise.xml +++ b/.mvn/gradle-enterprise.xml @@ -23,11 +23,11 @@ - #{env['GRADLE_LOCAL_BUILD_CACHE'] != null} + #{env['GRADLE_LOCAL_BUILD_CACHE'] != null and env['RELEASE_GITHUB_TOKEN'] == null} - true - #{env['CI'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != ''} + #{env['RELEASE_GITHUB_TOKEN'] == null} + #{env['CI'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != null and env['GRADLE_ENTERPRISE_ACCESS_KEY'] != '' and env['RELEASE_GITHUB_TOKEN'] == null} From f55eef68d467bbf8b41605908cc7ab2d53e09871 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 22:45:13 +0000 Subject: [PATCH 142/212] Bump io.quarkus:quarkus-platform-bom-maven-plugin Bumps [io.quarkus:quarkus-platform-bom-maven-plugin](https://github.com/quarkusio/quarkus-platform-bom-generator) from 0.0.100 to 0.0.101. - [Release notes](https://github.com/quarkusio/quarkus-platform-bom-generator/releases) - [Commits](https://github.com/quarkusio/quarkus-platform-bom-generator/compare/0.0.100...0.0.101) --- updated-dependencies: - dependency-name: io.quarkus:quarkus-platform-bom-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 6e1f5a80895a16465aa06752227a132c40beee5c) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3e14413b69ea7..e041b8d91bff9 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ jdbc:postgresql:hibernate_orm_test 4.5.1 - 0.0.100 + 0.0.101 false false From 5bc610aa74921a6a23fd8f2fd1f621615009dd88 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 11 Dec 2023 14:10:03 +0100 Subject: [PATCH 143/212] Make sure dev mode is properly written in doc It should be `dev mode` with a space and no capitalization. (cherry picked from commit 33a4b2500ed2202e176cf91e8df19359c9b3a98b) --- docs/src/main/asciidoc/security-getting-started-tutorial.adoc | 4 ++-- docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc | 4 ++-- .../main/asciidoc/security-openid-connect-dev-services.adoc | 2 +- .../main/asciidoc/security-openid-connect-multitenancy.adoc | 2 +- docs/src/main/asciidoc/security-openid-connect-providers.adoc | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/security-getting-started-tutorial.adoc b/docs/src/main/asciidoc/security-getting-started-tutorial.adoc index da06f8669bc1a..30d84156ad5fa 100644 --- a/docs/src/main/asciidoc/security-getting-started-tutorial.adoc +++ b/docs/src/main/asciidoc/security-getting-started-tutorial.adoc @@ -462,9 +462,9 @@ As you can see in this code sample, you do not need to start the test container [NOTE] ==== -When you start your application in dev mode, `Dev Services for PostgreSQL` launches a `PostgreSQL` `devmode` container so that you can start developing your application. +When you start your application in dev mode, Dev Services for PostgreSQL launches a PostgreSQL dev mode container so that you can start developing your application. While developing your application, you can add tests one by one and run them using the xref:continuous-testing.adoc[Continuous Testing] feature. -`Dev Services for PostgreSQL` supports testing while you develop by providing a separate `PostgreSQL` test container that does not conflict with the `devmode` container. +Dev Services for PostgreSQL supports testing while you develop by providing a separate PostgreSQL test container that does not conflict with the dev mode container. ==== === Use Curl or a browser to test your application diff --git a/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc index f652fa7ca262a..e0796c3b007c0 100644 --- a/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-auth0-tutorial.adoc @@ -914,7 +914,7 @@ public class GreetingResourceTest { } ---- -If you recall, when the application was started in devmode, the following could be seen in the CLI window: +If you recall, when the application was started in dev mode, the following could be seen in the CLI window: image::auth0-devmode-started.png[Auth0 DevMode started] @@ -1075,7 +1075,7 @@ Open a browser, access http://localhost:8080/hello and get the name displayed in == Troubleshooting -The steps described in this tutorial should work exactly as the tutorial describes. You might have to clear the browser cookies when accessing the updated Quarkus endpoint if you have already completed the authentication. You might need to restart the Quarkus application manually in devmode but it is not expected. If you need help completing this tutorial, you can get in touch with the Quarkus team. +The steps described in this tutorial should work exactly as the tutorial describes. You might have to clear the browser cookies when accessing the updated Quarkus endpoint if you have already completed the authentication. You might need to restart the Quarkus application manually in dev mode but it is not expected. If you need help completing this tutorial, you can get in touch with the Quarkus team. == Summary diff --git a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc index 0d99374a5b348..dbb796ef7f5c9 100644 --- a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc @@ -204,7 +204,7 @@ To make Dev UI more useful for supporting the development of OIDC `web-app` appl It will ensure that all Dev UI options described in <> will be available when your `web-app` application is run in dev mode. The limitation of this approach is that both access and ID tokens returned with the code flow and acquired with Dev UI will be sent to the endpoint as HTTP `Bearer` tokens - which will not work well if your endpoint requires the injection of `IdToken`. However, it will work as expected if your `web-app` application only uses the access token, for example, as a source of roles or to get `UserInfo`, even if it is assumed to be a `service` application in dev mode. -Even a better option is to use a `hybrid` application type in devmode: +Even a better option is to use a `hybrid` application type in dev mode: [source,properties] ---- diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index 7c9bb2dd06daf..70894fafbc9ab 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -270,7 +270,7 @@ public class CustomTenantResolver implements TenantResolver { You can define multiple tenants in your configuration file, just make sure they have a unique alias so that you can map them properly when resolving a tenant from your `TenantResolver` implementation. -However, using a static tenant resolution (configuring tenants in `application.properties` and resolving them with `TenantResolver`) prevents testing the endpoint with `Dev Services for Keycloak` since `Dev Services for Keycloak` has no knowledge of how the requests will be mapped to individual tenants and can not dynamically provide tenant-specific `quarkus.oidc..auth-server-url` values and therefore using `%prod` prefixes with the tenant-specific URLs in `application.properties` will not work in tests or devmode. +However, using a static tenant resolution (configuring tenants in `application.properties` and resolving them with `TenantResolver`) prevents testing the endpoint with `Dev Services for Keycloak` since `Dev Services for Keycloak` has no knowledge of how the requests will be mapped to individual tenants and can not dynamically provide tenant-specific `quarkus.oidc..auth-server-url` values and therefore using `%prod` prefixes with the tenant-specific URLs in `application.properties` will not work in test or dev mode. [NOTE] ==== diff --git a/docs/src/main/asciidoc/security-openid-connect-providers.adoc b/docs/src/main/asciidoc/security-openid-connect-providers.adoc index 2c0d7860d3c40..79a39a1e0aa7a 100644 --- a/docs/src/main/asciidoc/security-openid-connect-providers.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-providers.adoc @@ -662,7 +662,7 @@ The pattern of authenticating with a given provider, where the endpoint uses eit == HTTPS Redirect URL -Some providers will only accept HTTPS-based redirect URLs. Tools such as https://ngrok.com/[ngrok] https://linuxhint.com/set-up-use-ngrok/[can be set up] to help testing such providers with Quarkus endpoints running on localhost in devmode. +Some providers will only accept HTTPS-based redirect URLs. Tools such as https://ngrok.com/[ngrok] https://linuxhint.com/set-up-use-ngrok/[can be set up] to help testing such providers with Quarkus endpoints running on localhost in dev mode. == Rate Limiting From 610141c617798d0af0cff9711e4f8fdaca5ba1dd Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 13 Dec 2023 10:25:40 +0200 Subject: [PATCH 144/212] Use NoStackTraceException in metrics This is done because the stacktrace produced is completely useless and only makes it harder to focus on the real problem Relates to: https://github.com/quarkiverse/quarkus-langchain4j/issues/140 (cherry picked from commit 542e93b2edd46bbbef0bb350ad6f656be00d0c67) --- .../runtime/binder/vertx/VertxMeterBinderAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java index e3a4b7de2a890..384f10ed0bc43 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxMeterBinderAdapter.java @@ -10,6 +10,7 @@ import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.impl.NoStackTraceException; import io.vertx.core.metrics.MetricsOptions; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetServerOptions; @@ -60,7 +61,7 @@ public MetricsOptions newOptions() { @Override public HttpServerMetrics createHttpServerMetrics(HttpServerOptions options, SocketAddress localAddress) { if (httpBinderConfiguration == null) { - throw new IllegalStateException("HttpBinderConfiguration was not found"); + throw new NoStackTraceException("HttpBinderConfiguration was not found"); } if (httpBinderConfiguration.isServerEnabled()) { log.debugf("Create HttpServerMetrics with options %s and address %s", options, localAddress); From df70f9984b4635b68e3e62e18a23b0d9fd5519b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 13 Dec 2023 15:14:54 +0100 Subject: [PATCH 145/212] Docs: Fix incorrect link reference in Cross-Site Request Forgery Prevention guide (cherry picked from commit 72afeacb8f73efc7db27aead4675eb3c523783b2) --- docs/src/main/asciidoc/security-csrf-prevention.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/security-csrf-prevention.adoc b/docs/src/main/asciidoc/security-csrf-prevention.adoc index 880487683477f..b56e7d49ad941 100644 --- a/docs/src/main/asciidoc/security-csrf-prevention.adoc +++ b/docs/src/main/asciidoc/security-csrf-prevention.adoc @@ -11,7 +11,7 @@ include::_attributes.adoc[] https://owasp.org/www-community/attacks/csrf[Cross-Site Request Forgery (CSRF)] is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. -Quarkus Security provides a CSRF prevention feature which implements https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie[Double Submit Cookie] and [CSRF Request Header] techniques. +Quarkus Security provides a CSRF prevention feature which implements https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie[Double Submit Cookie] and <> techniques. `Double Submit Cookie` technique requires that the CSRF token sent as `HTTPOnly`, optionally signed, cookie to the client, and directly embedded in a hidden form input of server-side rendered HTML forms, or submitted as a request header value. @@ -139,6 +139,7 @@ You can get `HMAC` signatures created for the generated CSRF tokens and have the quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow ---- +[[csrf-request-header]] == CSRF Request Header If HTML `form` tags are not used and you need to pass CSRF token as a header, then inject the header name and token, for example, into HTMX: From f9870f6202cba88aec81346a3094bf94dc330cdd Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 13 Dec 2023 18:18:55 +0000 Subject: [PATCH 146/212] Do not use CSRF cookie as the next token value (cherry picked from commit 0e4097c26f766c5b4ab1a1a646d6cd202ef96240) --- .../CsrfRequestResponseReactiveFilter.java | 13 ++++---- .../java/io/quarkus/it/csrf/TestResource.java | 30 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- .../templates/csrfTokenFirstForm.html | 17 +++++++++++ .../templates/csrfTokenSecondForm.html | 17 +++++++++++ .../io/quarkus/it/csrf/CsrfReactiveTest.java | 30 +++++++++++++++++++ 6 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html create mode 100644 integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index 694b690fe797f..b75764772a912 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -66,8 +66,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi String cookieToken = getCookieToken(routing, config); if (cookieToken != null) { - routing.put(CSRF_TOKEN_KEY, cookieToken); - try { int cookieTokenSize = Base64.getUrlDecoder().decode(cookieToken).length; // HMAC SHA256 output is 32 bytes long @@ -98,10 +96,10 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi // unsafe HTTP method, token is required // Check the header first - String csrfTokenInHeader = requestContext.getHeaderString(config.tokenHeaderName); - if (csrfTokenInHeader != null) { + String csrfTokenHeaderParam = requestContext.getHeaderString(config.tokenHeaderName); + if (csrfTokenHeaderParam != null) { LOG.debugf("CSRF token found in the token header"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenInHeader); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenHeaderParam); return; } @@ -128,9 +126,9 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi ResteasyReactiveRequestContext rrContext = (ResteasyReactiveRequestContext) requestContext .getServerRequestContext(); - String csrfToken = (String) rrContext.getFormParameter(config.formFieldName, true, false); + String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false); LOG.debugf("CSRF token found in the form parameter"); - verifyCsrfToken(requestContext, routing, config, cookieToken, csrfToken); + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam); return; } else if (cookieToken == null) { @@ -159,6 +157,7 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont requestContext.abortWith(badClientRequest()); return; } else { + routing.put(CSRF_TOKEN_KEY, csrfToken); routing.put(CSRF_TOKEN_VERIFIED, true); return; } diff --git a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java index 0f66abf3ed4a1..d3c7b18c47306 100644 --- a/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java +++ b/integration-tests/csrf-reactive/src/main/java/io/quarkus/it/csrf/TestResource.java @@ -29,6 +29,12 @@ public class TestResource { @Inject Template csrfTokenForm; + @Inject + Template csrfTokenFirstForm; + + @Inject + Template csrfTokenSecondForm; + @Inject Template csrfTokenHeader; @@ -49,6 +55,14 @@ public TemplateInstance getCsrfTokenForm() { return csrfTokenForm.instance(); } + @GET + @Path("/csrfTokenFirstForm") + @Produces(MediaType.TEXT_HTML) + @Authenticated + public TemplateInstance getCsrfTokenFirstForm() { + return csrfTokenFirstForm.instance(); + } + @GET @Path("/csrfTokenWithFormRead") @Produces(MediaType.TEXT_HTML) @@ -71,6 +85,22 @@ public String postCsrfTokenForm(@FormParam("name") String name, @HeaderParam("X- return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); } + @POST + @Path("/csrfTokenFirstForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_HTML) + public TemplateInstance postCsrfTokenFirstForm() { + return csrfTokenSecondForm.instance(); + } + + @POST + @Path("/csrfTokenSecondForm") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_PLAIN) + public String postCsrfTokenSecondForm(@FormParam("name") String name, @HeaderParam("X-CSRF-TOKEN") String csrfHeader) { + return name + ":" + routingContext.get("csrf_token_verified", false) + ":tokenHeaderIsSet=" + (csrfHeader != null); + } + @POST @Path("/csrfTokenWithFormRead") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) diff --git a/integration-tests/csrf-reactive/src/main/resources/application.properties b/integration-tests/csrf-reactive/src/main/resources/application.properties index 23f5d88b8c1f0..9a778c50dda03 100644 --- a/integration-tests/csrf-reactive/src/main/resources/application.properties +++ b/integration-tests/csrf-reactive/src/main/resources/application.properties @@ -1,5 +1,5 @@ quarkus.csrf-reactive.cookie-name=csrftoken -quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader +quarkus.csrf-reactive.create-token-path=/service/csrfTokenForm,/service/csrfTokenFirstForm,/service/csrfTokenSecondForm,/service/csrfTokenWithFormRead,/service/csrfTokenMultipart,/service/csrfTokenWithHeader quarkus.csrf-reactive.token-signature-key=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow quarkus.http.auth.basic=true diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html new file mode 100644 index 0000000000000..71dadecdf41e2 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenFirstForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token First Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html new file mode 100644 index 0000000000000..7c28b45f662a6 --- /dev/null +++ b/integration-tests/csrf-reactive/src/main/resources/templates/csrfTokenSecondForm.html @@ -0,0 +1,17 @@ + + + + +CSRF Token Second Form Test + + +

CSRF Test

+ +
+ + +

Your Name:

+

+
+ + diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index e6d50f0fc39b7..770d36441e300 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -61,6 +61,36 @@ public void testCsrfTokenInForm() throws Exception { } } + @Test + public void testCsrfTokenTwoForms() throws Exception { + try (final WebClient webClient = createWebClient()) { + webClient.addRequestHeader("Authorization", basicAuth("alice", "alice")); + HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenFirstForm"); + + assertEquals("CSRF Token First Form Test", htmlPage.getTitleText()); + + HtmlForm loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + + htmlPage = loginForm.getInputByName("submit").click(); + + assertEquals("CSRF Token Second Form Test", htmlPage.getTitleText()); + + loginForm = htmlPage.getForms().get(0); + + loginForm.getInputByName("name").setValueAttribute("alice"); + + TextPage textPage = loginForm.getInputByName("submit").click(); + assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); + assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + + webClient.getCookieManager().clearCookies(); + } + } + @Test public void testCsrfTokenWithFormRead() throws Exception { try (final WebClient webClient = createWebClient()) { From 42a4539feb898ce6fc6d8d88a251df93d79f0675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 13 Dec 2023 13:54:23 +0100 Subject: [PATCH 147/212] Remove the driver property in the documentation for Cloud SQL (cherry picked from commit 7e3b81a4ba80218ec49b957bd606bf9ed2da4c69) --- docs/src/main/asciidoc/deploying-to-google-cloud.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc index 534901043c870..a2259e91ce324 100644 --- a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc +++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc @@ -251,7 +251,6 @@ Finally, you need to configure your datasource specifically to use the socket fa ---- quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.url=jdbc:postgresql:///mydatabase <1> -quarkus.datasource.jdbc.driver=org.postgresql.Driver quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.additional-jdbc-properties.cloudSqlInstance=project-id:gcp-region:instance <2> From ce9fd600fb1c96193389f91e3c7aafc43521555b Mon Sep 17 00:00:00 2001 From: Benedikt Schneppe Date: Fri, 15 Dec 2023 13:22:35 +0100 Subject: [PATCH 148/212] docs(rabbitmq): fix typo replace MINE type with MIME type (cherry picked from commit aecf4a6a52e8ea8b97038f22593bf004d01347c9) --- docs/src/main/asciidoc/rabbitmq-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rabbitmq-reference.adoc b/docs/src/main/asciidoc/rabbitmq-reference.adoc index bd421a5b2b202..d264e5aa97e47 100644 --- a/docs/src/main/asciidoc/rabbitmq-reference.adoc +++ b/docs/src/main/asciidoc/rabbitmq-reference.adoc @@ -714,7 +714,7 @@ Type: _boolean_ | false | `false` Type: _string_ | false | `#` -| [.no-hyphens]#*content-type-override*# | Override the content_type attribute of the incoming message, should be a valid MINE type +| [.no-hyphens]#*content-type-override*# | Override the content_type attribute of the incoming message, should be a valid MIME type Type: _string_ | false | From aa99a21885c190bce88c4bee9d3556b1a5cf58cb Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 15 Dec 2023 12:33:54 +0200 Subject: [PATCH 149/212] Add SequencedCollection to BANNED_INTERFACE_TYPES This is done because this type exists in Java 21 but not Java 17. Fixes: #37768 (cherry picked from commit 674eac22b98f33ce589e66ecfe1f3053e31372c0) --- .../src/main/java/io/quarkus/arc/processor/Types.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 27340b943a063..76693f02a196b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -84,7 +84,8 @@ public final class Types { // TODO: add a extensible banning mechanism based on predicates if we find that this set needs to grow... private static final Set BANNED_INTERFACE_TYPES = new HashSet<>( Arrays.asList(DotName.createSimple("java.lang.constant.ConstantDesc"), - DotName.createSimple("java.lang.constant.Constable"))); + DotName.createSimple("java.lang.constant.Constable"), + DotName.createSimple("java.util.SequencedCollection"))); private Types() { } From ad057b6b6b1e44275e3e79afbe21e59e73bc9ddd Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sat, 16 Dec 2023 18:35:55 +0200 Subject: [PATCH 150/212] Take priority into account in ConfigurationImpl This is necessary for REST Client MessageBodyReader and MessageBodyWriter registration (cherry picked from commit e7fcd327371250b7551475a5e008b471d9ea5eb7) --- .../ContextProvidersPriorityTest.java | 26 +++++++++++++++++++ .../common/jaxrs/ConfigurationImpl.java | 6 +++++ 2 files changed, 32 insertions(+) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java index c10b4923d2f1f..740d1782f26b9 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ContextProvidersPriorityTest.java @@ -4,6 +4,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.net.URI; import java.util.List; import java.util.Map; @@ -13,17 +17,24 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Priorities; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.ContextResolver; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; import io.quarkus.test.QuarkusUnitTest; @@ -78,6 +89,7 @@ public Map> callClient(String uri) { } } + @RegisterProvider(ErroneousJacksonBasicMessageBodyReader.class) public interface Client { @GET Map> get(); @@ -101,6 +113,20 @@ public ClientHeadersFactory getContext(Class aClass) { } } + @Priority(Priorities.USER + 100) + public static class ErroneousJacksonBasicMessageBodyReader extends JacksonBasicMessageBodyReader { + public ErroneousJacksonBasicMessageBodyReader() { + super(new ObjectMapper()); + } + + @Override + public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should never be called"); + } + } + public static class CustomClientHeadersFactory implements ClientHeadersFactory { private final String value; diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java index 72a269f62e419..26af7a3155e4e 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java @@ -280,6 +280,9 @@ private void register(Object component, Integer priority) { resourceReader .setMediaTypeStrings( consumes != null ? Arrays.asList(consumes.value()) : WILDCARD_STRING_LIST); + if (priority != null) { + resourceReader.setPriority(priority); + } Type[] args = Types.findParameterizedTypes(componentClass, MessageBodyReader.class); resourceReaders.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, resourceReader); @@ -298,6 +301,9 @@ private void register(Object component, Integer priority) { resourceWriter .setMediaTypeStrings( produces != null ? Arrays.asList(produces.value()) : WILDCARD_STRING_LIST); + if (priority != null) { + resourceWriter.setPriority(priority); + } Type[] args = Types.findParameterizedTypes(componentClass, MessageBodyWriter.class); resourceWriters.add(args != null && args.length == 1 ? Types.getRawType(args[0]) : Object.class, resourceWriter); From 958034351df3b37ee04a3f9dbd7d4d5fb1c976dc Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 17 Dec 2023 18:53:04 +0100 Subject: [PATCH 151/212] Only update ~/.docker/config.json if it exists Not having this file is probably due to GitHub infra issues but let's not fail the build for that. (cherry picked from commit 0f388725cea5fb875702e2fef264bc5fdf23b526) --- .github/workflows/ci-actions-incremental.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index c4e8f1af7e5a2..ce4078e4abf9e 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -835,7 +835,9 @@ jobs: # We do this so we can get better analytics for the downloaded version of the build images - name: Update Docker Client User Agent run: | - cat <<< $(jq '.HttpHeaders += {"User-Agent": "Quarkus-CI-Docker-Client"}' ~/.docker/config.json) > ~/.docker/config.json + if [ -f ~/.docker/config.json ]; then + cat <<< $(jq '.HttpHeaders += {"User-Agent": "Quarkus-CI-Docker-Client"}' ~/.docker/config.json) > ~/.docker/config.json + fi - name: Build env: TEST_MODULES: ${{matrix.test-modules}} From 8f275232a700a84e7e923d5c82313ea8c57c873f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 18 Dec 2023 10:39:33 +0100 Subject: [PATCH 152/212] Dev mode: add null checks to TimestampSet.isRestartNeeded() - we observed NPE in some edge cases during live reload (cherry picked from commit 3fb7924bbf12f6289a0160f807ed83ad80de3363) --- .../deployment/dev/RuntimeUpdatesProcessor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index 24a1c175a3059..0e44200ee8043 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -1309,12 +1309,14 @@ boolean isRestartNeeded(String changedFile) { } } // Then try to match a new file that was added to a resource root - Boolean ret = watchedFilePaths.get(changedFile); + Boolean ret = watchedFilePaths != null ? watchedFilePaths.get(changedFile) : null; if (ret == null) { - ret = false; - for (Entry, Boolean> e : watchedFilePredicates) { - if (e.getKey().test(changedFile)) { - ret = ret || e.getValue(); + ret = Boolean.FALSE; + if (watchedFilePredicates != null) { + for (Entry, Boolean> e : watchedFilePredicates) { + if (e.getKey().test(changedFile)) { + ret = ret || e.getValue(); + } } } } From 768e2a2f1cc0a3beb8530e70d0f412b99e00d101 Mon Sep 17 00:00:00 2001 From: Frantisek Havel <42615282+fhavel@users.noreply.github.com> Date: Thu, 14 Dec 2023 09:18:58 +0100 Subject: [PATCH 153/212] Query logging is beeing done in io.quarkus.mongodb.panache.common.runtime.MongoOperations (cherry picked from commit f3746cb7d909fb1044fe274ada9d79d967fe7b51) --- docs/src/main/asciidoc/mongodb-panache.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 0acbb0a762fe9..1c2140715c483 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -746,7 +746,7 @@ This can be achieved by setting to DEBUG the following log category inside your [source,properties] ---- -quarkus.log.category."io.quarkus.mongodb.panache.runtime".level=DEBUG +quarkus.log.category."io.quarkus.mongodb.panache.common.runtime".level=DEBUG ---- == The PojoCodecProvider: easy object to BSON document conversion. From 27bf076c8a1b7ea44d8debe0a8092aa2ed993f4b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 18 Dec 2023 14:47:44 +0100 Subject: [PATCH 154/212] CLI - Rework how missing commands are detected We had a problem here as most options are only valid in the context of the root command (for instance -D) so trying to parse them in the context of a subcommand or on their own will most probably fail. Also parsing recursively the command is non-efficient as we do a lot of parsing, especially since it was done twice (this was fixed too). The new approach uses the ParseResult generated once and get the knownledge from there. From my tests, it provides similar results and avoid false positives and running the parsing too many times. This should fix CliHelpTest#testCommandHelp() being flaky as not being able to interpret the options ended up triggering the missing command branch and then the JBang support. (cherry picked from commit fb4a3799d3ea42b8267000455e734486bdef1dcb) --- .../main/java/io/quarkus/cli/QuarkusCli.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java index b08ebf2ec4c3a..2948d61ed43b5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -12,6 +12,7 @@ import java.util.Optional; import java.util.concurrent.Callable; import java.util.function.Supplier; +import java.util.stream.Collectors; import jakarta.inject.Inject; @@ -96,7 +97,9 @@ public int run(String... args) throws Exception { boolean pluginCommand = args.length >= 1 && (args[0].equals("plug") || args[0].equals("plugin")); try { - boolean existingCommand = checkMissingCommand(cmd, args).isEmpty(); + Optional missingCommand = checkMissingCommand(cmd, args); + + boolean existingCommand = missingCommand.isEmpty(); // If the command already exists and is not a help command (that lists subcommands) or plugin command, then just execute // without dealing with plugins. // The reason that we check if its a plugin command is that plugin commands need PluginManager initialization. @@ -108,8 +111,7 @@ public int run(String... args) throws Exception { pluginManager.syncIfNeeded(); Map plugins = new HashMap<>(pluginManager.getInstalledPlugins()); pluginCommandFactory.populateCommands(cmd, plugins); - Optional missing = checkMissingCommand(cmd, args); - missing.ifPresent(m -> { + missingCommand.ifPresent(m -> { try { Map installable = pluginManager.getInstallablePlugins(); if (installable.containsKey(m)) { @@ -119,11 +121,13 @@ public int run(String... args) throws Exception { output.info("Command %s not installed but the following plugin is available:\n%s", m, table.getContent()); if (interactiveMode && Prompt.yesOrNo(true, - "Would you like to install it now ?", + "Would you like to install it now?", args)) { pluginManager.addPlugin(m).ifPresent(added -> plugins.put(added.getName(), added)); pluginCommandFactory.populateCommands(cmd, plugins); } + } else { + output.error("Command %s is missing and can't be installed.", m); } } catch (Exception e) { output.error("Command %s is missing and can't be installed.", m); @@ -136,7 +140,7 @@ public int run(String... args) throws Exception { } /** - * Recursivelly processes the arguments passed to the command and checks wether a subcommand is missing. + * Process the arguments passed and return an identifier of the potentially missing subcommand if any. * * @param root the root command * @param args the arguments passed to the root command @@ -148,17 +152,26 @@ public Optional checkMissingCommand(CommandLine root, String[] args) { } try { - ParseResult result = root.parseArgs(args); - if (args.length == 1) { - return Optional.empty(); - } - CommandLine next = root.getSubcommands().get(args[0]); - if (next == null) { - return Optional.of(args[0]); - } - String[] remaining = new String[args.length - 1]; - System.arraycopy(args, 1, remaining, 0, remaining.length); - return checkMissingCommand(next, remaining).map(nextMissing -> root.getCommandName() + "-" + nextMissing); + ParseResult currentParseResult = root.parseArgs(args); + StringBuilder missingCommand = new StringBuilder(); + + do { + if (missingCommand.length() > 0) { + missingCommand.append("-"); + } + missingCommand.append(currentParseResult.commandSpec().name()); + + List unmatchedSubcommands = currentParseResult.unmatched().stream() + .filter(u -> !u.startsWith("-")).collect(Collectors.toList()); + if (!unmatchedSubcommands.isEmpty()) { + missingCommand.append("-").append(unmatchedSubcommands.get(0)); + return Optional.of(missingCommand.toString()); + } + + currentParseResult = currentParseResult.subcommand(); + } while (currentParseResult != null); + + return Optional.empty(); } catch (UnmatchedArgumentException e) { return Optional.of(args[0]); } From 5ae4d998bd9e3929282380f63f6e03c90b467fc0 Mon Sep 17 00:00:00 2001 From: franz1981 Date: Tue, 5 Dec 2023 11:54:34 +0100 Subject: [PATCH 155/212] Zero-Garbage ContextInstances::forEach --- .../processor/ContextInstancesGenerator.java | 29 ++++++++++++++++++- .../arc/processor/MethodDescriptors.java | 4 +++ .../arc/impl/AbstractSharedContext.java | 11 +++---- .../impl/ComputingCacheContextInstances.java | 6 ++++ .../io/quarkus/arc/impl/ContextInstances.java | 3 ++ .../io/quarkus/arc/impl/RequestContext.java | 2 +- 6 files changed, 48 insertions(+), 7 deletions(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java index 01a7ba04fc80a..81e66fc8f5e0f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java @@ -5,6 +5,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_VOLATILE; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -13,6 +14,7 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import java.util.function.Supplier; import org.jboss.jandex.DotName; @@ -88,7 +90,7 @@ Collection generate(DotName scope) { implementRemove(contextInstances, beans, idToField, lockField.getFieldDescriptor()); implementClear(contextInstances, idToField, lockField.getFieldDescriptor()); implementGetAllPresent(contextInstances, idToField, lockField.getFieldDescriptor()); - + implementForEach(contextInstances, idToField, lockField.getFieldDescriptor()); contextInstances.close(); return classOutput.getResources(); @@ -149,6 +151,31 @@ private void implementClear(ClassCreator applicationContextInstances, Map idToField, + FieldDescriptor lockField) { + MethodCreator forEach = contextInstances.getMethodCreator("forEach", void.class, Consumer.class) + .setModifiers(ACC_PUBLIC); + // lock.lock(); + // ContextInstanceHandle copy = this.1; + // lock.unlock(); + // if (copy != null) { + // consumer.accept(copy); + // } + ResultHandle lock = forEach.readInstanceField(lockField, forEach.getThis()); + forEach.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + List results = new ArrayList<>(idToField.size()); + for (FieldDescriptor field : idToField.values()) { + results.add(forEach.readInstanceField(field, forEach.getThis())); + } + forEach.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + for (int i = 0; i < results.size(); i++) { + ResultHandle copy = results.get(i); + BytecodeCreator isNotNull = forEach.ifNotNull(copy).trueBranch(); + isNotNull.invokeInterfaceMethod(MethodDescriptors.CONSUMER_ACCEPT, forEach.getMethodParam(0), copy); + } + forEach.returnVoid(); + } + private void implementRemove(ClassCreator contextInstances, List applicationScopedBeans, Map idToField, FieldDescriptor lockField) { MethodCreator remove = contextInstances diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 8cc52eb815fb9..79b2f7af860b2 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -63,6 +64,9 @@ public final class MethodDescriptors { public static final MethodDescriptor SUPPLIER_GET = MethodDescriptor.ofMethod(Supplier.class, "get", Object.class); + public static final MethodDescriptor CONSUMER_ACCEPT = MethodDescriptor.ofMethod(Consumer.class, "accept", + void.class, Object.class); + public static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, CreationalContext.class); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java index 748fae45d3fb7..111f18f174d1b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java @@ -1,9 +1,6 @@ package io.quarkus.arc.impl; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -87,7 +84,11 @@ public void destroy(Contextual contextual) { @Override public synchronized void destroy() { - Set> values = instances.getAllPresent(); + List> values = new LinkedList<>(); + instances.forEach(values::add); + if (values.isEmpty()) { + return; + } // Destroy the producers first for (Iterator> iterator = values.iterator(); iterator.hasNext();) { ContextInstanceHandle instanceHandle = iterator.next(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java index fd89543cbc65b..5fdfdbb6d4761 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java @@ -1,6 +1,7 @@ package io.quarkus.arc.impl; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import io.quarkus.arc.ContextInstanceHandle; @@ -38,4 +39,9 @@ public void clear() { instances.clear(); } + @Override + public void forEach(Consumer> handleConsumer) { + instances.getPresentValues().forEach(handleConsumer); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java index 76ca4ad531e6f..7c0557215ef57 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java @@ -1,6 +1,7 @@ package io.quarkus.arc.impl; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import io.quarkus.arc.ContextInstanceHandle; @@ -17,4 +18,6 @@ public interface ContextInstances { void clear(); + void forEach(Consumer> handleConsumer); + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java index 08e2043ff9179..40aa2e3240482 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java @@ -210,7 +210,7 @@ public void destroy(ContextState state) { if (reqState.invalidate()) { // Fire an event with qualifier @BeforeDestroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(beforeDestroyedNotifier); - reqState.contextInstances.getAllPresent().forEach(this::destroyContextElement); + reqState.contextInstances.forEach(this::destroyContextElement); reqState.contextInstances.clear(); // Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(destroyedNotifier); From c65bc8676738b8fbd86c9e4013b2238de95b250f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 2 Jan 2024 16:29:53 +0100 Subject: [PATCH 156/212] ArC: fix and optimize the ContextInstances abstraction - fixes #37958 and #38040 - use a separate lock for each bean in the generated ContextInstances - replace ContextInstances#forEach() and ContextInstances#clear() with ContextInstances#removeEach() - optimize the generated ContextInstances to significantly reduce the size of the generated bytecode --- .../processor/ContextInstancesGenerator.java | 275 ++++++++++-------- .../arc/impl/AbstractInstanceHandle.java | 4 - .../arc/impl/AbstractSharedContext.java | 13 +- .../impl/ComputingCacheContextInstances.java | 10 +- .../arc/impl/ContextInstanceHandleImpl.java | 10 +- .../io/quarkus/arc/impl/ContextInstances.java | 29 +- .../io/quarkus/arc/impl/RequestContext.java | 11 +- .../ApplicationContextInstancesTest.java | 46 ++- .../RequestContextInstancesTest.java | 109 +++++++ 9 files changed, 347 insertions(+), 160 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java index 81e66fc8f5e0f..24944d995e807 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java @@ -35,7 +35,7 @@ public class ContextInstancesGenerator extends AbstractGenerator { - static final String APP_CONTEXT_INSTANCES_SUFFIX = "_ContextInstances"; + static final String CONTEXT_INSTANCES_SUFFIX = "_ContextInstances"; private final BeanDeployment beanDeployment; private final Map scopeToGeneratedName; @@ -50,7 +50,7 @@ public ContextInstancesGenerator(boolean generateSources, ReflectionRegistration void precomputeGeneratedName(DotName scope) { String generatedName = DEFAULT_PACKAGE + "." + beanDeployment.name + UNDERSCORE + scope.toString().replace(".", UNDERSCORE) - + APP_CONTEXT_INSTANCES_SUFFIX; + + CONTEXT_INSTANCES_SUFFIX; scopeToGeneratedName.put(scope, generatedName); } @@ -63,121 +63,127 @@ Collection generate(DotName scope) { ClassCreator contextInstances = ClassCreator.builder().classOutput(classOutput).className(generatedName) .interfaces(ContextInstances.class).build(); - // Add fields for all beans + // Add ContextInstanceHandle and Lock fields for every bean // The name of the field is a generated index // For example: // private volatile ContextInstanceHandle 1; - Map idToField = new HashMap<>(); + // private final Lock 1l = new ReentrantLock(); + Map idToFields = new HashMap<>(); int fieldIndex = 0; for (BeanInfo bean : beans) { - FieldCreator fc = contextInstances.getFieldCreator("" + fieldIndex++, ContextInstanceHandle.class) + String beanIdx = "" + fieldIndex++; + FieldCreator handleField = contextInstances.getFieldCreator(beanIdx, ContextInstanceHandle.class) .setModifiers(ACC_PRIVATE | ACC_VOLATILE); - idToField.put(bean.getIdentifier(), fc.getFieldDescriptor()); + FieldCreator lockField = contextInstances.getFieldCreator(beanIdx + "l", Lock.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + idToFields.put(bean.getIdentifier(), + new InstanceAndLock(handleField.getFieldDescriptor(), lockField.getFieldDescriptor())); } - FieldCreator lockField = contextInstances.getFieldCreator("lock", Lock.class) - .setModifiers(ACC_PRIVATE | ACC_FINAL); - MethodCreator constructor = contextInstances.getMethodCreator(MethodDescriptor.INIT, "V"); constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); - constructor.writeInstanceField(lockField.getFieldDescriptor(), constructor.getThis(), - constructor.newInstance(MethodDescriptor.ofConstructor(ReentrantLock.class))); + for (InstanceAndLock fields : idToFields.values()) { + constructor.writeInstanceField(fields.lock, constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(ReentrantLock.class))); + } constructor.returnVoid(); - implementComputeIfAbsent(contextInstances, beans, idToField, - lockField.getFieldDescriptor()); - implementGetIfPresent(contextInstances, beans, idToField); - implementRemove(contextInstances, beans, idToField, lockField.getFieldDescriptor()); - implementClear(contextInstances, idToField, lockField.getFieldDescriptor()); - implementGetAllPresent(contextInstances, idToField, lockField.getFieldDescriptor()); - implementForEach(contextInstances, idToField, lockField.getFieldDescriptor()); + implementComputeIfAbsent(contextInstances, beans, idToFields); + implementGetIfPresent(contextInstances, beans, idToFields); + implementRemove(contextInstances, beans, idToFields); + implementGetAllPresent(contextInstances, idToFields); + implementRemoveEach(contextInstances, idToFields); + + // These methods are needed to significantly reduce the size of the stack map table for getAllPresent() and removeEach() + implementLockAll(contextInstances, idToFields); + implementUnlockAll(contextInstances, idToFields); + contextInstances.close(); return classOutput.getResources(); } - private void implementGetAllPresent(ClassCreator contextInstances, Map idToField, - FieldDescriptor lockField) { + private void implementGetAllPresent(ClassCreator contextInstances, Map idToFields) { MethodCreator getAllPresent = contextInstances.getMethodCreator("getAllPresent", Set.class) .setModifiers(ACC_PUBLIC); - // lock.lock(); - // try { - // Set> ret = new HashSet<>(); - // ContextInstanceHandle copy = this.1; - // if (copy != null) { - // ret.add(copy); - // } - // return ret; - // } catch(Throwable t) { - // lock.unlock(); - // throw t; + // this.lockAll(); + // ContextInstanceHandle copy1 = this.1; + // this.unlockAll(); + // Set> ret = new HashSet<>(); + // if (copy1 != null) { + // ret.add(copy1); // } - ResultHandle lock = getAllPresent.readInstanceField(lockField, getAllPresent.getThis()); - getAllPresent.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); - TryBlock tryBlock = getAllPresent.tryBlock(); - ResultHandle ret = tryBlock.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (FieldDescriptor field : idToField.values()) { - ResultHandle copy = tryBlock.readInstanceField(field, tryBlock.getThis()); - tryBlock.ifNotNull(copy).trueBranch().invokeInterfaceMethod(MethodDescriptors.SET_ADD, ret, copy); + // return ret; + getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), + getAllPresent.getThis()); + List results = new ArrayList<>(idToFields.size()); + for (InstanceAndLock fields : idToFields.values()) { + results.add(getAllPresent.readInstanceField(fields.instance, getAllPresent.getThis())); } - tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - tryBlock.returnValue(ret); - CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); - catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - catchBlock.throwException(catchBlock.getCaughtException()); + getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), + getAllPresent.getThis()); + ResultHandle ret = getAllPresent.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (ResultHandle result : results) { + getAllPresent.ifNotNull(result).trueBranch().invokeInterfaceMethod(MethodDescriptors.SET_ADD, ret, result); + } + getAllPresent.returnValue(ret); } - private void implementClear(ClassCreator applicationContextInstances, Map idToField, - FieldDescriptor lockField) { - MethodCreator clear = applicationContextInstances.getMethodCreator("clear", void.class).setModifiers(ACC_PUBLIC); - // lock.lock(); - // try { - // this.1 = null; - // lock.unlock(); - // } catch(Throwable t) { - // lock.unlock(); - // throw t; - // } - ResultHandle lock = clear.readInstanceField(lockField, clear.getThis()); - clear.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); - TryBlock tryBlock = clear.tryBlock(); - for (FieldDescriptor field : idToField.values()) { - tryBlock.writeInstanceField(field, tryBlock.getThis(), tryBlock.loadNull()); + private void implementLockAll(ClassCreator contextInstances, Map idToFields) { + MethodCreator lockAll = contextInstances.getMethodCreator("lockAll", void.class) + .setModifiers(ACC_PRIVATE); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle lock = lockAll.readInstanceField(fields.lock, lockAll.getThis()); + lockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + } + lockAll.returnVoid(); + } + + private void implementUnlockAll(ClassCreator contextInstances, Map idToFields) { + MethodCreator unlockAll = contextInstances.getMethodCreator("unlockAll", void.class) + .setModifiers(ACC_PRIVATE); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle lock = unlockAll.readInstanceField(fields.lock, unlockAll.getThis()); + unlockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); } - tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - tryBlock.returnVoid(); - CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); - catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - catchBlock.throwException(catchBlock.getCaughtException()); + unlockAll.returnVoid(); } - private void implementForEach(ClassCreator contextInstances, Map idToField, - FieldDescriptor lockField) { - MethodCreator forEach = contextInstances.getMethodCreator("forEach", void.class, Consumer.class) + private void implementRemoveEach(ClassCreator contextInstances, Map idToFields) { + MethodCreator removeEach = contextInstances.getMethodCreator("removeEach", void.class, Consumer.class) .setModifiers(ACC_PUBLIC); - // lock.lock(); - // ContextInstanceHandle copy = this.1; - // lock.unlock(); - // if (copy != null) { - // consumer.accept(copy); + // this.lockAll(); + // ContextInstanceHandle copy1 = this.1; + // if (copy1 != null) { + // this.1 = null; // } - ResultHandle lock = forEach.readInstanceField(lockField, forEach.getThis()); - forEach.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); - List results = new ArrayList<>(idToField.size()); - for (FieldDescriptor field : idToField.values()) { - results.add(forEach.readInstanceField(field, forEach.getThis())); + // this.unlockAll(); + // if (action != null) + // if (copy1 != null) { + // consumer.accept(copy1); + // } + // } + removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), + removeEach.getThis()); + List results = new ArrayList<>(idToFields.size()); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle copy = removeEach.readInstanceField(fields.instance, removeEach.getThis()); + results.add(copy); + BytecodeCreator isNotNull = removeEach.ifNotNull(copy).trueBranch(); + isNotNull.writeInstanceField(fields.instance, isNotNull.getThis(), isNotNull.loadNull()); } - forEach.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - for (int i = 0; i < results.size(); i++) { - ResultHandle copy = results.get(i); - BytecodeCreator isNotNull = forEach.ifNotNull(copy).trueBranch(); - isNotNull.invokeInterfaceMethod(MethodDescriptors.CONSUMER_ACCEPT, forEach.getMethodParam(0), copy); + removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), + removeEach.getThis()); + BytecodeCreator actionIsNotNull = removeEach.ifNotNull(removeEach.getMethodParam(0)).trueBranch(); + for (ResultHandle result : results) { + BytecodeCreator isNotNull = actionIsNotNull.ifNotNull(result).trueBranch(); + isNotNull.invokeInterfaceMethod(MethodDescriptors.CONSUMER_ACCEPT, removeEach.getMethodParam(0), result); } - forEach.returnVoid(); + removeEach.returnVoid(); } - private void implementRemove(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField, FieldDescriptor lockField) { + private void implementRemove(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator remove = contextInstances .getMethodCreator("remove", ContextInstanceHandle.class, String.class) .setModifiers(ACC_PUBLIC); @@ -185,45 +191,37 @@ private void implementRemove(ClassCreator contextInstances, List appli StringSwitch strSwitch = remove.stringSwitch(remove.getMethodParam(0)); // https://github.com/quarkusio/gizmo/issues/164 strSwitch.fallThrough(); - for (BeanInfo bean : applicationScopedBeans) { - FieldDescriptor instanceField = idToField.get(bean.getIdentifier()); - // There is a separate remove method for every bean instance field - MethodCreator removeBean = contextInstances.getMethodCreator("r" + instanceField.getName(), + for (BeanInfo bean : beans) { + InstanceAndLock fields = idToFields.get(bean.getIdentifier()); + FieldDescriptor instanceField = fields.instance; + // There is a separate remove method for every instance handle field + // To eliminate large stack map table in the bytecode + MethodCreator removeHandle = contextInstances.getMethodCreator("r" + instanceField.getName(), ContextInstanceHandle.class).setModifiers(ACC_PRIVATE); - // lock.lock(); - // try { - // ContextInstanceHandle copy = this.1; - // if (copy != null) { - // this.1 = null; - // } - // lock.unlock(); - // return copy; - // } catch(Throwable t) { - // lock.unlock(); - // throw t; + // this.1l.lock(); + // ContextInstanceHandle copy = this.1; + // if (copy != null) { + // this.1 = null; // } - - ResultHandle lock = removeBean.readInstanceField(lockField, removeBean.getThis()); - removeBean.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); - TryBlock tryBlock = removeBean.tryBlock(); - ResultHandle copy = tryBlock.readInstanceField(instanceField, tryBlock.getThis()); - BytecodeCreator isNotNull = tryBlock.ifNotNull(copy).trueBranch(); + // this.1l.unlock(); + // return copy; + ResultHandle lock = removeHandle.readInstanceField(fields.lock, removeHandle.getThis()); + removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + ResultHandle copy = removeHandle.readInstanceField(instanceField, removeHandle.getThis()); + BytecodeCreator isNotNull = removeHandle.ifNotNull(copy).trueBranch(); isNotNull.writeInstanceField(instanceField, isNotNull.getThis(), isNotNull.loadNull()); - tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - tryBlock.returnValue(copy); - CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); - catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - catchBlock.throwException(catchBlock.getCaughtException()); + removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + removeHandle.returnValue(copy); strSwitch.caseOf(bean.getIdentifier(), bc -> { - bc.returnValue(bc.invokeVirtualMethod(removeBean.getMethodDescriptor(), bc.getThis())); + bc.returnValue(bc.invokeVirtualMethod(removeHandle.getMethodDescriptor(), bc.getThis())); }); } strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } - private void implementGetIfPresent(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField) { + private void implementGetIfPresent(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator getIfPresent = contextInstances .getMethodCreator("getIfPresent", ContextInstanceHandle.class, String.class) .setModifiers(ACC_PUBLIC); @@ -231,16 +229,16 @@ private void implementGetIfPresent(ClassCreator contextInstances, List StringSwitch strSwitch = getIfPresent.stringSwitch(getIfPresent.getMethodParam(0)); // https://github.com/quarkusio/gizmo/issues/164 strSwitch.fallThrough(); - for (BeanInfo bean : applicationScopedBeans) { + for (BeanInfo bean : beans) { strSwitch.caseOf(bean.getIdentifier(), bc -> { - bc.returnValue(bc.readInstanceField(idToField.get(bean.getIdentifier()), bc.getThis())); + bc.returnValue(bc.readInstanceField(idToFields.get(bean.getIdentifier()).instance, bc.getThis())); }); } strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } - private void implementComputeIfAbsent(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField, FieldDescriptor lockField) { + private void implementComputeIfAbsent(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator computeIfAbsent = contextInstances .getMethodCreator("computeIfAbsent", ContextInstanceHandle.class, String.class, Supplier.class) .setModifiers(ACC_PUBLIC); @@ -248,41 +246,41 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List copy = this.1; // if (copy != null) { // return copy; // } - // lock.lock(); + // this.1l.lock(); // try { // if (this.1 == null) { // this.1 = supplier.get(); // } - // lock.unlock(); + // this.1l.unlock(); // return this.1; // } catch(Throwable t) { - // lock.unlock(); + // this.1l.unlock(); // throw t; // } - ResultHandle copy = compute.readInstanceField(instanceField, compute.getThis()); + ResultHandle copy = compute.readInstanceField(fields.instance, compute.getThis()); compute.ifNotNull(copy).trueBranch().returnValue(copy); - ResultHandle lock = compute.readInstanceField(lockField, compute.getThis()); + ResultHandle lock = compute.readInstanceField(fields.lock, compute.getThis()); compute.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); TryBlock tryBlock = compute.tryBlock(); - ResultHandle val = tryBlock.readInstanceField(instanceField, compute.getThis()); + ResultHandle val = tryBlock.readInstanceField(fields.instance, compute.getThis()); BytecodeCreator isNull = tryBlock.ifNull(val).trueBranch(); ResultHandle newVal = isNull.invokeInterfaceMethod(MethodDescriptors.SUPPLIER_GET, compute.getMethodParam(0)); - isNull.writeInstanceField(instanceField, compute.getThis(), newVal); + isNull.writeInstanceField(fields.instance, compute.getThis(), newVal); tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); catchBlock.throwException(catchBlock.getCaughtException()); - compute.returnValue(compute.readInstanceField(instanceField, compute.getThis())); + compute.returnValue(compute.readInstanceField(fields.instance, compute.getThis())); strSwitch.caseOf(bean.getIdentifier(), bc -> { bc.returnValue(bc.invokeVirtualMethod(compute.getMethodDescriptor(), bc.getThis(), bc.getMethodParam(1))); @@ -291,4 +289,23 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } + final class InstanceAndLock { + + private final FieldDescriptor instance, lock; + + InstanceAndLock(FieldDescriptor instance, FieldDescriptor lock) { + this.instance = instance; + this.lock = lock; + } + + FieldDescriptor instance() { + return instance; + } + + FieldDescriptor lock() { + return lock; + } + + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java index 55cc81bed93f8..e9e04ef605da8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java @@ -7,8 +7,6 @@ import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.spi.CreationalContext; -import org.jboss.logging.Logger; - import io.quarkus.arc.Arc; import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableContext; @@ -16,8 +14,6 @@ abstract class AbstractInstanceHandle implements InstanceHandle { - private static final Logger LOGGER = Logger.getLogger(AbstractInstanceHandle.class.getName()); - @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = AtomicIntegerFieldUpdater .newUpdater(AbstractInstanceHandle.class, "destroyed"); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java index 111f18f174d1b..3648dfce08890 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java @@ -84,23 +84,24 @@ public void destroy(Contextual contextual) { @Override public synchronized void destroy() { - List> values = new LinkedList<>(); - instances.forEach(values::add); + // Note that shared contexts are usually only destroyed when the app stops + // I.e. we don't need to use the optimized ContextInstances methods here + Set> values = instances.getAllPresent(); if (values.isEmpty()) { return; } // Destroy the producers first - for (Iterator> iterator = values.iterator(); iterator.hasNext();) { - ContextInstanceHandle instanceHandle = iterator.next(); + for (Iterator> it = values.iterator(); it.hasNext();) { + ContextInstanceHandle instanceHandle = it.next(); if (instanceHandle.getBean().getDeclaringBean() != null) { instanceHandle.destroy(); - iterator.remove(); + it.remove(); } } for (ContextInstanceHandle instanceHandle : values) { instanceHandle.destroy(); } - instances.clear(); + instances.removeEach(null); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java index 5fdfdbb6d4761..a875191831f7d 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java @@ -35,13 +35,11 @@ public Set> getAllPresent() { } @Override - public void clear() { + public void removeEach(Consumer> action) { + if (action != null) { + instances.getPresentValues().forEach(action); + } instances.clear(); } - @Override - public void forEach(Consumer> handleConsumer) { - instances.getPresentValues().forEach(handleConsumer); - } - } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java index fcfce31cbe023..948f79e161a73 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java @@ -2,6 +2,8 @@ import jakarta.enterprise.context.spi.CreationalContext; +import org.jboss.logging.Logger; + import io.quarkus.arc.ContextInstanceHandle; import io.quarkus.arc.InjectableBean; @@ -12,13 +14,19 @@ */ public class ContextInstanceHandleImpl extends EagerInstanceHandle implements ContextInstanceHandle { + private static final Logger LOG = Logger.getLogger(ContextInstanceHandleImpl.class); + public ContextInstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext) { super(bean, instance, creationalContext); } @Override public void destroy() { - destroyInternal(); + try { + destroyInternal(); + } catch (Exception e) { + LOG.error("Unable to destroy instance" + get(), e); + } } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java index 7c0557215ef57..bf3d24c39a3cd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java @@ -8,16 +8,39 @@ public interface ContextInstances { + /** + * + * @param id + * @param supplier + * @return the instance handle + */ ContextInstanceHandle computeIfAbsent(String id, Supplier> supplier); + /** + * + * @param id + * @return the instance handle if present, {@code null} otherwise + */ ContextInstanceHandle getIfPresent(String id); + /** + * + * @param id + * @return the removed instance handle, or {@code null} + */ ContextInstanceHandle remove(String id); + /** + * + * @return all instance handles + */ Set> getAllPresent(); - void clear(); - - void forEach(Consumer> handleConsumer); + /** + * Removes all instance handles and performs the given action (if present) for each handle. + * + * @param action + */ + void removeEach(Consumer> action); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java index 40aa2e3240482..762663007603f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java @@ -210,8 +210,7 @@ public void destroy(ContextState state) { if (reqState.invalidate()) { // Fire an event with qualifier @BeforeDestroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(beforeDestroyedNotifier); - reqState.contextInstances.forEach(this::destroyContextElement); - reqState.contextInstances.clear(); + reqState.contextInstances.removeEach(ContextInstanceHandle::destroy); // Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(destroyedNotifier); } @@ -229,14 +228,6 @@ private static void traceDestroy(ContextState state) { LOG.tracef("Destroy %s%s\n\t...", state != null ? Integer.toHexString(state.hashCode()) : "", stack); } - private void destroyContextElement(ContextInstanceHandle contextInstanceHandle) { - try { - contextInstanceHandle.destroy(); - } catch (Exception e) { - throw new IllegalStateException("Unable to destroy instance" + contextInstanceHandle.get(), e); - } - } - private void fireIfNotEmpty(Notifier notifier) { if (notifier != null && !notifier.isEmpty()) { try { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java index 247173814784e..69695d80be209 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java @@ -4,9 +4,17 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -21,7 +29,7 @@ public class ApplicationContextInstancesTest { @RegisterExtension ArcTestContainer container = ArcTestContainer.builder() - .beanClasses(Boom.class) + .beanClasses(Boom.class, Bim.class) .optimizeContexts(true) .build(); @@ -30,15 +38,23 @@ public void testContext() { ArcContainer container = Arc.container(); InstanceHandle handle = container.instance(Boom.class); Boom boom = handle.get(); + // ContextInstances#computeIfAbsent() String id1 = boom.ping(); assertEquals(id1, boom.ping()); + // ContextInstances#remove() handle.destroy(); + // Bim bean is not destroyed + // ContextInstances#getAllPresent() + assertEquals(1, container.getActiveContext(ApplicationScoped.class).getState().getContextualInstances().size()); + + // Init a new instance of Boom String id2 = boom.ping(); assertNotEquals(id1, id2); assertEquals(id2, boom.ping()); InjectableContext appContext = container.getActiveContext(ApplicationScoped.class); + // ContextInstances#removeEach() appContext.destroy(); assertNotEquals(id2, boom.ping()); } @@ -48,6 +64,9 @@ public static class Boom { private String id; + @Inject + Bim bim; + String ping() { return id; } @@ -55,6 +74,31 @@ String ping() { @PostConstruct void init() { id = UUID.randomUUID().toString(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future f = executorService.submit(() -> { + // Force the init of the bean on a different thread + bim.bam(); + }); + try { + f.get(2, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IllegalStateException(e); + } + executorService.shutdownNow(); + } + + @PreDestroy + void destroy() { + throw new IllegalStateException("Boom"); + } + + } + + @ApplicationScoped + public static class Bim { + + public void bam() { } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java new file mode 100644 index 0000000000000..6ceaa360d3908 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java @@ -0,0 +1,109 @@ +package io.quarkus.arc.test.contexts.request.optimized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class RequestContextInstancesTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Boom.class, Bim.class) + .optimizeContexts(true) + .build(); + + @Test + public void testContext() { + ArcContainer container = Arc.container(); + container.requestContext().activate(); + + InstanceHandle handle = container.instance(Boom.class); + Boom boom = handle.get(); + // ContextInstances#computeIfAbsent() + String id1 = boom.ping(); + assertEquals(id1, boom.ping()); + + // ContextInstances#remove() + handle.destroy(); + // ContextInstances#getAllPresent() + assertEquals(0, container.getActiveContext(RequestScoped.class).getState().getContextualInstances().size()); + + // Init a new instance of Boom + String id2 = boom.ping(); + assertNotEquals(id1, id2); + assertEquals(id2, boom.ping()); + + InjectableContext appContext = container.getActiveContext(RequestScoped.class); + // ContextInstances#removeEach() + appContext.destroy(); + assertNotEquals(id2, boom.ping()); + + container.requestContext().terminate(); + } + + @RequestScoped + public static class Boom { + + private String id; + + @Inject + Bim bim; + + String ping() { + return id; + } + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future f = executorService.submit(() -> { + // Force the init of the bean on a different thread + bim.bam(); + }); + try { + f.get(2, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IllegalStateException(e); + } + executorService.shutdownNow(); + } + + @PreDestroy + void destroy() { + throw new IllegalStateException("Boom"); + } + + } + + @ApplicationScoped + public static class Bim { + + public void bam() { + } + + } +} From 0e6a4b3e855634fc11fb90c38d3e8cc5d218e33d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 9 Jan 2024 14:14:10 +0100 Subject: [PATCH 157/212] Upgrade to Hibernate ORM 6.2.18.Final --- 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 d699b3a03fe18..41905d456312f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -97,7 +97,7 @@ 1.5.1 - 6.2.13.Final + 6.2.18.Final 1.14.7 6.0.6.Final 2.0.6.Final From 96c4d2d41ed714033efa7ffd18a5f0b4343ddd71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 20 Dec 2023 14:50:59 +0100 Subject: [PATCH 158/212] RR client: fix chunking between \n\n in SSEParser #37625 Fixes #37625 (cherry picked from commit 5070e60b439def56b2dbaec27a96f77e3c419c86) --- .../deployment/test/sse/SseParserTest.java | 16 ++++++++++++++++ .../reactive/client/impl/SseParser.java | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java index 36cca2e23779e..12c47625797e5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java @@ -108,6 +108,22 @@ public void testParser() { testParser(Arrays.asList("data:f", "oo\n\n"), Collections.singletonList(new InboundSseEventImpl(null, null) .setData("foo"))); + testParser(Arrays.asList("dat", "a:foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data", ":foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data:", "foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + // chunk at the worst possible place, make sure we don't drop events + testParser(Arrays.asList("data:foo\n", "\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data:foo\n", "data:bar\n", "\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo\nbar"))); // one event in two buffers within a UTF-8 char testParserWithBytes( Arrays.asList(new byte[] { 'd', 'a', 't', 'a', ':', (byte) 0b11000010 }, diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java index 46bb82858514a..45db7ae4ac6fb 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java @@ -40,7 +40,10 @@ public class SseParser implements Handler { * True if we're at the very beginning of the data stream and could see a BOM */ private boolean firstByte = true; - + /** + * True if we've started to read at least one byte of an event + */ + private boolean startedEvent = false; /** * The event type we're reading. Defaults to "message" and changes with "event" fields */ @@ -95,6 +98,7 @@ public void handle(Buffer event) { while (hasByte()) { boolean lastFirstByte = firstByte; + startedEvent = false; nameBuffer.setLength(0); valueBuffer.setLength(0); commentBuffer.setLength(0); @@ -105,10 +109,19 @@ public void handle(Buffer event) { eventReconnectTime = SseEvent.RECONNECT_NOT_SET; // SSE spec says ID is persistent + boolean needsMoreData = false; int lastEventStart = i; try { parseEvent(); + // if we started an event but did not fire it, it means we lacked a final end-of-line and must + // wait for more data + if (startedEvent) { + needsMoreData = true; + } } catch (NeedsMoreDataException x) { + needsMoreData = true; + } + if (needsMoreData) { // save the remaining bytes for later i = lastEventStart; // be ready to rescan the BOM, but only if we didn't already see it in a previous event @@ -133,8 +146,10 @@ private void parseEvent() { int c = readChar(); firstByte = false; if (c == COLON) { + startedEvent = true; parseComment(); } else if (isNameChar(c)) { + startedEvent = true; parseField(c); } else if (isEofWithSideEffect(c)) { dispatchEvent(); @@ -164,6 +179,8 @@ private void dispatchEvent() { event.setReconnectDelay(eventReconnectTime); event.setMediaType(contentType != null ? MediaType.valueOf(contentType) : null); sseEventSource.fireEvent(event); + // make sure we mark that we are done with this event + startedEvent = false; } private byte peekByte() { From 8e33f892abdf686d75cfe3f5145434195be44610 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 20 Dec 2023 18:12:22 -0600 Subject: [PATCH 159/212] Choose correct level field value and type to avoid non-resolution error Fixes #37598 (cherry picked from commit 9aee1dbd21c3e3dde74ef972cf5bc59b41388ab8) --- .../logging/LoggingResourceProcessor.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index 346e3adf46bd7..a1b3e84a7fd53 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -619,10 +619,23 @@ private static BiFunction generate } private static ResultHandle getLogManagerLevelIntValue(String levelName, BytecodeCreator method) { - final ResultHandle infoLevel = method.readStaticField( - FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class)); + FieldDescriptor fd; + switch (levelName) { + case "FATAL": + case "ERROR": + case "WARN": + case "INFO": + case "DEBUG": + case "TRACE": + fd = FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class); + break; + default: + fd = FieldDescriptor.of(Level.class, levelName, Level.class); + break; + } + final ResultHandle levelVal = method.readStaticField(fd); return method - .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), infoLevel); + .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), levelVal); } private static void generateDefaultLoggingLogger(Level minLevel, ClassOutput output) { From 9bfa40d0c32ef16251a01c27aa2c116938aa0fb2 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 20 Dec 2023 07:50:26 -0600 Subject: [PATCH 160/212] Update `jboss-logmanager` to 3.0.4.Final Fixes #36919. Fixes #22844. (cherry picked from commit 47f0c586356ac6cfaa493a81fc255b946b7f78af) --- bom/application/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d699b3a03fe18..cedfd439909e8 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -164,7 +164,7 @@ 4.0.3 3.2.0 4.2.0 - 3.0.2.Final + 3.0.4.Final 9.22.3 3.0.3 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index ad9f2f590dadf..c4cc6b630e003 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -67,7 +67,7 @@ 1.0.1 2.8 1.2.6 - 3.0.2.Final + 3.0.4.Final 1.1.0.Final 1.7.36 23.1.0 From 4bbd759e9f6c6e90509502f799e68bf9e2daf93c Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 21 Dec 2023 08:39:38 -0600 Subject: [PATCH 161/212] Make sure all testing frameworks use `ExtHandler` This avoids the situation where `printf`-formatted messages are translated to "simple" messages due to #22844. (cherry picked from commit d6e8ae3b5e25baeca9e43e2645033f1044abac03) --- .../io/quarkus/vertx/mdc/InMemoryLogHandler.java | 4 ++-- independent-projects/resteasy-reactive/pom.xml | 13 +++++++++++++ .../resteasy-reactive/server/vertx/pom.xml | 6 ++++++ .../vertx/test/framework/InMemoryLogHandler.java | 5 +++-- .../java/io/quarkus/test/InMemoryLogHandler.java | 4 ++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java index ecc3d790b0529..2db16e1fa057f 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java @@ -5,12 +5,12 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.ErrorManager; import java.util.logging.Formatter; -import java.util.logging.Handler; import java.util.logging.LogRecord; +import org.jboss.logmanager.ExtHandler; import org.jboss.logmanager.formatters.PatternFormatter; -public class InMemoryLogHandler extends Handler { +public class InMemoryLogHandler extends ExtHandler { private static final PatternFormatter FORMATTER = new PatternFormatter("%X{requestId} ### %s"); private static final List recordList = new CopyOnWriteArrayList<>(); diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 5ce2d22175a19..f91324942e673 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -54,6 +54,7 @@ 3.9.6 3.24.2 3.5.3.Final + 3.0.4.Final 2.1.1 1.7.0 3.1.0 @@ -295,6 +296,18 @@ ${jboss-logging.version} + + org.jboss.logmanager + jboss-logmanager + ${jboss-logmanager.version} + + + * + * + + + + jakarta.annotation jakarta.annotation-api diff --git a/independent-projects/resteasy-reactive/server/vertx/pom.xml b/independent-projects/resteasy-reactive/server/vertx/pom.xml index 4e4ea50e5e650..6149a3d62dc5b 100644 --- a/independent-projects/resteasy-reactive/server/vertx/pom.xml +++ b/independent-projects/resteasy-reactive/server/vertx/pom.xml @@ -100,6 +100,12 @@ jboss-logging + + org.jboss.logmanager + jboss-logmanager + test + + diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java index b1dde96f45bc1..274570a6883c2 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java @@ -3,11 +3,12 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; -public class InMemoryLogHandler extends Handler { +import org.jboss.logmanager.ExtHandler; + +public class InMemoryLogHandler extends ExtHandler { private final Predicate predicate; diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java index 60eeb40d6f5ba..59f41cc339b4e 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java @@ -3,13 +3,13 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; +import org.jboss.logmanager.ExtHandler; import org.wildfly.common.Assert; -public class InMemoryLogHandler extends Handler { +public class InMemoryLogHandler extends ExtHandler { private final Predicate predicate; From 635aebc9ef0f0b1bc408a5a92c17d58c96f02c32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:02:02 +0000 Subject: [PATCH 162/212] Bump org.bouncycastle:bctls-fips from 1.0.17 to 1.0.18 Bumps org.bouncycastle:bctls-fips from 1.0.17 to 1.0.18. --- updated-dependencies: - dependency-name: org.bouncycastle:bctls-fips dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 02752964e97f46945c6cb90100591deae2758dda) --- 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 cedfd439909e8..74f2d63a0a2bf 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -17,7 +17,7 @@ 2.0.1 1.77 1.0.2.4 - 1.0.17 + 1.0.18 5.0.0 3.0.2 3.1.6 From 3390752707a1c4a220d349942fe64c6068fb9354 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 13 Dec 2023 22:55:09 +0000 Subject: [PATCH 163/212] Reset CSRF cookie to minimize a risk of failures due to its expiry (cherry picked from commit 5a95be2b559d2ccb320a3eb24a043ab857269fe1) --- .../reactive/runtime/CsrfReactiveConfig.java | 2 +- .../CsrfRequestResponseReactiveFilter.java | 22 +++++++++++-------- .../io/quarkus/it/csrf/CsrfReactiveTest.java | 12 +++++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java index e525994361f12..6d7717e0a8d3f 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java @@ -34,7 +34,7 @@ public class CsrfReactiveConfig { /** * CSRF cookie max age. */ - @ConfigItem(defaultValue = "10M") + @ConfigItem(defaultValue = "2H") public Duration cookieMaxAge; /** diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index b75764772a912..25df381ac7b59 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -30,6 +30,7 @@ public class CsrfRequestResponseReactiveFilter { */ private static final String CSRF_TOKEN_KEY = "csrf_token"; private static final String CSRF_TOKEN_BYTES_KEY = "csrf_token_bytes"; + private static final String NEW_COOKIE_REQUIRED = "true"; /** * CSRF token verification status. @@ -85,12 +86,14 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi if (requestMethodIsSafe(requestContext)) { // safe HTTP method, tolerate the absence of a token - if (cookieToken == null && isCsrfTokenRequired(routing, config)) { + if (isCsrfTokenRequired(routing, config)) { // Set the CSRF cookie with a randomly generated value byte[] tokenBytes = new byte[config.tokenSize]; secureRandom.nextBytes(tokenBytes); routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes); routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes)); + + routing.put(NEW_COOKIE_REQUIRED, true); } } else if (config.verifyToken) { // unsafe HTTP method, token is required @@ -129,7 +132,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false); LOG.debugf("CSRF token found in the form parameter"); verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam); - return; } else if (cookieToken == null) { LOG.debug("CSRF token is not found"); @@ -159,6 +161,8 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont } else { routing.put(CSRF_TOKEN_KEY, csrfToken); routing.put(CSRF_TOKEN_VERIFIED, true); + // reset the cookie + routing.put(NEW_COOKIE_REQUIRED, true); return; } } @@ -195,9 +199,9 @@ private static Response badClientRequest() { @ServerResponseFilter public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext, RoutingContext routing) { - final CsrfReactiveConfig config = configInstance.get(); - if (requestContext.getMethod().equals("GET") && isCsrfTokenRequired(routing, config) - && getCookieToken(routing, config) == null) { + if (routing.get(NEW_COOKIE_REQUIRED) != null) { + + final CsrfReactiveConfig config = configInstance.get(); String cookieValue = null; if (config.tokenSignatureKey.isPresent()) { @@ -230,7 +234,7 @@ && getCookieToken(routing, config) == null) { * * @return An Optional containing the token, or an empty Optional if the token cookie is not present or is invalid */ - private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) { + private static String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) { Cookie cookie = routing.getCookie(config.cookieName); if (cookie == null) { @@ -241,14 +245,14 @@ private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) return cookie.getValue(); } - private boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) { + private static boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) { return config.createTokenPath .map(value -> value.contains(routing.normalizedPath())).orElse(true); } - private void createCookie(String csrfToken, RoutingContext routing, CsrfReactiveConfig config) { + private static void createCookie(String cookieTokenValue, RoutingContext routing, CsrfReactiveConfig config) { - ServerCookie cookie = new CookieImpl(config.cookieName, csrfToken); + ServerCookie cookie = new CookieImpl(config.cookieName, cookieTokenValue); cookie.setHttpOnly(config.cookieHttpOnly); cookie.setSecure(config.cookieForceSecure || routing.request().isSSL()); cookie.setMaxAge(config.cookieMaxAge.toSeconds()); diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index 770d36441e300..a8a937e1906a8 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -41,6 +41,8 @@ public void testCsrfTokenInForm() throws Exception { try (final WebClient webClient = createWebClient()) { webClient.addRequestHeader("Authorization", basicAuth("alice", "alice")); HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm"); + htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm"); + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("CSRF Token Form Test", htmlPage.getTitleText()); @@ -51,11 +53,19 @@ public void testCsrfTokenInForm() throws Exception { assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); TextPage textPage = loginForm.getInputByName("submit").click(); - + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + // This request which returns String is not CSRF protected textPage = webClient.getPage("http://localhost:8081/service/hello"); assertEquals("hello", textPage.getContent()); + // therefore no Set-Cookie header is expected + assertNull(textPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); + + // Repeat a form submission + textPage = loginForm.getInputByName("submit").click(); + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); + assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); webClient.getCookieManager().clearCookies(); } From ce5ec522d9579e24693ad21b73025e67c18eff24 Mon Sep 17 00:00:00 2001 From: Welton Rodrigo Torres Nascimento Date: Mon, 25 Dec 2023 15:15:41 -0300 Subject: [PATCH 164/212] Update getting-started-testing.adoc fix typo (cherry picked from commit b4baa1a0ff53c3b926fbcc690ded138d1d2cfd39) --- docs/src/main/asciidoc/getting-started-testing.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 31639cfb3af85..7b3537015ebaa 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1088,7 +1088,7 @@ or starting a mock HTTP server using https://wiremock.org/[Wiremock] (an example === Altering the test class -When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject the something into the test class, the `inject` methods can be used. +When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject something into the test class, the `inject` methods can be used. If for example you have a test like the following: [source,java] From cb9e118f9197b40800fae704a367177f6455ffbe Mon Sep 17 00:00:00 2001 From: Jakub Scholz Date: Thu, 28 Dec 2023 17:06:56 +0100 Subject: [PATCH 165/212] Update Strimzi container images in docs Signed-off-by: Jakub Scholz Signed-off-by: Jakub Scholz (cherry picked from commit bb89fe365ebc822b3b4de78858bfde5208087236) --- docs/src/main/asciidoc/kafka-dev-services.adoc | 2 +- .../src/main/asciidoc/kafka-reactive-getting-started.adoc | 4 ++-- docs/src/main/asciidoc/kafka-schema-registry-avro.adoc | 8 ++++---- docs/src/main/asciidoc/kafka-streams.adoc | 4 ++-- docs/src/main/asciidoc/kafka.adoc | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/kafka-dev-services.adoc b/docs/src/main/asciidoc/kafka-dev-services.adoc index a94da965a3a21..2c0a35c674c8c 100644 --- a/docs/src/main/asciidoc/kafka-dev-services.adoc +++ b/docs/src/main/asciidoc/kafka-dev-services.adoc @@ -82,7 +82,7 @@ For Strimzi, you can select any image with a Kafka version which has Kraft suppo [source, properties] ---- -quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.100.0-kafka-3.1.0 +quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0 ---- == Configuring Kafka topics diff --git a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc index cb4c541a3c9c1..bce3607373361 100644 --- a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc +++ b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc @@ -407,7 +407,7 @@ version: '3.5' services: zookeeper: - image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -420,7 +420,7 @@ services: - kafka-quickstart-network kafka: - image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}" diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc index ca1b4c787a759..224a81fbe682e 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc @@ -324,7 +324,7 @@ version: '2' services: zookeeper: - image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -335,7 +335,7 @@ services: LOG_DIR: /tmp/logs kafka: - image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}" @@ -545,7 +545,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio io.strimzi strimzi-test-container - 0.22.1 + 0.105.0 test @@ -559,7 +559,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -testImplementation("io.strimzi:strimzi-test-container:0.22.1") { +testImplementation("io.strimzi:strimzi-test-container:0.105.0") { exclude group: "org.apache.logging.log4j", module: "log4j-core" } ---- diff --git a/docs/src/main/asciidoc/kafka-streams.adoc b/docs/src/main/asciidoc/kafka-streams.adoc index e5cc5fa70d8fa..28f940e4a9e3a 100644 --- a/docs/src/main/asciidoc/kafka-streams.adoc +++ b/docs/src/main/asciidoc/kafka-streams.adoc @@ -499,7 +499,7 @@ version: '3.5' services: zookeeper: - image: strimzi/kafka:0.19.0-kafka-2.5.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -511,7 +511,7 @@ services: networks: - kafkastreams-network kafka: - image: strimzi/kafka:0.19.0-kafka-2.5.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT} --override num.partitions=$${KAFKA_NUM_PARTITIONS}" diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index b559acfa4d85d..f672fbcb4d37c 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -2268,7 +2268,7 @@ The configuration of the created Kafka broker can be customized using `@Resource [source,java] ---- @QuarkusTestResource(value = KafkaCompanionResource.class, initArgs = { - @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi/kafka:0.28.0-kafka-3.0.0"), // Image name + @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0"), // Image name @ResourceArg(name = "kafka.port", value = "9092"), // Fixed port for kafka, by default it will be exposed on a random port @ResourceArg(name = "kraft", value = "true"), // Enable Kraft mode @ResourceArg(name = "num.partitions", value = "3"), // Other custom broker configurations From a9bcc134d77d5d7f9e9b45c9122a7fd69abe5328 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 2 Jan 2024 22:26:01 +0000 Subject: [PATCH 166/212] Do not expand config properties for Gradle Workers (cherry picked from commit 1aa4604aeeb6ad4f9a2c8de9067c7c742531bb35) --- .../io/quarkus/gradle/tasks/QuarkusBuildTask.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index 13c97cfd722a1..88196dbba173a 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -28,6 +28,7 @@ import io.quarkus.gradle.QuarkusPlugin; import io.quarkus.gradle.tasks.worker.BuildWorker; import io.quarkus.maven.dependency.GACTV; +import io.smallrye.config.Expressions; /** * Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildCacheableAppParts}, {@link QuarkusBuild} tasks @@ -207,12 +208,14 @@ void generateBuild() { ApplicationModel appModel = resolveAppModelForBuild(); Map configMap = new HashMap<>(); - for (Map.Entry entry : extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap() - .entrySet()) { - if (entry.getKey().startsWith("quarkus.")) { - configMap.put(entry.getKey(), entry.getValue()); + EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); + Expressions.withoutExpansion(() -> { + for (Map.Entry entry : effectiveConfig.configMap().entrySet()) { + if (entry.getKey().startsWith("quarkus.")) { + configMap.put(entry.getKey(), effectiveConfig.config().getRawValue(entry.getKey())); + } } - } + }); getLogger().info("Starting Quarkus application build for package type {}", packageType); From 27fc3e505a6f487369b72502d5e2a0011089ed8b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 21 Dec 2023 16:20:42 +0100 Subject: [PATCH 167/212] Upgrade to Kotlin 1.9.22 (cherry picked from commit 1d648ece44517e97f513c0eac93fff30097ce480) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- .../src/test/java/io/quarkus/gradle/QuarkusPluginTest.java | 2 +- devtools/gradle/gradle/libs.versions.toml | 2 +- independent-projects/arc/pom.xml | 2 +- .../conditional-dependencies-kotlin/build.gradle.kts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 74f2d63a0a2bf..9156846eb8e85 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -157,7 +157,7 @@ 2.14.0 2.2.0 1.0.0 - 1.9.21 + 1.9.22 1.7.3 0.27.0 1.6.2 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ae8c28c5ea334..5124c1c267c1e 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -20,7 +20,7 @@ 3.11.0 - 1.9.21 + 1.9.22 1.9.10 2.13.8 4.8.1 diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 862ad0a62abb6..74f976e2b4b7a 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -105,7 +105,7 @@ public void shouldReturnMultipleOutputSourceDirectories() { @Test public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException { - var kotlinVersion = System.getProperty("kotlin_version", "1.9.21"); + var kotlinVersion = System.getProperty("kotlin_version", "1.9.22"); var settingFile = testProjectDir.resolve("settings.gradle.kts"); var mppProjectDir = testProjectDir.resolve("mpp"); var quarkusProjectDir = testProjectDir.resolve("quarkus"); diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index 5bb10bc0b84fc..a8f4fe2d5dfa3 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -2,7 +2,7 @@ plugin-publish = "1.2.1" # updating Kotlin here makes QuarkusPluginTest > shouldNotFailOnProjectDependenciesWithoutMain(Path) fail -kotlin = "1.9.21" +kotlin = "1.9.22" smallrye-config = "3.4.4" junit5 = "5.10.1" diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 5cf3128c08d0c..9e02257fdec6c 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -55,7 +55,7 @@ 3.24.2 5.10.1 - 1.9.21 + 1.9.22 1.7.3 5.7.0 diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts index 5e3808937ce3e..5e4ee3b8fe2e4 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.9.21" - kotlin("plugin.allopen") version "1.9.21" + kotlin("jvm") version "1.9.22" + kotlin("plugin.allopen") version "1.9.22" id("io.quarkus") } From 7430ec6cd7be35f0fd4176b8d9ad0954b41374a6 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 12:43:03 +0200 Subject: [PATCH 168/212] Mention exit handler parameter variant of Quarkus.run Fixes: #37929 (cherry picked from commit 4f239039bb297cb07ee063888bf5722d07d9e987) --- docs/src/main/asciidoc/lifecycle.adoc | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/src/main/asciidoc/lifecycle.adoc b/docs/src/main/asciidoc/lifecycle.adoc index 911f10ff02f8b..a528863ac6bf0 100644 --- a/docs/src/main/asciidoc/lifecycle.adoc +++ b/docs/src/main/asciidoc/lifecycle.adoc @@ -111,6 +111,42 @@ public class Main { } ---- +[TIP] +==== +`Quarkus.run` also provides a version that allows the code to handle errors. +For example: + +[source,java] +---- +package com.acme; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; + +@QuarkusMain +public class Main { + public static void main(String... args) { + Quarkus.run(MyApp.class, + (exitCode, exception) -> { + // do whatever + }, + args); + } + + public static class MyApp implements QuarkusApplication { + + @Override + public int run(String... args) throws Exception { + System.out.println("Do startup logic here"); + Quarkus.waitForExit(); + return 0; + } + } +} +---- +==== + === Injecting the command line arguments It is possible to inject the arguments that were passed in on the command line: From 577969527f8cc0628370922426615930d697195b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 10:20:23 +0200 Subject: [PATCH 169/212] Register JsonSubTypes.Type values for native mode Fixes: #37942 (cherry picked from commit a110cea7bb88d0b934f7717d9cfa8fe7b95f004c) --- .../jackson/deployment/JacksonProcessor.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index dc7b382a921a4..d120cea847a9e 100644 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -25,6 +25,7 @@ import org.jboss.jandex.Type; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.SimpleObjectIdResolver; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; @@ -79,7 +80,7 @@ public class JacksonProcessor { private static final DotName JSON_AUTO_DETECT = DotName.createSimple(JsonAutoDetect.class.getName()); private static final DotName JSON_TYPE_ID_RESOLVER = DotName.createSimple(JsonTypeIdResolver.class.getName()); - + private static final DotName JSON_SUBTYPES = DotName.createSimple(JsonSubTypes.class.getName()); private static final DotName JSON_CREATOR = DotName.createSimple("com.fasterxml.jackson.annotation.JsonCreator"); private static final DotName JSON_NAMING = DotName.createSimple("com.fasterxml.jackson.databind.annotation.JsonNaming"); @@ -98,6 +99,7 @@ public class JacksonProcessor { // this list can probably be enriched with more modules private static final List MODULES_NAMES_TO_AUTO_REGISTER = Arrays.asList(TIME_MODULE, JDK8_MODULE, PARAMETER_NAMES_MODULE); + private static final String[] EMPTY_STRING = new String[0]; @Inject CombinedIndexBuildItem combinedIndexBuildItem; @@ -266,6 +268,25 @@ void register( } } + // register @JsonSubTypes.Type values for reflection + Set subTypeTypesNames = new HashSet<>(); + for (AnnotationInstance subTypeInstance : index.getAnnotations(JSON_SUBTYPES)) { + AnnotationValue subTypeValue = subTypeInstance.value(); + if (subTypeValue != null) { + for (AnnotationInstance subTypeTypeInstance : subTypeValue.asNestedArray()) { + AnnotationValue subTypeTypeValue = subTypeTypeInstance.value(); + if (subTypeTypeValue != null) { + subTypeTypesNames.add(subTypeTypeValue.asClass().name().toString()); + } + } + + } + } + if (!subTypeTypesNames.isEmpty()) { + reflectiveClass.produce(ReflectiveClassBuildItem.builder(subTypeTypesNames.toArray(EMPTY_STRING)) + .methods().fields().build()); + } + // this needs to be registered manually since the runtime module is not indexed by Jandex additionalBeans.produce(new AdditionalBeanBuildItem(ObjectMapperProducer.class)); } From 08c3e9a4a643b1de3bf4383c13920ab328e34035 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 13:03:28 +0200 Subject: [PATCH 170/212] Add native test for verifying JsonSubTypes.Type handling (cherry picked from commit d7c765ca12e88ce02111efade1644d16e00cca4b) --- .../it/jackson/JsonSubTypesResource.java | 29 +++++++++++++++++++ .../io/quarkus/it/jackson/model/Elephant.java | 20 +++++++++++++ .../io/quarkus/it/jackson/model/Mammal.java | 15 ++++++++++ .../it/jackson/model/MammalFamily.java | 19 ++++++++++++ .../io/quarkus/it/jackson/model/Whale.java | 20 +++++++++++++ .../it/jackson/JsonSubTypesResourceIT.java | 7 +++++ .../it/jackson/JsonSubTypesResourceTest.java | 27 +++++++++++++++++ 7 files changed, 137 insertions(+) create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java create mode 100644 integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java create mode 100644 integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java new file mode 100644 index 0000000000000..ebffa6018819c --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java @@ -0,0 +1,29 @@ +package io.quarkus.it.jackson; + +import java.util.List; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.it.jackson.model.Elephant; +import io.quarkus.it.jackson.model.MammalFamily; +import io.quarkus.it.jackson.model.Whale; + +@Path("jsonSubTypes") +public class JsonSubTypesResource { + + private final ObjectMapper objectMapper; + + public JsonSubTypesResource(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @GET + public String test() throws JsonProcessingException { + return objectMapper.writeValueAsString(new MammalFamily(List.of( + new Whale(30.0, "white"), new Elephant(10, "africa")))); + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java new file mode 100644 index 0000000000000..4fea8838dc3b7 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java @@ -0,0 +1,20 @@ +package io.quarkus.it.jackson.model; + +public class Elephant implements Mammal { + + private final int hornLength; + private final String continent; + + public Elephant(int hornLength, String continent) { + this.hornLength = hornLength; + this.continent = continent; + } + + public int getHornLength() { + return hornLength; + } + + public String getContinent() { + return continent; + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java new file mode 100644 index 0000000000000..dc622346349f2 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java @@ -0,0 +1,15 @@ +package io.quarkus.it.jackson.model; + +import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY; +import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo(use = NAME, include = PROPERTY) +@JsonSubTypes({ + @JsonSubTypes.Type(value = Elephant.class, name = "Elephant"), + @JsonSubTypes.Type(value = Whale.class, name = "Whale"), +}) +public interface Mammal { +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java new file mode 100644 index 0000000000000..b35e13d143ddc --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java @@ -0,0 +1,19 @@ +package io.quarkus.it.jackson.model; + +import java.util.Collection; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public class MammalFamily { + + private final Collection mammals; + + public MammalFamily(Collection mammals) { + this.mammals = mammals; + } + + public Collection getMammals() { + return mammals; + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java new file mode 100644 index 0000000000000..de7d22dfe2c64 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java @@ -0,0 +1,20 @@ +package io.quarkus.it.jackson.model; + +public class Whale implements Mammal { + + private final double swimSpeed; + private final String color; + + public Whale(double swimSpeed, String color) { + this.swimSpeed = swimSpeed; + this.color = color; + } + + public double getSwimSpeed() { + return swimSpeed; + } + + public String getColor() { + return color; + } +} diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java new file mode 100644 index 0000000000000..9d58d36a156f4 --- /dev/null +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.jackson; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class JsonSubTypesResourceIT extends JsonSubTypesResourceTest { +} diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java new file mode 100644 index 0000000000000..fe1979b72db7a --- /dev/null +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java @@ -0,0 +1,27 @@ +package io.quarkus.it.jackson; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import io.restassured.response.Response; + +@QuarkusTest +class JsonSubTypesResourceTest { + + @Test + void test() { + Response response = given() + .accept(ContentType.JSON) + .when() + .get("jsonSubTypes") + .then() + .statusCode(200) + .extract().response(); + assertThat(response.jsonPath().getString("mammals[0].color")).isEqualTo("white"); + assertThat(response.jsonPath().getString("mammals[1].continent")).isEqualTo("africa"); + } +} From 6d2b36e810c02c276ec4e2ed9ba87726f4edc731 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 15:01:22 +0200 Subject: [PATCH 171/212] Fix the status is a couple extensions documentation pages Closes: #37941 (cherry picked from commit 13662011f190c1de6e736000e52360839d3bb82d) --- docs/src/main/asciidoc/cache.adoc | 2 -- docs/src/main/asciidoc/security-oauth2.adoc | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs/src/main/asciidoc/cache.adoc b/docs/src/main/asciidoc/cache.adoc index 2b711ed7ff187..10f397041d653 100644 --- a/docs/src/main/asciidoc/cache.adoc +++ b/docs/src/main/asciidoc/cache.adoc @@ -12,8 +12,6 @@ include::_attributes.adoc[] In this guide, you will learn how to enable application data caching in any CDI managed bean of your Quarkus application. -include::{includes}/extension-status.adoc[] - == Prerequisites include::{includes}/prerequisites.adoc[] diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index 7720edb8bb238..cf216a14c8225 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -22,8 +22,6 @@ This extension provides a light-weight support for using the opaque Bearer Token If the OAuth2 Authentication server provides JWT Bearer Tokens, consider using either xref:security-oidc-bearer-token-authentication.adoc[OIDC Bearer token authentication] or xref:security-jwt.adoc[SmallRye JWT] extensions instead. OpenID Connect extension has to be used if the Quarkus application needs to authenticate the users using OIDC Authorization Code Flow. For more information, see the xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications] guide. -include::{includes}/extension-status.adoc[] - == Solution We recommend that you follow the instructions in the next sections and create the application step by step. From 9c042cf57517eed72586aad9077deafba2f2c5ba Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 12:22:04 +0200 Subject: [PATCH 172/212] Don't fail if config is not a directory Fixes: #37903 (cherry picked from commit b364fe9e0dad4c74b3a735d93cb47d3ff469b6e6) --- .../io/quarkus/runtime/configuration/ConfigDiagnostic.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java index 574ae74cf169e..d073ca201d7f9 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -6,6 +6,7 @@ import java.net.URI; import java.nio.file.DirectoryStream; import java.nio.file.Files; +import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; @@ -186,6 +187,9 @@ public static Set configFiles(Path configFilesLocation) throws IOExcepti for (Path candidate : candidates) { configFiles.add(candidate.toUri().toURL().toString()); } + } catch (NotDirectoryException ignored) { + log.debugf("File %s is not a directory", configFilesLocation.toAbsolutePath()); + return Collections.emptySet(); } return configFiles; } From 23ab911b4a3eb6542ff8ed03f0edb86f74fa4ae2 Mon Sep 17 00:00:00 2001 From: "rob.spoor" Date: Wed, 3 Jan 2024 16:00:38 +0100 Subject: [PATCH 173/212] Set the correct port properties for HTTPS (cherry picked from commit 5bd6a7947a1ac31f6aca711db3952f31eb8ea78e) --- .../http/runtime/PortSystemProperties.java | 38 +++++--- .../it/vertx/RandomTestPortTestCase.java | 86 +++++++++++++++++++ 2 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java index 40fa135373900..02d3a1bf5deb5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java @@ -11,28 +11,38 @@ public class PortSystemProperties { public void set(String subProperty, int actualPort, LaunchMode launchMode) { String portPropertyValue = String.valueOf(actualPort); - //we always set the .port property, even if we are in test mode, so this will always - //reflect the current port String portPropertyName = "quarkus." + subProperty + ".port"; - String prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue); - if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) { - portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue); + String testPropName = "quarkus." + subProperty + ".test-port"; + + set(portPropertyName, testPropName, portPropertyValue, launchMode); + //if subProperty is "https", the correct properties are not quarkus.https.port and quarkus.https.test-port + //but quarkus.http.ssl-port and quarkus.http.test-ssl-port + //the incorrect properties are still set for backward compatibility with code that works around the incorrect + //names + if ("https".equals(subProperty)) { + set("quarkus.http.ssl-port", "quarkus.http.test-ssl-port", portPropertyValue, launchMode); } + } + + private void set(String portPropertyName, String testPropName, String portPropertyValue, LaunchMode launchMode) { + //we always set the .port property, even if we are in test mode, so this will always + //reflect the current port + set(portPropertyName, portPropertyValue); if (launchMode == LaunchMode.TEST) { //we also set the test-port property in a test - String testPropName = "quarkus." + subProperty + ".test-port"; - String prevTestPropPrevValue = System.setProperty(testPropName, portPropertyValue); - if (!Objects.equals(prevTestPropPrevValue, portPropertyValue)) { - portPropertiesToRestore.put(testPropName, prevTestPropPrevValue); - } + set(testPropName, portPropertyValue); } if (launchMode.isDevOrTest()) { // set the profile property as well to make sure we don't have any inconsistencies portPropertyName = "%" + launchMode.getDefaultProfile() + "." + portPropertyName; - prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue); - if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) { - portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue); - } + set(portPropertyName, portPropertyValue); + } + } + + private void set(String propertyName, String propertyValue) { + String prevPropertyValue = System.setProperty(propertyName, propertyValue); + if (!Objects.equals(prevPropertyValue, propertyValue)) { + portPropertiesToRestore.put(propertyName, prevPropertyValue); } } diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java new file mode 100644 index 0000000000000..b8275f241d538 --- /dev/null +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java @@ -0,0 +1,86 @@ +package io.quarkus.it.vertx; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.net.URL; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(RandomTestPortTestCase.TestPortProfile.class) +public class RandomTestPortTestCase { + + @ConfigProperty(name = "quarkus.http.port") + int httpPort; + + @ConfigProperty(name = "quarkus.http.test-port") + int httpTestPort; + + @TestHTTPResource(value = "/some-path") + URL httpTestUrl; + + @ConfigProperty(name = "quarkus.http.ssl-port") + int httpsPort; + + @ConfigProperty(name = "quarkus.http.test-ssl-port") + int httpsTestPort; + + @TestHTTPResource(value = "/some-path", ssl = true) + URL httpsTestUrl; + + @Test + public void testHttpTestPort() { + assertNotEquals(0, httpTestPort); + assertNotEquals(8080, httpTestPort); + assertNotEquals(8081, httpTestPort); + + assertEquals(httpTestPort, httpPort); + assertEquals(httpTestPort, httpTestUrl.getPort()); + } + + @Test + public void testHttpsTestPort() { + assertNotEquals(0, httpsTestPort); + assertNotEquals(8443, httpsTestPort); + assertNotEquals(8444, httpsTestPort); + + assertEquals(httpsTestPort, httpsPort); + assertEquals(httpsTestPort, httpsTestUrl.getPort()); + } + + @Test + public void testLegacyProperties() { + Config config = ConfigProvider.getConfig(); + + testLegacyProperty(config, "quarkus.http.ssl-port", "quarkus.https.port", httpsPort); + testLegacyProperty(config, "quarkus.http.test-ssl-port", "quarkus.https.test-port", httpsTestPort); + testLegacyProperty(config, "%test.quarkus.http.ssl-port", "%test.quarkus.https.port", httpsTestPort); + } + + private void testLegacyProperty(Config config, String correctName, String legacyName, int expectedValue) { + Optional portWithCorrectProperty = config.getOptionalValue(correctName, Integer.class); + Optional portWithLegacyProperty = config.getOptionalValue(legacyName, Integer.class); + + assertEquals(Optional.of(expectedValue), portWithCorrectProperty); + assertEquals(portWithCorrectProperty, portWithLegacyProperty); + } + + public static class TestPortProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.http.test-port", "0", "quarkus.http.test-ssl-port", "0"); + } + } +} From 3af3724314054235fdb9d5894632e377bd8dc1e9 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 15:52:35 +0300 Subject: [PATCH 174/212] Properly take Quarkus HTTP body configuration into account for File body (cherry picked from commit e9a0c7c470307e18058529b04aa42c6c2f14e568) --- .../test/MessageBodyReaderTests.java | 8 ++ .../BuiltInReaderOverrideBuildItem.java | 42 ++++++++ .../deployment/ResteasyReactiveProcessor.java | 18 +++- .../test/multipart/AbstractMultipartTest.java | 6 ++ .../multipart/FileInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/FileInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../MultipartInputBodyHandlerTest.java | 5 - .../test/multipart/MultipartInputTest.java | 5 - .../runtime/QuarkusServerFileBodyHandler.java | 95 ++++++++++++++++++ .../serialisers/FileBodyHandler.java | 16 ++-- .../core/ResteasyReactiveRequestContext.java | 6 ++ .../server/spi/ServerRequestContext.java | 3 + 12 files changed, 362 insertions(+), 18 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index d4e1238aadc60..ab49f42c3381b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,12 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -20,6 +22,7 @@ import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import org.jboss.resteasy.reactive.server.jaxrs.HttpHeadersImpl; import org.jboss.resteasy.reactive.server.spi.ContentType; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; @@ -266,6 +269,11 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { return null; } + @Override + public HttpHeaders getRequestHeaders() { + return new HttpHeadersImpl(Collections.emptyList()); + } + @Override public void abortWith(Response response) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java new file mode 100644 index 0000000000000..13b3ce8eb68da --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java @@ -0,0 +1,42 @@ +package io.quarkus.resteasy.reactive.server.deployment; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class BuiltInReaderOverrideBuildItem extends MultiBuildItem { + + private final String readerClassName; + private final String overrideClassName; + + public BuiltInReaderOverrideBuildItem(String readerClassName, String overrideClassName) { + this.readerClassName = readerClassName; + this.overrideClassName = overrideClassName; + } + + public String getReaderClassName() { + return readerClassName; + } + + public String getOverrideClassName() { + return overrideClassName; + } + + public static Map toMap(List items) { + if (items.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + for (BuiltInReaderOverrideBuildItem item : items) { + String previousOverride = result.put(item.getReaderClassName(), item.getOverrideClassName()); + if (previousOverride != null) { + throw new IllegalStateException( + "Providing multiple BuiltInReaderOverrideBuildItem for the same readerClassName is not supported"); + } + } + return result; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 2b018c4247b58..65ef31c761aad 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -110,6 +110,7 @@ import org.jboss.resteasy.reactive.server.processor.scanning.ResponseHeaderMethodScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner; import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter; @@ -165,6 +166,7 @@ import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem; import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -1020,6 +1022,12 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } } + @BuildStep + public void builtInReaderOverrides(BuildProducer producer) { + producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + QuarkusServerFileBodyHandler.class.getName())); + } + @BuildStep @Record(value = ExecutionTime.STATIC_INIT, useIdentityComparisonForParameters = false) public void serverSerializers(ResteasyReactiveRecorder recorder, @@ -1029,6 +1037,7 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, List additionalMessageBodyWriters, List messageBodyReaderOverrideBuildItems, List messageBodyWriterOverrideBuildItems, + List builtInReaderOverrideBuildItems, BuildProducer reflectiveClass, BuildProducer serverSerializersProducer) { @@ -1046,11 +1055,16 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinWriter.writerClass.getName()) .build()); } + Map builtInReaderOverrides = BuiltInReaderOverrideBuildItem.toMap(builtInReaderOverrideBuildItems); for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) { - registerReader(recorder, serialisers, builtinReader.entityClass.getName(), builtinReader.readerClass.getName(), + String effectiveReaderClassName = builtinReader.readerClass.getName(); + if (builtInReaderOverrides.containsKey(effectiveReaderClassName)) { + effectiveReaderClassName = builtInReaderOverrides.get(effectiveReaderClassName); + } + registerReader(recorder, serialisers, builtinReader.entityClass.getName(), effectiveReaderClassName, beanContainerBuildItem.getValue(), builtinReader.mediaType, builtinReader.constraint); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinReader.readerClass.getName()) + reflectiveClass.produce(ReflectiveClassBuildItem.builder(effectiveReaderClassName) .build()); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java index 86348aa16e4b8..9bcb1a8dd1d7c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java @@ -3,6 +3,8 @@ import static org.awaitility.Awaitility.await; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.concurrent.Callable; @@ -40,4 +42,8 @@ public Boolean call() { } }); } + + protected String fileSizeAsStr(File file) throws IOException { + return "" + Files.readAllBytes(file.toPath()).length; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java new file mode 100644 index 0000000000000..a3d43bad36ab3 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..318b8602e9167 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java index f75e0a416573d..9b371cb02996e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; @@ -164,8 +163,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).length; - } - } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java index 3b3bc76ad6b54..e29c3791fda69 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Supplier; @@ -245,8 +244,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).length; - } - } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java new file mode 100644 index 0000000000000..01e6b53b33b35 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -0,0 +1,95 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerFileBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerFileBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + private Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java index 82d2045179024..1f502c66555a2 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java @@ -21,8 +21,8 @@ import org.jboss.resteasy.reactive.common.headers.HeaderUtil; public class FileBodyHandler implements MessageBodyReader, MessageBodyWriter { - protected static final String PREFIX = "pfx"; - protected static final String SUFFIX = "sfx"; + public static final String PREFIX = "pfx"; + public static final String SUFFIX = "sfx"; @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { @@ -33,16 +33,20 @@ public boolean isReadable(Class type, Type genericType, Annotation[] annotati public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { - File downloadedFile = Files.createTempFile(PREFIX, SUFFIX).toFile(); + return doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + public static File doRead(MultivaluedMap httpHeaders, InputStream entityStream, + File file) throws IOException { if (HeaderUtil.isContentLengthZero(httpHeaders)) { - return downloadedFile; + return file; } - try (OutputStream output = new BufferedOutputStream(new FileOutputStream(downloadedFile))) { + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { entityStream.transferTo(output); } - return downloadedFile; + return file; } public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index 2248a8f7763ac..4fd59f33828db 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -19,6 +19,7 @@ import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.Request; @@ -148,6 +149,11 @@ public ResteasyReactiveRequestContext(Deployment deployment, @Override public abstract ServerHttpResponse serverResponse(); + @Override + public HttpHeaders getRequestHeaders() { + return getHttpHeaders(); + } + public Deployment getDeployment() { return deployment; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java index 50fba8210e0da..4825de1472c11 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java @@ -3,6 +3,7 @@ import java.io.InputStream; import java.io.OutputStream; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -22,5 +23,7 @@ public interface ServerRequestContext extends ResteasyReactiveCallbackContext { ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo(); + HttpHeaders getRequestHeaders(); + void abortWith(Response response); } From d8bc51aa4327a6ac810732a31a1893e10917ef8d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 16:04:51 +0300 Subject: [PATCH 175/212] Add support for Path as a JAX-RS method body type (cherry picked from commit 96135286ba942f45fdf57af3093660720814cea2) --- .../test/MessageBodyReaderTests.java | 2 +- .../deployment/ResteasyReactiveProcessor.java | 10 +- .../multipart/PathInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/PathInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../runtime/QuarkusServerFileBodyHandler.java | 42 +------- .../runtime/QuarkusServerPathBodyHandler.java | 96 +++++++++++++++++++ 6 files changed, 282 insertions(+), 44 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index ab49f42c3381b..68bd47c204deb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -17,6 +16,7 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.container.ConnectionCallback; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 65ef31c761aad..26a848224c7ed 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -12,6 +12,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -167,6 +168,7 @@ import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -1023,9 +1025,13 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } @BuildStep - public void builtInReaderOverrides(BuildProducer producer) { - producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + public void fileHandling(BuildProducer overrideProducer, + BuildProducer readerProducer) { + overrideProducer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), QuarkusServerFileBodyHandler.class.getName())); + readerProducer.produce( + new MessageBodyReaderBuildItem(QuarkusServerPathBodyHandler.class.getName(), Path.class.getName(), List.of( + MediaType.WILDCARD), RuntimeType.SERVER, true, Priorities.USER)); } @BuildStep diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java new file mode 100644 index 0000000000000..b03d853f2fcab --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..08b7f7181da6b --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java index 01e6b53b33b35..e8c8effc7300a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -1,28 +1,24 @@ package io.quarkus.resteasy.reactive.server.runtime; +import static io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler.createFile; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.Paths; import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; -import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; @@ -56,40 +52,4 @@ public File readFrom(Class type, Type genericType, Annotation[] annotation // however this should never be called in a real world scenario return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); } - - private Path createFile(ServerRequestContext context) throws IOException { - RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() - .getRuntimeConfiguration().body(); - boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); - String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); - Path uploadDirectory = Paths.get(uploadsDirectoryStr); - try { - Files.createDirectories(uploadDirectory); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); - if (deleteUploadedFilesOnEnd) { - context.registerCompletionCallback(new CompletionCallback() { - @Override - public void onComplete(Throwable throwable) { - ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { - @Override - public void run() { - if (Files.exists(file)) { - try { - Files.delete(file); - } catch (NoSuchFileException e) { // ignore - } catch (IOException e) { - log.error("Cannot remove uploaded file " + file, e); - } - } - } - }); - } - }); - } - return file; - } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java new file mode 100644 index 0000000000000..e6dcb8ff7dfa9 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerPathBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerPathBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return Path.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()) + .toPath(); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()).toPath(); + } + + static Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} From f2f4762f3528caf506175a8a66e9b2f4639e6141 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Jan 2024 09:08:40 +0200 Subject: [PATCH 176/212] Don't want about missing JSON when returning String Closes: #38044 (cherry picked from commit d53358a8efca70de449c4c5c51184818d7586ebc) --- .../resteasy/reactive/common/processor/EndpointIndexer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 0f4dca20e286b..b8dedcde357e2 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -1623,6 +1623,9 @@ protected void warnAboutMissingJsonProviderIfNeeded(ResourceMethod method, Metho DefaultProducesHandler jsonDefaultProducersHandler, DefaultProducesHandler.Context context) { if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson(jsonDefaultProducersHandler, context))) { + if (STRING.toString().equals(method.getSimpleReturnType())) { // when returning string, we assume that the method implementation is actually handling to conversion + return; + } boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders()); boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters()); if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) { From fffa654a60b5b9e3e2b7924426cc50a62b06a6e6 Mon Sep 17 00:00:00 2001 From: Marco Schaub Date: Thu, 4 Jan 2024 15:57:50 +0000 Subject: [PATCH 177/212] Add hint for Scheduled.ApplicationNotRunning skip predicate (cherry picked from commit f37d9bd60eb936b7797cd453d059d89533f58f21) --- docs/src/main/asciidoc/scheduler-reference.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc index b999361cdb397..3ba552f80c169 100644 --- a/docs/src/main/asciidoc/scheduler-reference.adoc +++ b/docs/src/main/asciidoc/scheduler-reference.adoc @@ -302,6 +302,8 @@ The main idea is to keep the logic to skip the execution outside the scheduled b TIP: A CDI event of type `io.quarkus.scheduler.SkippedExecution` is fired when an execution of a scheduled method is skipped. +TIP: To skip the scheduled executions while the application is starting up/shutting down, you can make use of the `io.quarkus.scheduler.Scheduled.ApplicationNotRunning` skip predicate. + [[non-blocking-methods]] === Non-blocking Methods From f9377cd0f0220ff0005b1d20a01178f6129acd18 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Jan 2024 16:01:26 +0200 Subject: [PATCH 178/212] Add companion classes to Kotlin reflective hierarchy registration Fixes: #37957 (cherry picked from commit e185df85d5ead125ec3487652411ebde7abc40ae) --- .../steps/ReflectiveHierarchyStep.java | 55 ++++++++++++++----- .../reactive/kotlin/CompanionResource.kt | 19 +++++++ .../resteasy/reactive/kotlin/ResponseData.kt | 17 ++++++ .../resteasy/reactive/kotlin/CompanionIT.kt | 5 ++ .../resteasy/reactive/kotlin/CompanionTest.kt | 29 ++++++++++ 5 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index c3dd57bae5e17..d63b5535faf49 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -1,5 +1,7 @@ package io.quarkus.deployment.steps; +import static io.quarkus.deployment.steps.KotlinUtil.isKotlinClass; + import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; @@ -27,6 +29,8 @@ import org.jboss.jandex.VoidType; import org.jboss.logging.Logger; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; @@ -51,7 +55,7 @@ public ReflectiveHierarchyIgnoreWarningBuildItem ignoreJavaClassWarnings() { } @BuildStep - public void build(CombinedIndexBuildItem combinedIndexBuildItem, + public void build(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities, List hierarchy, List ignored, List finalFieldsWritablePredicates, @@ -73,7 +77,7 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem, final Deque visits = new ArrayDeque<>(); for (ReflectiveHierarchyBuildItem i : hierarchy) { - addReflectiveHierarchy(combinedIndexBuildItem, + addReflectiveHierarchy(combinedIndexBuildItem, capabilities, i, i.hasSource() ? i.getSource() : i.getType().name().toString(), i.getType(), @@ -128,7 +132,7 @@ private void removeIgnored(Map> unindexedClasses, } private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, - ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type, + Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type, Set processedReflectiveHierarchies, Map> unindexedClasses, Predicate finalFieldsWritable, BuildProducer reflectiveClass, Deque visits) { @@ -142,30 +146,34 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte return; } - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), type.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(), + type.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + subclass.name(), subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); } for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + subclass.name(), subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); } } else if (type instanceof ArrayType) { - visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, + reflectiveHierarchyBuildItem, source, type.asArrayType().constituent(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } else if (type instanceof ParameterizedType) { if (!reflectiveHierarchyBuildItem.getIgnoreTypePredicate().test(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(), type.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); @@ -173,14 +181,15 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte final ParameterizedType parameterizedType = (ParameterizedType) type; for (Type typeArgument : parameterizedType.arguments()) { visits.addLast( - () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, typeArgument, + () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + typeArgument, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } } } - private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, + private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, DotName name, @@ -223,7 +232,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem return; } - visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, info.superName(), initialName, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); @@ -237,7 +246,8 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem } final Type fieldType = getFieldType(combinedIndexBuildItem, initialName, info, field); visits.addLast( - () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, fieldType, + () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + fieldType, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } @@ -249,11 +259,30 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem method.returnType().kind() == Kind.VOID) { continue; } - visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, + reflectiveHierarchyBuildItem, source, method.returnType(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } + + // for Kotlin classes, we need to register the nested classes as well because companion classes are very often necessary at runtime + if (capabilities.isPresent(Capability.KOTLIN) && isKotlinClass(info)) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + Class[] declaredClasses = classLoader.loadClass(info.name().toString()).getDeclaredClasses(); + for (Class clazz : declaredClasses) { + DotName dotName = DotName.createSimple(clazz.getName()); + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + dotName, dotName, + processedReflectiveHierarchies, unindexedClasses, + finalFieldsWritable, reflectiveClass, visits); + } + } catch (ClassNotFoundException e) { + log.warnf(e, "Failed to load Class %s", info.name().toString()); + } + + } } private static Type getFieldType(CombinedIndexBuildItem combinedIndexBuildItem, DotName initialName, ClassInfo info, diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt new file mode 100644 index 0000000000000..6459e438c013f --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt @@ -0,0 +1,19 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType + +@Path("/companion") +class CompanionResource { + @Path("success") + @GET + @Produces(MediaType.APPLICATION_JSON) + fun success() = ResponseData.success() + + @Path("failure") + @GET + @Produces(MediaType.APPLICATION_JSON) + fun failure() = ResponseData.failure("error") +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt new file mode 100644 index 0000000000000..efce43fa20bac --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt @@ -0,0 +1,17 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +class ResponseData( + val code: Int = STATUS_CODE.SUCCESS.code, + val msg: String = "", + val data: T? = null, +) { + companion object { + fun success() = ResponseData() + fun failure(msg: String) = ResponseData(code = STATUS_CODE.ERROR.code, msg = msg) + } +} + +enum class STATUS_CODE(val code: Int) { + SUCCESS(200), + ERROR(500) +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt new file mode 100644 index 0000000000000..75b52c24a0edc --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import io.quarkus.test.junit.QuarkusIntegrationTest + +@QuarkusIntegrationTest class CompanionIT : CompanionTest() {} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt new file mode 100644 index 0000000000000..1f088fffed227 --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt @@ -0,0 +1,29 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +@QuarkusTest +class CompanionTest { + + @Test + fun testSuccessResponseData() { + When { get("/companion/success") } Then + { + statusCode(200) + body(containsString("200")) + } + } + + @Test + fun testFailureResponseData() { + When { get("/companion/failure") } Then + { + statusCode(200) + body(containsString("500"), containsString("error")) + } + } +} From 94728244101443a588213ad0ae4b312ecbdde89c Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Mon, 8 Jan 2024 18:31:01 +0100 Subject: [PATCH 179/212] Always set ssl and alpn for non-plain-text with Vert.x gRPC channel (cherry picked from commit bea42972acb01c70e768d7a00e82827f7caf92dc) --- .../java/io/quarkus/grpc/runtime/supports/Channels.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java index 4e66fd21023bb..65831169c0b03 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java @@ -254,14 +254,16 @@ public static Channel createChannel(String name, Set perClientIntercepto options.setHttp2ClearTextUpgrade(false); // this fixes i30379 if (!plainText) { + // always set ssl + alpn for plain-text=false + options.setSsl(true); + options.setUseAlpn(true); + if (config.ssl.trustStore.isPresent()) { Optional trustStorePath = config.ssl.trustStore; if (trustStorePath.isPresent()) { PemTrustOptions to = new PemTrustOptions(); to.addCertValue(bufferFor(trustStorePath.get(), "trust store")); options.setTrustOptions(to); - options.setSsl(true); - options.setUseAlpn(true); } Optional certificatePath = config.ssl.certificate; Optional keyPath = config.ssl.key; @@ -270,8 +272,6 @@ public static Channel createChannel(String name, Set perClientIntercepto cko.setCertValue(bufferFor(certificatePath.get(), "certificate")); cko.setKeyValue(bufferFor(keyPath.get(), "key")); options.setKeyCertOptions(cko); - options.setSsl(true); - options.setUseAlpn(true); } } } From 842b3fe91fbb980f8c6229f6ca566dfe7d473585 Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Thu, 14 Dec 2023 19:30:28 +0100 Subject: [PATCH 180/212] Stork path param resolution fix: use raw path and avoid double encoding when creating new URI Related to #37713 refactor: clean up a few commented lines fix: use raw path and avoid double encoding, adapt tests accordingly (cherry picked from commit ce206d5e43f5260ce71bc792ac5dd576bd4ff2f5) --- .../client/reactive/stork/HelloClient.java | 13 +++++++ .../client/reactive/stork/HelloResource.java | 18 ++++++++++ .../reactive/stork/PassThroughResource.java | 13 +++++++ .../reactive/stork/StorkDevModeTest.java | 21 +++++++++++ .../reactive/stork/StorkIntegrationTest.java | 35 ++++++++++++------- .../StorkResponseTimeLoadBalancerTest.java | 6 ++-- .../stork/StorkWithPathIntegrationTest.java | 32 +++++++++++------ .../client/impl/StorkClientRequestFilter.java | 10 +++--- .../it/rest/client/reactive/stork/Client.java | 8 +++++ .../reactive/stork/ClientCallingResource.java | 10 ++++++ .../reactive/stork/FastWiremockServer.java | 3 ++ .../stork/RestClientReactiveStorkTest.java | 13 +++++++ .../reactive/stork/SlowWiremockServer.java | 3 ++ 13 files changed, 154 insertions(+), 31 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java index 664dea477ef7d..baa2e8d3771d5 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java @@ -1,7 +1,11 @@ package io.quarkus.rest.client.reactive.stork; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -10,4 +14,13 @@ public interface HelloClient { @GET String hello(); + + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Path("/") + String echo(String name); + + @GET + @Path("/{name}") + public String helloWithPathParam(@PathParam("name") String name); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java index e9966a8d8eac6..1a544e2ab878e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java @@ -1,7 +1,13 @@ package io.quarkus.rest.client.reactive.stork; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; @Path("/hello") public class HelloResource { @@ -12,4 +18,16 @@ public class HelloResource { public String hello() { return HELLO_WORLD; } + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public String invoke(@PathParam("name") String name) { + return "Hello, " + name; + } + + @POST + public String echo(String name, @Context Request request) { + return "hello, " + name; + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java index 129b7aece4cda..51f11c1b539ca 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java @@ -4,6 +4,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -22,6 +23,18 @@ public String invokeClient() { return client.hello(); } + @Path("/v2/{name}") + @GET + public String invokeClientWithPathParamContainingSlash(@PathParam("name") String name) { + return client.helloWithPathParam(name + "/" + name); + } + + @Path("/{name}") + @GET + public String invokeClientWithPathParam(@PathParam("name") String name) { + return client.helloWithPathParam(name); + } + @Path("/cdi") @GET public String invokeCdiClient() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java index f30d13b937008..5a12b520c497b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java @@ -67,4 +67,25 @@ void shouldModifyStorkSettings() { .body(equalTo(WIREMOCK_RESPONSE)); // @formatter:on } + + @Test + void shouldSayHelloNameWithSlash() { + when() + .get("/helper/v2/stork") + .then() + .statusCode(200) + // The response contains an encoded `/` + .body(equalTo("Hello, stork/stork")); + + } + + @Test + void shouldSayHelloNameWithBlank() { + when() + .get("/helper/smallrye stork") + .then() + .statusCode(200) + // The response contains an encoded blank espace + .body(equalTo("Hello, smallrye stork")); + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java index cb22c1393db59..639ae39cd8fac 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java @@ -15,8 +15,6 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; import io.smallrye.stork.api.NoSuchServiceDefinitionException; @@ -24,45 +22,58 @@ public class StorkIntegrationTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-application.properties"); @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldDetermineUrlViaStork() { String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello")) - .build(HelloClient2.class) + .build(HelloClient.class) .echo("black and white bird"); assertThat(greeting).isEqualTo("hello, black and white bird"); + + greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello")) + .build(HelloClient.class) + .helloWithPathParam("black and white bird"); + assertThat(greeting).isEqualTo("Hello, black and white bird"); } @Test void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException { - String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(new URI("stork://hello-service/hello")).request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/hello")).request() .get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); + + greeting = ClientBuilder.newClient().target("stork://hello-service/hello").path("big bird").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test void shouldDetermineUrlViaStorkCDI() { String greeting = client.echo("big bird"); assertThat(greeting).isEqualTo("hello, big bird"); + + greeting = client.helloWithPathParam("big bird"); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test @Timeout(20) void shouldFailOnUnknownService() { - HelloClient2 client2 = RestClientBuilder.newBuilder() + HelloClient client = RestClientBuilder.newBuilder() .baseUri(URI.create("stork://nonexistent-service")) - .build(HelloClient2.class); - assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); + .build(HelloClient.class); + assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java index 507ca9eb31b1a..9dc52a8d0d271 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java @@ -16,8 +16,6 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; public class StorkResponseTimeLoadBalancerTest { @@ -28,7 +26,7 @@ public class StorkResponseTimeLoadBalancerTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-stat-lb.properties"); @BeforeAll @@ -46,7 +44,7 @@ public static void shutDown() { } @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldUseFasterService() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java index 26ba43279cbae..26ac15b363f45 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java @@ -15,8 +15,6 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; import io.smallrye.stork.api.NoSuchServiceDefinitionException; @@ -24,45 +22,57 @@ public class StorkWithPathIntegrationTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-application-with-path.properties"); @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldDetermineUrlViaStork() { String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service")) - .build(HelloClient2.class) + .build(HelloClient.class) .echo("black and white bird"); assertThat(greeting).isEqualTo("hello, black and white bird"); + + greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service")) + .build(HelloClient.class) + .helloWithPathParam("black and white bird"); + assertThat(greeting).isEqualTo("Hello, black and white bird"); } @Test void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException { String greeting = ClientBuilder.newClient().target("stork://hello-service").request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(new URI("stork://hello-service")).request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/")).request() .get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); + + greeting = ClientBuilder.newClient().target("stork://hello-service/").path("big bird").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test void shouldDetermineUrlViaStorkCDI() { String greeting = client.echo("big bird"); assertThat(greeting).isEqualTo("hello, big bird"); + + greeting = client.helloWithPathParam("big bird"); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test @Timeout(20) void shouldFailOnUnknownService() { - HelloClient2 client2 = RestClientBuilder.newBuilder() + HelloClient client = RestClientBuilder.newBuilder() .baseUri(URI.create("stork://nonexistent-service")) - .build(HelloClient2.class); - assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); + .build(HelloClient.class); + assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java index 7083d96803940..60990009a9d88 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java @@ -7,6 +7,7 @@ import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.ext.Provider; import org.jboss.logging.Logger; @@ -62,7 +63,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { } // Service instance can also contain an optional path. Optional path = instance.getPath(); - String actualPath = uri.getPath(); + String actualPath = uri.getRawPath(); if (path.isPresent()) { var p = path.get(); if (!p.startsWith("/")) { @@ -79,11 +80,12 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { } } } - + //To avoid the path double encoding we create uri with path=null and set the path after URI newUri = new URI(scheme, uri.getUserInfo(), host, port, - actualPath, uri.getQuery(), uri.getFragment()); - requestContext.setUri(newUri); + null, uri.getQuery(), uri.getFragment()); + URI build = UriBuilder.fromUri(newUri).path(actualPath).build(); + requestContext.setUri(build); if (measureTime && instance.gatherStatistics()) { requestContext.setCallStatsCollector(instance); } diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java index 3c3bcea3042bc..ebf44dc314680 100644 --- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java +++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java @@ -2,6 +2,9 @@ import jakarta.ws.rs.Consumes; 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.rest.client.inject.RegisterRestClient; @@ -11,4 +14,9 @@ public interface Client { @GET @Consumes(MediaType.TEXT_PLAIN) String echo(String name); + + @GET + @Path("/v2/{name}") + @Produces(MediaType.TEXT_PLAIN) + String invoke(@PathParam("name") String name); } diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java index 2e3a307174dc8..782165a5c3895 100644 --- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java @@ -3,6 +3,9 @@ import jakarta.enterprise.context.ApplicationScoped; 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.rest.client.inject.RestClient; @@ -17,4 +20,11 @@ public class ClientCallingResource { public String passThrough() { return client.echo("World!"); } + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public String invoke(@PathParam("name") String name) { + return client.invoke(name + "/" + name); + } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java index ba55cdc0f30f0..a6b277681a970 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java @@ -1,6 +1,7 @@ package io.quarkus.it.rest.reactive.stork; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; import java.util.Map; @@ -25,6 +26,8 @@ int httpsPort() { protected Map initWireMock(WireMockServer server) { server.stubFor(WireMock.get("/hello") .willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200))); + server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}")) + .willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200))); return Map.of("fast-service", "localhost:8443"); } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java index 5adb6924ee71b..884d5ffbbfc0f 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java @@ -54,4 +54,17 @@ void shouldUseFasterService() { // after hitting the slow endpoint, we should only use the fast one: assertThat(responses).containsOnly(FAST_RESPONSE, FAST_RESPONSE, FAST_RESPONSE); } + + @Test + void shouldUseV2Service() { + Set responses = new HashSet<>(); + + for (int i = 0; i < 2; i++) { + Response response = when().get("/client/quarkus"); + response.then().statusCode(200); + } + + responses.clear(); + + } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java index 7dbc7f74b9b9d..3b1a051f345a6 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java @@ -1,6 +1,7 @@ package io.quarkus.it.rest.reactive.stork; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; import java.util.Map; @@ -26,6 +27,8 @@ protected Map initWireMock(WireMockServer server) { server.stubFor(WireMock.get("/hello") .willReturn(aResponse().withFixedDelay(1000) .withBody(SLOW_RESPONSE).withStatus(200))); + server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}")) + .willReturn(aResponse().withFixedDelay(1000).withBody(SLOW_RESPONSE).withStatus(200))); return Map.of("slow-service", "localhost:8444"); } } From 315a25498bae4ca4cfdfd2cab932e48d59f07a00 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 Jan 2024 10:56:31 +0200 Subject: [PATCH 181/212] Make Picocli version providers unremovable classes (cherry picked from commit 8161071196200925c2be70c0664a6f8898c7e905) --- .../picocli/deployment/PicocliProcessor.java | 13 ++++++++++ .../it/picocli/EntryWithVersionCommand.java | 9 +++++++ .../quarkus/it/picocli/VersionProvider.java | 22 ++++++++++++++++ .../io/quarkus/it/picocli/TestVersion.java | 25 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java create mode 100644 integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java create mode 100644 integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java diff --git a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java index b2ed32c390f8b..18c452474e5f7 100644 --- a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java +++ b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java @@ -12,6 +12,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.AutoAddScopeBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Feature; @@ -76,6 +77,7 @@ IndexDependencyBuildItem picocliIndexDependency() { void picocliRunner(ApplicationIndexBuildItem applicationIndex, CombinedIndexBuildItem combinedIndex, BuildProducer additionalBean, + BuildProducer unremovableBean, BuildProducer quarkusApplicationClass, BuildProducer annotationsTransformer) { IndexView index = combinedIndex.getIndex(); @@ -99,6 +101,17 @@ void picocliRunner(ApplicationIndexBuildItem applicationIndex, additionalBean.produce(AdditionalBeanBuildItem.unremovableOf(DefaultPicocliCommandLineFactory.class)); quarkusApplicationClass.produce(new QuarkusApplicationClassBuildItem(PicocliRunner.class)); } + + // Make all classes that can be instantiated by IFactory unremovable + unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(CommandLine.ITypeConverter.class, + CommandLine.IVersionProvider.class, + CommandLine.IModelTransformer.class, + CommandLine.IModelTransformer.class, + CommandLine.IDefaultValueProvider.class, + CommandLine.IParameterConsumer.class, + CommandLine.IParameterPreprocessor.class, + CommandLine.INegatableOptionTransformer.class, + CommandLine.IHelpFactory.class)); } private List classesAnnotatedWith(IndexView indexView, String annotationClassName) { diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java new file mode 100644 index 0000000000000..c2121fe991b46 --- /dev/null +++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java @@ -0,0 +1,9 @@ +package io.quarkus.it.picocli; + +import io.quarkus.picocli.runtime.annotations.TopCommand; +import picocli.CommandLine; + +@TopCommand +@CommandLine.Command(mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) +public class EntryWithVersionCommand { +} diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java new file mode 100644 index 0000000000000..e06d7c92156f7 --- /dev/null +++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java @@ -0,0 +1,22 @@ +package io.quarkus.it.picocli; + +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import picocli.CommandLine; + +@Singleton +public class VersionProvider implements CommandLine.IVersionProvider { + + private final String version; + + public VersionProvider(@ConfigProperty(name = "some.version", defaultValue = "0.0.1") String version) { + this.version = version; + } + + @Override + public String[] getVersion() throws Exception { + return new String[] { version }; + } +} diff --git a/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java new file mode 100644 index 0000000000000..1218095d3de49 --- /dev/null +++ b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java @@ -0,0 +1,25 @@ +package io.quarkus.it.picocli; + +import static io.quarkus.it.picocli.TestUtils.createConfig; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class TestVersion { + + @RegisterExtension + static final QuarkusProdModeTest config = createConfig("version-app", EntryWithVersionCommand.class, + VersionProvider.class) + .overrideConfigKey("some.version", "1.1") + .setCommandLineParameters("--version"); + + @Test + public void simpleTest() { + Assertions.assertThat(config.getStartupConsoleOutput()).containsOnlyOnce("1.1"); + Assertions.assertThat(config.getExitCode()).isZero(); + } + +} From a22eed5e9bcd3fee001bd5bb7c9142d41c85b557 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 15 Jan 2024 17:28:37 +0100 Subject: [PATCH 182/212] Update to Hibernate ORM 6.2.19 and Hibernate Reactive 2.0.8.Final --- bom/application/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 65b925421f065..0377e3763b1f3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -97,10 +97,10 @@ 1.5.1 - 6.2.18.Final + 6.2.19.Final 1.14.7 6.0.6.Final - 2.0.6.Final + 2.0.8.Final 8.0.1.Final 6.2.2.Final From ab55048e62f26d74b68e51d9140624f40a1e4eac Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Sat, 9 Dec 2023 01:34:04 +0200 Subject: [PATCH 183/212] Netty requires more classes to be runtime initialized In https://github.com/quarkusio/quarkus/pull/37347 we moved `PlatformDependent` and `PlatformDependent0` classes to run-time-initialization as they are platform-dependent and thus need to be initialized on the target platform. It looks like there are more classes like these that need to be runtime initialized as they transitively rely on platform dependent values. Closes https://github.com/Karm/mandrel-integration-tests/issues/236 Supersedes https://github.com/quarkusio/quarkus/pull/37628 (cherry picked from commit 72d7b79a7a67344145df94b98a271c7393777f90) --- .../netty/deployment/NettyProcessor.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index 5de637d124c8c..8d0f294af25fe 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -162,8 +162,28 @@ NativeImageConfigBuildItem build( log.debug("Not registering Netty native kqueue classes as they were not found"); } - builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent"); - builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent0"); + builder.addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent") + .addRuntimeReinitializedClass("io.netty.util.internal.PlatformDependent0") + .addRuntimeReinitializedClass("io.netty.buffer.PooledByteBufAllocator"); + + if (QuarkusClassLoader.isClassPresentAtRuntime("io.netty.buffer.UnpooledByteBufAllocator")) { + builder.addRuntimeReinitializedClass("io.netty.buffer.UnpooledByteBufAllocator") + .addRuntimeReinitializedClass("io.netty.buffer.Unpooled") + .addRuntimeReinitializedClass("io.vertx.core.http.impl.Http1xServerResponse") + .addRuntimeReinitializedClass("io.netty.handler.codec.http.HttpObjectAggregator") + .addRuntimeReinitializedClass("io.netty.handler.codec.ReplayingDecoderByteBuf") + .addRuntimeReinitializedClass("io.vertx.core.parsetools.impl.RecordParserImpl"); + + if (QuarkusClassLoader.isClassPresentAtRuntime("io.vertx.ext.web.client.impl.MultipartFormUpload")) { + builder.addRuntimeReinitializedClass("io.vertx.ext.web.client.impl.MultipartFormUpload"); + } + + if (QuarkusClassLoader + .isClassPresentAtRuntime("org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartFormUpload")) { + builder.addRuntimeReinitializedClass( + "org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartFormUpload"); + } + } return builder //TODO: make configurable .build(); From 602b2dd42e4621f51f7deba6d37242f5c372acaf Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Mon, 8 Jan 2024 22:21:28 +0100 Subject: [PATCH 184/212] doc: mention registration is per extension not per repo (cherry picked from commit e7cdc2f50d3cb2545e2b713afa1d97182ded20a4) --- docs/src/main/asciidoc/writing-extensions.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 861bfa9fb9578..61b7dee65d648 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -3051,5 +3051,7 @@ group-id: artifact-id: ---- +NOTE: When your repository contains multiple extensions, you need to create a separate file for each individual extension, not just one file for the entire repository. + That's all. Once the pull request is merged, a scheduled job will check Maven Central for new versions and update the xref:extension-registry-user.adoc[Quarkus Extension Registry]. From 613d9e36139b227e2cc1a20611488e1dfe4db285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:49:42 +0000 Subject: [PATCH 185/212] Bump io.quarkus.bot:build-reporter-maven-extension from 3.2.2 to 3.3.3 Bumps [io.quarkus.bot:build-reporter-maven-extension](https://github.com/quarkusio/build-reporter) from 3.2.2 to 3.3.3. - [Commits](https://github.com/quarkusio/build-reporter/compare/3.2.2...3.3.3) --- updated-dependencies: - dependency-name: io.quarkus.bot:build-reporter-maven-extension dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit 775ae550d6e4f717bf2bed0e0337a15430463d1f) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e041b8d91bff9..d60d6c8db4be1 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ io.quarkus.bot build-reporter-maven-extension - 3.2.2 + 3.3.3 From f6082fbb536997faa3ca76e5cf086ff2fec971ab Mon Sep 17 00:00:00 2001 From: Jerome Prinet Date: Tue, 9 Jan 2024 14:48:47 +0100 Subject: [PATCH 186/212] Change storage location on CI to avoid Develocity scan dumps with disabled publication to be captured for republication (cherry picked from commit 5d61c691acce60e600fffa79a6f5dae7b1ad2535) --- .mvn/gradle-enterprise-custom-user-data.groovy | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.mvn/gradle-enterprise-custom-user-data.groovy b/.mvn/gradle-enterprise-custom-user-data.groovy index a076e48718294..cec7b24758994 100644 --- a/.mvn/gradle-enterprise-custom-user-data.groovy +++ b/.mvn/gradle-enterprise-custom-user-data.groovy @@ -12,6 +12,17 @@ if(session?.getRequest()?.getBaseDirectory() != null) { if(!publish) { // do not publish a build scan for test builds log.debug("Disabling build scan publication for " + session.getRequest().getBaseDirectory()) + + // change storage location on CI to avoid Develocity scan dumps with disabled publication to be captured for republication + if (System.env.GITHUB_ACTIONS) { + try { + def storageLocationTmpDir = java.nio.file.Files.createTempDirectory(java.nio.file.Paths.get(System.env.RUNNER_TEMP), "buildScanTmp").toAbsolutePath() + log.debug('Update storage location to ' + storageLocationTmpDir) + gradleEnterprise.setStorageDirectory(storageLocationTmpDir) + } catch (IOException e) { + log.error('Temporary storage location directory cannot be created, the Build Scan will be published', e) + } + } } } buildScan.publishAlwaysIf(publish) From 98ae832c7fa49e6b9ac53246997d8a6e2d8641a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 10 Jan 2024 12:58:06 +0100 Subject: [PATCH 187/212] Add missing methods to ReactiveMongoCollection Fixes #38114 (cherry picked from commit 8debb80c7e024deeb65c12a7a7fe25cf7c227265) --- .../impl/ReactiveMongoCollectionImpl.java | 63 +++++++ .../reactive/ReactiveMongoCollection.java | 169 ++++++++++++++++++ 2 files changed, 232 insertions(+) diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/impl/ReactiveMongoCollectionImpl.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/impl/ReactiveMongoCollectionImpl.java index acac2af661e43..a122df0796b70 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/impl/ReactiveMongoCollectionImpl.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/impl/ReactiveMongoCollectionImpl.java @@ -584,6 +584,27 @@ public Uni updateOne(ClientSession clientSession, Bson filter, Bso return Wrappers.toUni(collection.updateOne(clientSession, filter, update, options)); } + @Override + public Uni updateOne(Bson filter, List update) { + return Wrappers.toUni(collection.updateOne(filter, update)); + } + + @Override + public Uni updateOne(Bson filter, List update, UpdateOptions options) { + return Wrappers.toUni(collection.updateOne(filter, update, options)); + } + + @Override + public Uni updateOne(ClientSession clientSession, Bson filter, List update) { + return Wrappers.toUni(collection.updateOne(clientSession, filter, update)); + } + + @Override + public Uni updateOne(ClientSession clientSession, Bson filter, List update, + UpdateOptions options) { + return Wrappers.toUni(collection.updateOne(clientSession, filter, update, options)); + } + @Override public Uni updateMany(Bson filter, Bson update) { return Wrappers.toUni(collection.updateMany(filter, update)); @@ -605,6 +626,27 @@ public Uni updateMany(ClientSession clientSession, Bson filter, Bs return Wrappers.toUni(collection.updateMany(clientSession, filter, update, options)); } + @Override + public Uni updateMany(Bson filter, List update) { + return Wrappers.toUni(collection.updateMany(filter, update)); + } + + @Override + public Uni updateMany(Bson filter, List update, UpdateOptions options) { + return Wrappers.toUni(collection.updateMany(filter, update, options)); + } + + @Override + public Uni updateMany(ClientSession clientSession, Bson filter, List update) { + return Wrappers.toUni(collection.updateMany(clientSession, filter, update)); + } + + @Override + public Uni updateMany(ClientSession clientSession, Bson filter, List update, + UpdateOptions options) { + return Wrappers.toUni(collection.updateMany(clientSession, filter, update, options)); + } + @Override public Uni findOneAndDelete(Bson filter) { return Wrappers.toUni(collection.findOneAndDelete(filter)); @@ -667,6 +709,27 @@ public Uni findOneAndUpdate(ClientSession clientSession, Bson filter, Bson up return Wrappers.toUni(collection.findOneAndUpdate(clientSession, filter, update, options)); } + @Override + public Uni findOneAndUpdate(Bson filter, List update) { + return Wrappers.toUni(collection.findOneAndUpdate(filter, update)); + } + + @Override + public Uni findOneAndUpdate(Bson filter, List update, FindOneAndUpdateOptions options) { + return Wrappers.toUni(collection.findOneAndUpdate(filter, update, options)); + } + + @Override + public Uni findOneAndUpdate(ClientSession clientSession, Bson filter, List update) { + return Wrappers.toUni(collection.findOneAndUpdate(clientSession, filter, update)); + } + + @Override + public Uni findOneAndUpdate(ClientSession clientSession, Bson filter, List update, + FindOneAndUpdateOptions options) { + return Wrappers.toUni(collection.findOneAndUpdate(clientSession, filter, update, options)); + } + @Override public Uni drop() { return Wrappers.toUni(collection.drop()); diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/reactive/ReactiveMongoCollection.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/reactive/ReactiveMongoCollection.java index 65ecdb58f72a4..34c7532d1a7c8 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/reactive/ReactiveMongoCollection.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/reactive/ReactiveMongoCollection.java @@ -1014,6 +1014,62 @@ Uni replaceOne(ClientSession clientSession, Bson filter, T replace Uni updateOne(ClientSession clientSession, Bson filter, Bson update, UpdateOptions options); + /** + * Update a single document in the collection according to the specified arguments. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the UpdateResult + */ + Uni updateOne(Bson filter, List update); + + /** + * Update a single document in the collection according to the specified arguments. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return a publisher with a single element the UpdateResult + */ + Uni updateOne(Bson filter, List update, UpdateOptions options); + + /** + * Update a single document in the collection according to the specified arguments. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the UpdateResult + */ + Uni updateOne(ClientSession clientSession, Bson filter, List update); + + /** + * Update a single document in the collection according to the specified arguments. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return a publisher with a single element the UpdateResult + */ + Uni updateOne(ClientSession clientSession, Bson filter, List update, UpdateOptions options); + /** * Update all documents in the collection according to the specified arguments. * @@ -1059,6 +1115,46 @@ Uni updateOne(ClientSession clientSession, Bson filter, Bson updat Uni updateMany(ClientSession clientSession, Bson filter, Bson update, UpdateOptions options); + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the UpdateResult + */ + Uni updateMany(Bson filter, List update); + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return a publisher with a single element the UpdateResult + */ + Uni updateMany(Bson filter, List update, UpdateOptions options); + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the UpdateResult + */ + Uni updateMany(ClientSession clientSession, Bson filter, List update); + + /** + * Update all documents in the collection according to the specified arguments. + * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the update operation + * @return a publisher with a single element the UpdateResult + */ + Uni updateMany(ClientSession clientSession, Bson filter, List update, UpdateOptions options); + /** * Atomically find a document and remove it. * @@ -1217,6 +1313,79 @@ Uni findOneAndReplace(ClientSession clientSession, Bson filter, T replacement Uni findOneAndUpdate(ClientSession clientSession, Bson filter, Bson update, FindOneAndUpdateOptions options); + /** + * Atomically find a document and update it. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the document that was updated. Depending on the value of the + * {@code returnOriginal} + * property, this will either be the document as it was before the update or as it is after the update. If no + * documents matched the + * query filter, then null will be returned + */ + Uni findOneAndUpdate(Bson filter, List update); + + /** + * Atomically find a document and update it. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the operation + * @return a publisher with a single element the document that was updated. Depending on the value of the + * {@code returnOriginal} + * property, this will either be the document as it was before the update or as it is after the update. If no + * documents matched the + * query filter, then null will be returned + */ + Uni findOneAndUpdate(Bson filter, List update, FindOneAndUpdateOptions options); + + /** + * Atomically find a document and update it. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @return a publisher with a single element the document that was updated. Depending on the value of the + * {@code returnOriginal} + * property, this will either be the document as it was before the update or as it is after the update. If no + * documents matched the + * query filter, then null will be returned + */ + Uni findOneAndUpdate(ClientSession clientSession, Bson filter, List update); + + /** + * Atomically find a document and update it. + * + *

+ * Note: Supports retryable writes on MongoDB server versions 3.6 or higher when the retryWrites setting is enabled. + *

+ * + * @param clientSession the client session with which to associate this operation + * @param filter a document describing the query filter, which may not be null. + * @param update a pipeline describing the update, which may not be null. + * @param options the options to apply to the operation + * @return a publisher with a single element the document that was updated. Depending on the value of the + * {@code returnOriginal} + * property, this will either be the document as it was before the update or as it is after the update. If no + * documents matched the + * query filter, then null will be returned + */ + Uni findOneAndUpdate(ClientSession clientSession, Bson filter, List update, + FindOneAndUpdateOptions options); + /** * Drops this collection from the database. * From df32d25724216c96ab6f460e19d21ec6c355ca5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:35:14 +0000 Subject: [PATCH 188/212] Bump io.quarkus:quarkus-platform-bom-maven-plugin Bumps [io.quarkus:quarkus-platform-bom-maven-plugin](https://github.com/quarkusio/quarkus-platform-bom-generator) from 0.0.101 to 0.0.102. - [Release notes](https://github.com/quarkusio/quarkus-platform-bom-generator/releases) - [Commits](https://github.com/quarkusio/quarkus-platform-bom-generator/compare/0.0.101...0.0.102) --- updated-dependencies: - dependency-name: io.quarkus:quarkus-platform-bom-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit dfb3c93a5ef25b8aa3d4dd186fd768a8d6b1dbf8) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d60d6c8db4be1..8faf852836586 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ jdbc:postgresql:hibernate_orm_test 4.5.1 - 0.0.101 + 0.0.102 false false From 3c52567100eb982ed0a304db7b814a776a5524a8 Mon Sep 17 00:00:00 2001 From: Idryss Bourdier Date: Mon, 18 Dec 2023 16:06:57 +0100 Subject: [PATCH 189/212] Fixing Jaxb unmarshalling error with native compilation (cherry picked from commit 24b321e29b9b96f4731ec8bddc76a876c57c8dac) --- .../jaxb/deployment/JaxbProcessor.java | 22 ++++++++++++--- .../io/quarkus/it/jaxb/BookIBANField.java | 24 +++++++++++++++++ .../io/quarkus/it/jaxb/BookWithParent.java | 27 +++++++++++++++++++ .../java/io/quarkus/it/jaxb/JaxbResource.java | 15 +++++++++++ .../test/java/io/quarkus/it/jaxb/JaxbIT.java | 18 ++++++++++++- 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java create mode 100644 integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java index 17768f6d8e62b..3d488e9d012d9 100644 --- a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java +++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java @@ -70,6 +70,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; @@ -186,6 +187,7 @@ void processAnnotationsAndIndexFiles( BuildProducer proxyDefinitions, CombinedIndexBuildItem combinedIndexBuildItem, List fileRoots, + BuildProducer reflectiveHierarchies, BuildProducer reflectiveClass, BuildProducer resource, BuildProducer resourceBundle, @@ -202,10 +204,11 @@ void processAnnotationsAndIndexFiles( for (DotName jaxbRootAnnotation : JAXB_ROOT_ANNOTATIONS) { for (AnnotationInstance jaxbRootAnnotationInstance : index .getAnnotations(jaxbRootAnnotation)) { - if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS) { - String className = jaxbRootAnnotationInstance.target().asClass().name().toString(); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).methods().fields().build()); - classesToBeBound.add(className); + if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS + && !JAXB_ANNOTATIONS.contains(jaxbRootAnnotationInstance.target().asClass().getClass())) { + DotName targetClass = jaxbRootAnnotationInstance.target().asClass().name(); + addReflectiveHierarchyClass(targetClass, reflectiveHierarchies, index); + classesToBeBound.add(targetClass.toString()); jaxbRootAnnotationsDetected = true; } } @@ -412,6 +415,17 @@ public static Stream safeWalk(Path p) { } } + private void addReflectiveHierarchyClass(DotName className, + BuildProducer reflectiveHierarchy, + IndexView index) { + Type jandexType = Type.create(className, Type.Kind.CLASS); + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder() + .type(jandexType) + .index(index) + .source(getClass().getSimpleName() + " > " + jandexType.name().toString()) + .build()); + } + private void addReflectiveClass(BuildProducer reflectiveClass, boolean methods, boolean fields, String... className) { reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className)); diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java new file mode 100644 index 0000000000000..5fc7a28bdcfa9 --- /dev/null +++ b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java @@ -0,0 +1,24 @@ +package io.quarkus.it.jaxb; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlTransient; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlTransient +public abstract class BookIBANField { + @XmlElement + private String IBAN; + + public BookIBANField() { + } + + public void setIBAN(String IBAN) { + this.IBAN = IBAN; + } + + public String getIBAN() { + return IBAN; + } +} diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java new file mode 100644 index 0000000000000..d4d81b481e267 --- /dev/null +++ b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java @@ -0,0 +1,27 @@ +package io.quarkus.it.jaxb; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlType; + +@XmlRootElement +@XmlType(propOrder = { "IBAN", "title" }) +public class BookWithParent extends BookIBANField { + @XmlElement + private String title; + + public BookWithParent() { + } + + public BookWithParent(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java index b03507a82089e..d6a9b8e386574 100644 --- a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java +++ b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java @@ -65,4 +65,19 @@ public io.quarkus.it.jaxb.Response seeAlso() { return response; } + //Test for Jaxb with parent class field + @Path("/bookwithparent") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getBookWithParent(@QueryParam("name") String name, @QueryParam("iban") String iban) throws JAXBException { + BookWithParent bookWithParent = new BookWithParent(); + bookWithParent.setTitle(name); + bookWithParent.setIBAN(iban); + JAXBContext context = JAXBContext.newInstance(bookWithParent.getClass()); + Marshaller marshaller = context.createMarshaller(); + StringWriter sw = new StringWriter(); + marshaller.marshal(bookWithParent, sw); + return sw.toString(); + } + } diff --git a/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java b/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java index ae862b4a11e41..06611d4a0c081 100644 --- a/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java +++ b/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java @@ -1,8 +1,24 @@ package io.quarkus.it.jaxb; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest public class JaxbIT extends JaxbTest { - + //We have to test native executable of Jaxb + @Test + public void bookWithParent() { + given().when() + .param("name", "Foundation") + .param("iban", "4242") + .get("/jaxb/bookwithparent") + .then() + .statusCode(200) + .body(is( + "4242Foundation")); + } } From 65cac1bf3f58dc0062a977f381d7eead5170ec7e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 15 Jan 2024 08:56:50 +0200 Subject: [PATCH 190/212] Add necessary Vertx dependency to quarkus-cache Fixes: #38157 (cherry picked from commit dfb681e24f005aebadd1624f70d9d84b34769597) --- extensions/cache/deployment/pom.xml | 4 ++++ extensions/cache/runtime/pom.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/extensions/cache/deployment/pom.xml b/extensions/cache/deployment/pom.xml index 055055a4c594c..7d8309729061c 100644 --- a/extensions/cache/deployment/pom.xml +++ b/extensions/cache/deployment/pom.xml @@ -38,6 +38,10 @@ io.quarkus quarkus-mutiny-deployment + + io.quarkus + quarkus-vertx-deployment + io.quarkus quarkus-vertx-http-dev-ui-spi diff --git a/extensions/cache/runtime/pom.xml b/extensions/cache/runtime/pom.xml index 70993637b709a..e849a5b223d04 100644 --- a/extensions/cache/runtime/pom.xml +++ b/extensions/cache/runtime/pom.xml @@ -27,6 +27,10 @@ io.quarkus quarkus-mutiny + + io.quarkus + quarkus-vertx + io.quarkus quarkus-cache-runtime-spi From 91f31116f1000f7764a9a06769aff24f6e5dad0e Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Mon, 8 Jan 2024 09:53:00 +0100 Subject: [PATCH 191/212] Add exclude + additionals option to AssembleDownstreamDocumentation Why: * downstreamdoc.yaml is used to generate the list of docs used by downstream doc builds and today its hardcoded thus very limited for downstream docs ability to adjust without changing the quarkus repo. This change addreses the need by: * adding a DOWNSTREAM_CONFIG_FILE to *optionally* override the hardcoded downstreamdoc.yaml * adding a DOWNSTREAM_ADDITONALS env var to the downstream doc build that can be used to add docs to the list of docs used by downstream doc builds * adding a DOWNSTREAM_EXCLUDES env var to the downstream doc build that can be used to exclude docs from the list of docs used by downstream doc builds + additionals (cherry picked from commit 865a88214a5aae7148c2cf63b5e78b637babda82) --- .../AssembleDownstreamDocumentation.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java b/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java index dbb4c74cd9b5c..0acc2d32ddf43 100755 --- a/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java +++ b/docs/src/main/java/io/quarkus/docs/generation/AssembleDownstreamDocumentation.java @@ -1,11 +1,17 @@ package io.quarkus.docs.generation; +//These are here to allow running the script directly from command line/IDE +//The real deps and call are in the pom.xml +//DEPS org.jboss.logging:jboss-logging:3.4.1.Final +//DEPS com.fasterxml.jackson.core:jackson-databind:2.12.3 +//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.8.0.rc1 import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -88,7 +94,30 @@ public static void main(String[] args) throws Exception { ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); yamlObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - ConfigFile configFile = yamlObjectMapper.readValue(new File("downstreamdoc.yaml"), ConfigFile.class); + String configFilePath = System.getenv("DOWNSTREAM_CONFIG_FILE"); + if (configFilePath == null) { + configFilePath = "downstreamdoc.yaml"; + } + ConfigFile configFile = yamlObjectMapper.readValue(new File(configFilePath), ConfigFile.class); + + String additionals = System.getenv("DOWNSTREAM_ADDITIONALS"); + if (additionals != null) { + String[] additional_files = additionals.split(","); + LOG.info("Additional files: " + Arrays.toString(additional_files)); + for (String file : additional_files) { + configFile.guides.add(file); + } + } + + String excludes = System.getenv("DOWNSTREAM_EXCLUDES"); + if (excludes != null) { + String[] excludePatterns = excludes.split(","); + LOG.info("Excluding patterns: " + Arrays.toString(excludePatterns)); + for (String pattern : excludePatterns) { + Pattern regexPattern = Pattern.compile(pattern); + configFile.guides.removeIf(guide -> regexPattern.matcher(guide).find()); + } + } Set guides = new TreeSet<>(); Set simpleIncludes = new TreeSet<>(); From 20d566cb2ceb329bafdc5c2804ecc785603c85b8 Mon Sep 17 00:00:00 2001 From: SIMULATAN Date: Wed, 10 Jan 2024 13:30:06 +0100 Subject: [PATCH 192/212] ConfigRecorder: fix value changed check with null build-time values (cherry picked from commit 0ebd47b80291da39602dd6f189ad2eba60f16b3a) --- .../java/io/quarkus/runtime/configuration/ConfigRecorder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java index ff18b51bb6562..ebf0ed694bbdf 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import org.eclipse.microprofile.config.ConfigProvider; @@ -42,7 +43,8 @@ public void handleConfigChange(Map buildTimeRuntimeValues) for (Map.Entry entry : buildTimeRuntimeValues.entrySet()) { ConfigValue currentValue = config.getConfigValue(entry.getKey()); // Check for changes. Also, we only have a change if the source ordinal is higher - if (currentValue.getValue() != null && !entry.getValue().getValue().equals(currentValue.getValue()) + // The config value can be null (for ex. if the property uses environment variables not available at build time) + if (currentValue.getValue() != null && !Objects.equals(entry.getValue().getValue(), currentValue.getValue()) && entry.getValue().getSourceOrdinal() < currentValue.getSourceOrdinal()) { mismatches.add( " - " + entry.getKey() + " is set to '" + currentValue.getValue() From d40a7209efcbdcb8fec5ff2a04181576c1edbd09 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 11 Jan 2024 10:56:14 +0100 Subject: [PATCH 193/212] [Gradle] make quarkusRun depend on quarkusBuild (cherry picked from commit 5d2923a67c6353f84f762d3148d90d0aa4c2f69e) --- .../src/main/java/io/quarkus/gradle/QuarkusPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index a44fcfd3d8780..f6092185bfa9f 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -227,7 +227,8 @@ public boolean isSatisfiedBy(Task t) { TaskProvider quarkusDev = tasks.register(QUARKUS_DEV_TASK_NAME, QuarkusDev.class, devRuntimeDependencies, quarkusExt); - TaskProvider quarkusRun = tasks.register(QUARKUS_RUN_TASK_NAME, QuarkusRun.class); + TaskProvider quarkusRun = tasks.register(QUARKUS_RUN_TASK_NAME, QuarkusRun.class, + build -> build.dependsOn(quarkusBuild)); TaskProvider quarkusRemoteDev = tasks.register(QUARKUS_REMOTE_DEV_TASK_NAME, QuarkusRemoteDev.class, devRuntimeDependencies, quarkusExt); TaskProvider quarkusTest = tasks.register(QUARKUS_TEST_TASK_NAME, QuarkusTest.class, From 518d71f99d76858a656c73367aeb8437ef839cd3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 15 Jan 2024 18:08:36 +0100 Subject: [PATCH 194/212] Fix a code example in RESTEasy Reactive doc Per report from @muditporwal in: https://github.com/quarkusio/quarkusio.github.io/pull/1866 (cherry picked from commit 40b0a65e5efffa01a47207720a402acb04e120fa) --- docs/src/main/asciidoc/resteasy-reactive.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index 5a4afac21f3d0..8faabcf584aad 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -1301,7 +1301,7 @@ public class Person { private final Long id; private final String first; private final String last; - @SecureField(rolesAllowed = ${role:admin}") <1> + @SecureField(rolesAllowed = "${role:admin}") <1> private String address; public Person(Long id, String first, String last) { From 08f00909a3cd79844b93913796ddfdb727168da4 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 4 Dec 2023 19:03:59 +0000 Subject: [PATCH 195/212] Improve security-oidc-bearer-token-authentication-tutorial --- ...oidc-bearer-token-authentication-tutorial.adoc | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc index 1be0d80000013..24829c571ceb9 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc @@ -52,11 +52,6 @@ You can clone the Git repository by running the command `git clone {quickstarts- The solution is located in the `security-openid-connect-quickstart` link:{quickstarts-tree-url}/security-openid-connect-quickstart[directory]. -== Procedure - -:sectnums: -:sectnumlevels: 3 - === Create the Maven project You can either create a new Maven project with the `oidc` extension or you can add the extension to an existing Maven project. @@ -234,7 +229,7 @@ For more information, see the <> section. docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:{keycloak.version} start-dev ---- ==== -* Where the `keycloak.version` is set to version `17.0.0` or later. +* Where the `keycloak.version` is set to version `23.0.0` or later. . You can access your Keycloak Server at http://localhost:8180[localhost:8180]. . To access the Keycloak Administration Console, log in as the `admin` user by using the following login credentials: @@ -243,13 +238,13 @@ docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=ad . Import the link:{quickstarts-tree-url}/security-openid-connect-quickstart/config/quarkus-realm.json[realm configuration file] from the upstream community repository to create a new realm. -For more information, see the Keycloak documentation about link:https://www.keycloak.org/docs/latest/server_admin/index.html#_create-realm[creating a new realm]. +For more information, see the Keycloak documentation about link:https://www.keycloak.org/docs/latest/server_admin/index.html#configuring-realms[create and configure a new realm]. [NOTE] ==== If you want to use the Keycloak Admin Client to configure your server from your application, you need to include either the `quarkus-keycloak-admin-client` or the `quarkus-keycloak-admin-client-reactive` (if the application uses `quarkus-rest-client-reactive`) extension. -For more information, see the link:{url-quarkusio-guides}security-keycloak-admin-client[Quarkus Keycloak Admin Client] guide. +For more information, see the xref:security-keycloak-admin-client.adoc[Quarkus Keycloak Admin Client] guide. ==== @@ -262,8 +257,8 @@ For more information, see the link:{url-quarkusio-guides}security-keycloak-admin ==== include::{includes}/devtools/dev.adoc[] ==== -* link:{quarkusio-guides}/security-openid-connect-dev-services[Dev Services for Keycloak] will start a Keycloak container and import a `quarkus-realm.json`. -. Open a link:{url-quarkusio-guides}dev-ui[Dev UI], which you can find at http://localhost:8080/q/dev-ui[/q/dev-ui], then click a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. +* xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] will start a Keycloak container and import a `quarkus-realm.json`. +. Open a xref:dev-ui.adoc[Dev UI], which you can find at http://localhost:8080/q/dev-ui[/q/dev-ui], then click a `Provider: Keycloak` link in an `OpenID Connect` `Dev UI` card. . When prompted to log in to a `Single Page Application` provided by `OpenID Connect Dev UI`, do the following steps: * Log in as `alice` (password: `alice`), who has a `user` role. From 0f36fb719e0f64163897e5a444a75f2897485d49 Mon Sep 17 00:00:00 2001 From: shjones Date: Tue, 16 Jan 2024 13:26:42 +0000 Subject: [PATCH 196/212] Fix broken links in 3.6 branch --- .../security-oidc-bearer-token-authentication-tutorial.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc index 24829c571ceb9..2fcb1697c610a 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc @@ -229,7 +229,7 @@ For more information, see the <> section. docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:{keycloak.version} start-dev ---- ==== -* Where the `keycloak.version` is set to version `23.0.0` or later. +* Where the `keycloak.version` is set to version `17.0.0` or later. . You can access your Keycloak Server at http://localhost:8180[localhost:8180]. . To access the Keycloak Administration Console, log in as the `admin` user by using the following login credentials: From 36eac3c8e7cf7f1318f31a8df40fb175864fd1bb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 16 Jan 2024 16:03:45 +0100 Subject: [PATCH 197/212] Revert "Fixing Jaxb unmarshalling error with native compilation" This reverts commit 24b321e29b9b96f4731ec8bddc76a876c57c8dac. (cherry picked from commit ed722a12eeb221ace29a2c80ee1d5bc25a03a1ed) --- .../jaxb/deployment/JaxbProcessor.java | 22 +++------------ .../io/quarkus/it/jaxb/BookIBANField.java | 24 ----------------- .../io/quarkus/it/jaxb/BookWithParent.java | 27 ------------------- .../java/io/quarkus/it/jaxb/JaxbResource.java | 15 ----------- .../test/java/io/quarkus/it/jaxb/JaxbIT.java | 18 +------------ 5 files changed, 5 insertions(+), 101 deletions(-) delete mode 100644 integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java delete mode 100644 integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java index 3d488e9d012d9..17768f6d8e62b 100644 --- a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java +++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java @@ -70,7 +70,6 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; @@ -187,7 +186,6 @@ void processAnnotationsAndIndexFiles( BuildProducer proxyDefinitions, CombinedIndexBuildItem combinedIndexBuildItem, List fileRoots, - BuildProducer reflectiveHierarchies, BuildProducer reflectiveClass, BuildProducer resource, BuildProducer resourceBundle, @@ -204,11 +202,10 @@ void processAnnotationsAndIndexFiles( for (DotName jaxbRootAnnotation : JAXB_ROOT_ANNOTATIONS) { for (AnnotationInstance jaxbRootAnnotationInstance : index .getAnnotations(jaxbRootAnnotation)) { - if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS - && !JAXB_ANNOTATIONS.contains(jaxbRootAnnotationInstance.target().asClass().getClass())) { - DotName targetClass = jaxbRootAnnotationInstance.target().asClass().name(); - addReflectiveHierarchyClass(targetClass, reflectiveHierarchies, index); - classesToBeBound.add(targetClass.toString()); + if (jaxbRootAnnotationInstance.target().kind() == Kind.CLASS) { + String className = jaxbRootAnnotationInstance.target().asClass().name().toString(); + reflectiveClass.produce(ReflectiveClassBuildItem.builder(className).methods().fields().build()); + classesToBeBound.add(className); jaxbRootAnnotationsDetected = true; } } @@ -415,17 +412,6 @@ public static Stream safeWalk(Path p) { } } - private void addReflectiveHierarchyClass(DotName className, - BuildProducer reflectiveHierarchy, - IndexView index) { - Type jandexType = Type.create(className, Type.Kind.CLASS); - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder() - .type(jandexType) - .index(index) - .source(getClass().getSimpleName() + " > " + jandexType.name().toString()) - .build()); - } - private void addReflectiveClass(BuildProducer reflectiveClass, boolean methods, boolean fields, String... className) { reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className)); diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java deleted file mode 100644 index 5fc7a28bdcfa9..0000000000000 --- a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookIBANField.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.quarkus.it.jaxb; - -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlTransient; - -@XmlAccessorType(XmlAccessType.FIELD) -@XmlTransient -public abstract class BookIBANField { - @XmlElement - private String IBAN; - - public BookIBANField() { - } - - public void setIBAN(String IBAN) { - this.IBAN = IBAN; - } - - public String getIBAN() { - return IBAN; - } -} diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java deleted file mode 100644 index d4d81b481e267..0000000000000 --- a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/BookWithParent.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.quarkus.it.jaxb; - -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; -import jakarta.xml.bind.annotation.XmlType; - -@XmlRootElement -@XmlType(propOrder = { "IBAN", "title" }) -public class BookWithParent extends BookIBANField { - @XmlElement - private String title; - - public BookWithParent() { - } - - public BookWithParent(String title) { - this.title = title; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } -} diff --git a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java index d6a9b8e386574..b03507a82089e 100644 --- a/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java +++ b/integration-tests/jaxb/src/main/java/io/quarkus/it/jaxb/JaxbResource.java @@ -65,19 +65,4 @@ public io.quarkus.it.jaxb.Response seeAlso() { return response; } - //Test for Jaxb with parent class field - @Path("/bookwithparent") - @GET - @Produces(MediaType.TEXT_PLAIN) - public String getBookWithParent(@QueryParam("name") String name, @QueryParam("iban") String iban) throws JAXBException { - BookWithParent bookWithParent = new BookWithParent(); - bookWithParent.setTitle(name); - bookWithParent.setIBAN(iban); - JAXBContext context = JAXBContext.newInstance(bookWithParent.getClass()); - Marshaller marshaller = context.createMarshaller(); - StringWriter sw = new StringWriter(); - marshaller.marshal(bookWithParent, sw); - return sw.toString(); - } - } diff --git a/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java b/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java index 06611d4a0c081..ae862b4a11e41 100644 --- a/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java +++ b/integration-tests/jaxb/src/test/java/io/quarkus/it/jaxb/JaxbIT.java @@ -1,24 +1,8 @@ package io.quarkus.it.jaxb; -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.is; - -import org.junit.jupiter.api.Test; - import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest public class JaxbIT extends JaxbTest { - //We have to test native executable of Jaxb - @Test - public void bookWithParent() { - given().when() - .param("name", "Foundation") - .param("iban", "4242") - .get("/jaxb/bookwithparent") - .then() - .statusCode(200) - .body(is( - "4242Foundation")); - } + } From d61e59ac2b19746661353550a0f82bb1f2c8a774 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 22:47:59 +0000 Subject: [PATCH 198/212] Bump resteasy.version from 6.2.6.Final to 6.2.7.Final Bumps `resteasy.version` from 6.2.6.Final to 6.2.7.Final. Updates `org.jboss.resteasy:resteasy-bom` from 6.2.6.Final to 6.2.7.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.6.Final...6.2.7.Final) Updates `org.jboss.resteasy:resteasy-core` from 6.2.6.Final to 6.2.7.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.6.Final...6.2.7.Final) Updates `org.jboss.resteasy:resteasy-core-spi` from 6.2.6.Final to 6.2.7.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.6.Final...6.2.7.Final) Updates `org.jboss.resteasy:resteasy-json-binding-provider` from 6.2.6.Final to 6.2.7.Final Updates `org.jboss.resteasy:resteasy-json-p-provider` from 6.2.6.Final to 6.2.7.Final Updates `org.jboss.resteasy:resteasy-jaxb-provider` from 6.2.6.Final to 6.2.7.Final Updates `org.jboss.resteasy:resteasy-jackson2-provider` from 6.2.6.Final to 6.2.7.Final Updates `org.jboss.resteasy:resteasy-rxjava2` from 6.2.6.Final to 6.2.7.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.6.Final...6.2.7.Final) Updates `org.jboss.resteasy:resteasy-links` from 6.2.6.Final to 6.2.7.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.6.Final...6.2.7.Final) --- updated-dependencies: - dependency-name: org.jboss.resteasy:resteasy-bom dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-core-spi dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-json-binding-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-json-p-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-jaxb-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-jackson2-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-rxjava2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-links dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 7fe2de810c7a9bd31526cc523cc1db95855d36f6) --- 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 0377e3763b1f3..da11df6da37eb 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -26,7 +26,7 @@ 1.1.5 2.1.4.Final 3.0.2.Final - 6.2.6.Final + 6.2.7.Final 0.33.0 0.2.4 0.1.15 From 25ee2c7d36b9ece35129d5e16a266869e41357ec Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 16 Jan 2024 19:32:53 +0000 Subject: [PATCH 199/212] Ensure the refreshed CSRF cookie retains the original value (cherry picked from commit 2a872fe20b48b40f4458381ac43f802ec57961dd) --- .../CsrfRequestResponseReactiveFilter.java | 35 +++++++++++++++---- .../io/quarkus/it/csrf/CsrfReactiveTest.java | 14 ++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index 25df381ac7b59..f8f00b715f8a1 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -87,12 +87,26 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi if (requestMethodIsSafe(requestContext)) { // safe HTTP method, tolerate the absence of a token if (isCsrfTokenRequired(routing, config)) { - // Set the CSRF cookie with a randomly generated value - byte[] tokenBytes = new byte[config.tokenSize]; - secureRandom.nextBytes(tokenBytes); - routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes); - routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes)); - + if (cookieToken == null) { + generateNewCsrfToken(routing, config); + } else { + String csrfTokenHeaderParam = requestContext.getHeaderString(config.tokenHeaderName); + if (csrfTokenHeaderParam != null) { + LOG.debugf("CSRF token found in the token header"); + // Verify the header, make sure the header value, possibly signed, is returned as the next cookie value + verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenHeaderParam); + } else if (!config.tokenSignatureKey.isEmpty()) { + // If the signature is required, then we can not use the current cookie value + // as the HTML form token key because it represents a signed value of the previous key + // and it will lead to the double-signing issue if this value is reused as the key. + // It should be fine for simple HTML forms anyway + generateNewCsrfToken(routing, config); + } else { + // Make sure the same cookie value is returned + routing.put(CSRF_TOKEN_KEY, cookieToken); + routing.put(CSRF_TOKEN_BYTES_KEY, Base64.getUrlDecoder().decode(cookieToken)); + } + } routing.put(NEW_COOKIE_REQUIRED, true); } } else if (config.verifyToken) { @@ -139,6 +153,14 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi } } + private void generateNewCsrfToken(RoutingContext routing, CsrfReactiveConfig config) { + // Set the CSRF cookie with a randomly generated value + byte[] tokenBytes = new byte[config.tokenSize]; + secureRandom.nextBytes(tokenBytes); + routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes); + routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes)); + } + private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestContext, RoutingContext routing, CsrfReactiveConfig config, String cookieToken, String csrfToken) { if (cookieToken == null) { @@ -160,6 +182,7 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont return; } else { routing.put(CSRF_TOKEN_KEY, csrfToken); + routing.put(CSRF_TOKEN_BYTES_KEY, Base64.getUrlDecoder().decode(csrfToken)); routing.put(CSRF_TOKEN_VERIFIED, true); // reset the cookie routing.put(NEW_COOKIE_REQUIRED, true); diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index a8a937e1906a8..e317f75c71eb3 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.net.URL; @@ -56,6 +57,8 @@ public void testCsrfTokenInForm() throws Exception { assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + Cookie cookie1 = webClient.getCookieManager().getCookie("csrftoken"); + // This request which returns String is not CSRF protected textPage = webClient.getPage("http://localhost:8081/service/hello"); assertEquals("hello", textPage.getContent()); @@ -67,6 +70,11 @@ public void testCsrfTokenInForm() throws Exception { assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + Cookie cookie2 = webClient.getCookieManager().getCookie("csrftoken"); + + assertEquals(cookie1.getValue(), cookie2.getValue()); + assertTrue(cookie1.getExpires().before(cookie2.getExpires())); + webClient.getCookieManager().clearCookies(); } } @@ -366,6 +374,12 @@ private void assurePostFormPath(io.vertx.ext.web.client.WebClient vertxWebClient if (responseBody != null) { assertEquals(responseBody, result.result().bodyAsString(), path); } + if (expectedStatus != 400) { + String[] nextCookie = result.result().cookies().get(0).split(";"); + String[] cookieNameValue = nextCookie[0].trim().split("="); + assertEquals(csrfCookie.getName(), cookieNameValue[0]); + assertEquals(csrfCookie.getValue(), cookieNameValue[1]); + } } private void assurePostJsonPath(io.vertx.ext.web.client.WebClient vertxWebClient, String path, From 837f0e279ccb46354267510008cf2aee8a1d4720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 16 Jan 2024 16:50:03 +0100 Subject: [PATCH 200/212] Add dependency management for org.hibernate:hibernate-jpamodelgen (cherry picked from commit 499327dcd704435eda433754b9a0fb32c4f58936) --- bom/application/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index da11df6da37eb..bb93702b95237 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -5013,6 +5013,14 @@ hibernate-jpamodelgen ${hibernate-orm.version} + + + org.hibernate + hibernate-jpamodelgen + ${hibernate-orm.version} + org.hibernate.common hibernate-commons-annotations From d5d162e972c3fe60f998091fb8539fda6d012471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 17 Jan 2024 13:46:05 +0100 Subject: [PATCH 201/212] Recommend quarkus.jib.jvm-additional-arguments rather than quarkus.jib.jvm-arguments in docs I think that setting quarkus.jib.jvm-arguments would lead to the default (-Djava.util.logging.manager=org.jboss.logmanager.LogManager) being replaced, which would mess with logging, so that's probably not something we want. (cherry picked from commit dacae71027fe6bbbdaffb3c2f0fe4d3c2eb7ab2b) --- docs/src/main/asciidoc/container-image.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/container-image.adoc b/docs/src/main/asciidoc/container-image.adoc index eb5e7c3ebff19..2fd411e028c52 100644 --- a/docs/src/main/asciidoc/container-image.adoc +++ b/docs/src/main/asciidoc/container-image.adoc @@ -47,14 +47,14 @@ For example, the presence of `src/main/jib/foo/bar` would result in `/foo/bar` There are cases where the built container image may need to have Java debugging conditionally enabled at runtime. -When the base image has not been changed (and therefore `ubi8/openjdk-11-runtime`, `ubi8/openjdk-17-runtime`, or `ubi8/openjdk-21-runtime` is used), then the `quarkus.jib.jvm-arguments` configuration property can be used in order to +When the base image has not been changed (and therefore `ubi8/openjdk-11-runtime`, `ubi8/openjdk-17-runtime`, or `ubi8/openjdk-21-runtime` is used), then the `quarkus.jib.jvm-additional-arguments` configuration property can be used in order to make the JVM listen on the debug port at startup. The exact configuration is: [source,properties] ---- -quarkus.jib.jvm-arguments=-agentlib:jdwp=transport=dt_socket\\,server=y\\,suspend=n\\,address=*:5005 +quarkus.jib.jvm-additional-arguments=-agentlib:jdwp=transport=dt_socket\\,server=y\\,suspend=n\\,address=*:5005 ---- Other base images might provide launch scripts that enable debugging when an environment variable is set, in which case you would set than environment variable when launching the container. From 93c649006f63df6414d33a73714a3aa44cc6942b Mon Sep 17 00:00:00 2001 From: Anton-Vasilev <91203770+Anton-Vasilev@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:59:31 +0200 Subject: [PATCH 202/212] Update qute-reference.adoc Fix markup in examples and a couple of typos (cherry picked from commit ecc32b76db733859d424fbcbd3207be0c3850812) --- docs/src/main/asciidoc/qute-reference.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index d25cfa49e0de5..722cf10fea891 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -223,7 +223,7 @@ Likewise, a line that contains an _expression_ or a _non-whitespace character_ i <3> {/for} <4> - + ---- <1> This is a standalone line and will be removed. @@ -240,7 +240,7 @@ Likewise, a line that contains an _expression_ or a _non-whitespace character_ i
  • Foo 100
  • - + ---- @@ -258,7 +258,7 @@ In this case, all whitespace characters from a standalone line will be printed t - + ---- @@ -273,7 +273,7 @@ In the `object.property` (dot notation) syntax, the `property` must be a <> value. An expression can start with an optional namespace followed by a colon (`:`). -A valid namespace consist of alphanumeric characters and underscores. +A valid namespace consists of alphanumeric characters and underscores. Namespace expressions are resolved differently - see also <>. .Property Accessor Examples @@ -336,7 +336,7 @@ You can learn more about virtual methods in the <>. -If no result is found for the first part it's resolved against the parent context object (if available). +If no result is found for the first part, it's resolved against the parent context object (if available). For an expression that starts with a namespace the current context object is found using all the available ``NamespaceResolver``s. For an expression that does not start with a namespace the current context object is *derived from the position* of the tag. All other parts of an expression are resolved using all ``ValueResolver``s against the result of the previous resolution. @@ -1426,7 +1426,7 @@ template.data(foo).createUni().subscribe().with(System.out::println); `TemplateInstance.createMulti()` returns a new `Multi` object. Each item represents a part/chunk of the rendered template. Again, `createMulti()` does not trigger rendering. -Instead, every time a computation is triggered by a subscriber the template is rendered again. +Instead, every time a computation is triggered by a subscriber, the template is rendered again. .`TemplateInstance.createMulti()` Example [source,java] From d7cd8f9a1c12cf6343cd3916345c9bd43f7e4096 Mon Sep 17 00:00:00 2001 From: Wladimir Hofmann Date: Mon, 22 Jan 2024 09:37:17 +0100 Subject: [PATCH 203/212] fix entity-manager retrieval in spring-data-repos when using multiple persistence-units: - save a detached entity (merge) - getOne - paginated queries - deleteAll closes 38319 (cherry picked from commit e7bd3432c0b057cafdec35962822ccdc8b2f90ab) --- .../runtime/AdditionalJpaOperations.java | 12 +-- .../generate/StockMethodsAdder.java | 26 +++--- .../MultiplePersistenceUnitConfigTest.java | 82 +++++++++++++++++++ .../multiple_pu/second/SecondEntity.java | 7 +- .../second/SecondEntityRepository.java | 23 ++++++ .../data/runtime/RepositorySupport.java | 2 +- 6 files changed, 131 insertions(+), 21 deletions(-) diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java index f136bf2ec6cf4..e0c9a7c729f1a 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java @@ -32,7 +32,7 @@ public class AdditionalJpaOperations { public static PanacheQuery find(AbstractJpaOperations jpaOperations, Class entityClass, String query, String countQuery, Sort sort, Map params) { String findQuery = createFindQuery(entityClass, query, jpaOperations.paramCount(params)); - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); Query jpaQuery = em.createQuery(sort != null ? findQuery + toOrderBy(sort) : findQuery); JpaOperations.bindParameters(jpaQuery, params); return new CustomCountPanacheQuery(em, jpaQuery, countQuery, params); @@ -47,14 +47,14 @@ public static PanacheQuery find(AbstractJpaOperations jpaOperations, Class public static PanacheQuery find(AbstractJpaOperations jpaOperations, Class entityClass, String query, String countQuery, Sort sort, Object... params) { String findQuery = createFindQuery(entityClass, query, jpaOperations.paramCount(params)); - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); Query jpaQuery = em.createQuery(sort != null ? findQuery + toOrderBy(sort) : findQuery); JpaOperations.bindParameters(jpaQuery, params); return new CustomCountPanacheQuery(em, jpaQuery, countQuery, params); } public static long deleteAllWithCascade(AbstractJpaOperations jpaOperations, Class entityClass) { - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); //detecting the case where there are cascade-delete associations, and do the bulk delete query otherwise. if (deleteOnCascadeDetected(jpaOperations, entityClass)) { int count = 0; @@ -77,7 +77,7 @@ public static long deleteAllWithCascade(AbstractJpaOperations jpaOperations, * @return true if cascading delete is needed. False otherwise */ private static boolean deleteOnCascadeDetected(AbstractJpaOperations jpaOperations, Class entityClass) { - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); Metamodel metamodel = em.getMetamodel(); EntityType entity1 = metamodel.entity(entityClass); Set> declaredAttributes = ((EntityTypeImpl) entity1).getDeclaredAttributes(); @@ -96,7 +96,7 @@ private static boolean deleteOnCascadeDetected(AbstractJpaOperations jpaOpera public static long deleteWithCascade(AbstractJpaOperations jpaOperations, Class entityClass, String query, Object... params) { - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); if (deleteOnCascadeDetected(jpaOperations, entityClass)) { int count = 0; List objects = jpaOperations.list(jpaOperations.find(entityClass, query, params)); @@ -112,7 +112,7 @@ public static long deleteWithCascade(AbstractJpaOperations long deleteWithCascade(AbstractJpaOperations jpaOperations, Class entityClass, String query, Map params) { - EntityManager em = jpaOperations.getEntityManager(); + EntityManager em = jpaOperations.getEntityManager(entityClass); if (deleteOnCascadeDetected(jpaOperations, entityClass)) { int count = 0; List objects = jpaOperations.list(jpaOperations.find(entityClass, query, params)); diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/StockMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/StockMethodsAdder.java index c719ced1638ae..f8fa8fce729a0 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/StockMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/StockMethodsAdder.java @@ -87,9 +87,9 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr // and if so generate the implementation while also keeping the proper records generateSave(classCreator, generatedClassName, entityDotName, entityTypeStr, - allMethodsToBeImplementedToResult); + allMethodsToBeImplementedToResult, entityClassFieldDescriptor); generateSaveAndFlush(classCreator, generatedClassName, entityDotName, entityTypeStr, - allMethodsToBeImplementedToResult); + allMethodsToBeImplementedToResult, entityClassFieldDescriptor); generateSaveAll(classCreator, entityClassFieldDescriptor, generatedClassName, entityDotName, entityTypeStr, allMethodsToBeImplementedToResult); generateFlush(classCreator, generatedClassName, allMethodsToBeImplementedToResult); @@ -121,7 +121,8 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr private void generateSave(ClassCreator classCreator, String generatedClassName, DotName entityDotName, String entityTypeStr, - Map allMethodsToBeImplementedToResult) { + Map allMethodsToBeImplementedToResult, + FieldDescriptor entityClassFieldDescriptor) { MethodDescriptor saveDescriptor = MethodDescriptor.ofMethod(generatedClassName, "save", entityTypeStr, entityTypeStr); @@ -144,7 +145,7 @@ private void generateSave(ClassCreator classCreator, String generatedClassName, entity); BranchResult isNewBranch = save.ifTrue(isNew); generatePersistAndReturn(entity, isNewBranch.trueBranch()); - generateMergeAndReturn(entity, isNewBranch.falseBranch()); + generateMergeAndReturn(entity, isNewBranch.falseBranch(), entityClassFieldDescriptor); } else { AnnotationTarget idAnnotationTarget = getIdAnnotationTarget(entityDotName, index); ResultHandle idValue = generateObtainValue(save, entityDotName, entity, idAnnotationTarget); @@ -167,7 +168,7 @@ private void generateSave(ClassCreator classCreator, String generatedClassName, versionValueTarget.get()); BranchResult versionValueIsNullBranch = save.ifNull(versionValue); generatePersistAndReturn(entity, versionValueIsNullBranch.trueBranch()); - generateMergeAndReturn(entity, versionValueIsNullBranch.falseBranch()); + generateMergeAndReturn(entity, versionValueIsNullBranch.falseBranch(), entityClassFieldDescriptor); } BytecodeCreator idValueUnset; @@ -192,7 +193,7 @@ private void generateSave(ClassCreator classCreator, String generatedClassName, idValueUnset = idValueNullBranch.trueBranch(); } generatePersistAndReturn(entity, idValueUnset); - generateMergeAndReturn(entity, idValueSet); + generateMergeAndReturn(entity, idValueSet, entityClassFieldDescriptor); } } try (MethodCreator bridgeSave = classCreator.getMethodCreator(bridgeSaveDescriptor)) { @@ -236,10 +237,13 @@ private void generatePersistAndReturn(ResultHandle entity, BytecodeCreator bytec bytecodeCreator.returnValue(entity); } - private void generateMergeAndReturn(ResultHandle entity, BytecodeCreator bytecodeCreator) { + private void generateMergeAndReturn(ResultHandle entity, BytecodeCreator bytecodeCreator, + FieldDescriptor entityClassFieldDescriptor) { + ResultHandle entityClass = bytecodeCreator.readInstanceField(entityClassFieldDescriptor, bytecodeCreator.getThis()); ResultHandle entityManager = bytecodeCreator.invokeVirtualMethod( - ofMethod(AbstractJpaOperations.class, "getEntityManager", EntityManager.class), - bytecodeCreator.readStaticField(operationsField)); + ofMethod(AbstractJpaOperations.class, "getEntityManager", EntityManager.class, Class.class), + bytecodeCreator.readStaticField(operationsField), + entityClass); entity = bytecodeCreator.invokeInterfaceMethod( MethodDescriptor.ofMethod(EntityManager.class, "merge", Object.class, Object.class), entityManager, entity); @@ -280,7 +284,7 @@ private Type getTypeOfTarget(AnnotationTarget idAnnotationTarget) { private void generateSaveAndFlush(ClassCreator classCreator, String generatedClassName, DotName entityDotName, String entityTypeStr, - Map allMethodsToBeImplementedToResult) { + Map allMethodsToBeImplementedToResult, FieldDescriptor entityClassFieldDescriptor) { MethodDescriptor saveAndFlushDescriptor = MethodDescriptor.ofMethod(generatedClassName, "saveAndFlush", entityTypeStr, entityTypeStr); @@ -298,7 +302,7 @@ private void generateSaveAndFlush(ClassCreator classCreator, // we need to force the generation of findById since this method depends on it allMethodsToBeImplementedToResult.put(save, false); generateSave(classCreator, generatedClassName, entityDotName, entityTypeStr, - allMethodsToBeImplementedToResult); + allMethodsToBeImplementedToResult, entityClassFieldDescriptor); try (MethodCreator saveAndFlush = classCreator.getMethodCreator(saveAndFlushDescriptor)) { saveAndFlush.addAnnotation(Transactional.class); diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/MultiplePersistenceUnitConfigTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/MultiplePersistenceUnitConfigTest.java index 03bdfddb810eb..5c9a54bd7a93c 100644 --- a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/MultiplePersistenceUnitConfigTest.java +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/MultiplePersistenceUnitConfigTest.java @@ -1,9 +1,20 @@ package io.quarkus.spring.data.deployment.multiple_pu; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Supplier; + +import jakarta.inject.Inject; + import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import io.quarkus.narayana.jta.QuarkusTransaction; import io.quarkus.spring.data.deployment.multiple_pu.first.FirstEntity; import io.quarkus.spring.data.deployment.multiple_pu.first.FirstEntityRepository; import io.quarkus.spring.data.deployment.multiple_pu.second.SecondEntity; @@ -21,6 +32,17 @@ public class MultiplePersistenceUnitConfigTest { PanacheTestResource.class) .addAsResource("application-multiple-persistence-units.properties", "application.properties")); + @Inject + private FirstEntityRepository repository1; + @Inject + private SecondEntityRepository repository2; + + @BeforeEach + void beforeEach() { + repository1.deleteAll(); + repository2.deleteAll(); + } + @Test public void panacheOperations() { /** @@ -35,4 +57,64 @@ public void panacheOperations() { RestAssured.when().get("/persistence-unit/second/name-1").then().body(Matchers.is("1")); RestAssured.when().get("/persistence-unit/second/name-2").then().body(Matchers.is("2")); } + + @Test + public void entityLifecycle() { + var detached = repository2.save(new SecondEntity()); + assertThat(detached.id).isNotNull(); + assertThat(inTx(repository2::count)).isEqualTo(1); + + detached.name = "name"; + repository2.save(detached); + assertThat(inTx(repository2::count)).isEqualTo(1); + + inTx(() -> { + var lazyRef = repository2.getOne(detached.id); + assertThat(lazyRef.name).isEqualTo(detached.name); + return null; + }); + + repository2.deleteByName("otherThan" + detached.name); + assertThat(inTx(() -> repository2.findById(detached.id))).isPresent(); + + repository2.deleteByName(detached.name); + assertThat(inTx(() -> repository2.findById(detached.id))).isEmpty(); + } + + @Test + void pagedQueries() { + var newEntity = new SecondEntity(); + newEntity.name = "name"; + var detached = repository2.save(newEntity); + + Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "id"); + + var page = inTx(() -> repository2.findByName(detached.name, pageable)); + assertThat(page.getContent()).extracting(e -> e.id).containsExactly(detached.id); + + var pageIndexParam = inTx(() -> repository2.findByNameQueryIndexed(detached.name, pageable)); + assertThat(pageIndexParam.getContent()).extracting(e -> e.id).containsExactly(detached.id); + + var pageNamedParam = inTx(() -> repository2.findByNameQueryNamed(detached.name, pageable)); + assertThat(pageNamedParam.getContent()).extracting(e -> e.id).containsExactly(detached.id); + } + + @Test + void cascading() { + var newParent = new SecondEntity(); + newParent.name = "parent"; + var newChild = new SecondEntity(); + newChild.name = "child"; + newParent.child = newChild; + var detachedParent = repository2.save(newParent); + + assertThat(inTx(repository2::count)).isEqualTo(2); + + repository2.deleteByName(detachedParent.name); + assertThat(inTx(repository2::count)).isZero(); + } + + private T inTx(Supplier action) { + return QuarkusTransaction.requiringNew().call(action::get); + } } diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntity.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntity.java index c0753077f8a0b..9bc0ce353c449 100644 --- a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntity.java +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntity.java @@ -1,8 +1,6 @@ package io.quarkus.spring.data.deployment.multiple_pu.second; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import jakarta.persistence.*; @Entity public class SecondEntity { @@ -12,4 +10,7 @@ public class SecondEntity { public Long id; public String name; + + @OneToOne(cascade = CascadeType.ALL) + public SecondEntity child; } diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntityRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntityRepository.java index d35fbaab8775f..f0ddfd7d4bfab 100644 --- a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntityRepository.java +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/multiple_pu/second/SecondEntityRepository.java @@ -1,5 +1,11 @@ package io.quarkus.spring.data.deployment.multiple_pu.second; +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository @@ -8,4 +14,21 @@ public interface SecondEntityRepository extends org.springframework.data.reposit SecondEntity save(SecondEntity entity); long count(); + + Optional findById(Long id); + + SecondEntity getOne(Long id); + + void deleteAll(); + + void deleteByName(String name); + + Page findByName(String name, Pageable pageable); + + @Query(value = "SELECT se FROM SecondEntity se WHERE name=?1", countQuery = "SELECT COUNT(*) FROM SecondEntity se WHERE name=?1") + Page findByNameQueryIndexed(String name, Pageable pageable); + + @Query(value = "SELECT se FROM SecondEntity se WHERE name=:name", countQuery = "SELECT COUNT(*) FROM SecondEntity se WHERE name=:name") + Page findByNameQueryNamed(@Param("name") String name, Pageable pageable); + } diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/RepositorySupport.java b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/RepositorySupport.java index 53e91d906f023..2da94e173b573 100644 --- a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/RepositorySupport.java +++ b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/spring/data/runtime/RepositorySupport.java @@ -42,7 +42,7 @@ public static void deleteAll(AbstractJpaOperations> operations, } public static Object getOne(AbstractJpaOperations> operations, Class entityClass, Object id) { - return operations.getEntityManager().getReference(entityClass, id); + return operations.getEntityManager(entityClass).getReference(entityClass, id); } public static void clear(Class clazz) { From 6d76eafa5c7b28d0a9854695d0590f3db969c49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 23 Jan 2024 22:22:04 +0100 Subject: [PATCH 204/212] Move RESTEasy Classic RBAC security checks to JAX-RS filter --- .../deployment/ResteasyBuiltinsProcessor.java | 20 +- .../test/security/EagerSecurityCheckTest.java | 176 ++++++++++++++++++ .../resteasy/runtime/EagerSecurityFilter.java | 117 ++++++++++-- .../StandardSecurityCheckInterceptor.java | 85 +++++++++ 4 files changed, 376 insertions(+), 22 deletions(-) create mode 100644 extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/EagerSecurityCheckTest.java create mode 100644 extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java index df5f2f1a1ff3e..2001747fbb72b 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; import org.jboss.jandex.ClassInfo; @@ -34,6 +33,7 @@ import io.quarkus.resteasy.runtime.JaxRsSecurityConfig; import io.quarkus.resteasy.runtime.NotFoundExceptionMapper; import io.quarkus.resteasy.runtime.SecurityContextFilter; +import io.quarkus.resteasy.runtime.StandardSecurityCheckInterceptor; import io.quarkus.resteasy.runtime.UnauthorizedExceptionMapper; import io.quarkus.resteasy.runtime.vertx.JsonArrayReader; import io.quarkus.resteasy.runtime.vertx.JsonArrayWriter; @@ -41,7 +41,6 @@ import io.quarkus.resteasy.runtime.vertx.JsonObjectWriter; import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentBuildItem; import io.quarkus.security.spi.AdditionalSecuredMethodsBuildItem; -import io.quarkus.vertx.http.deployment.EagerSecurityInterceptorBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; import io.quarkus.vertx.http.deployment.devmode.RouteDescriptionBuildItem; @@ -91,8 +90,7 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index, */ @BuildStep void setUpSecurity(BuildProducer providers, - BuildProducer additionalBeanBuildItem, Capabilities capabilities, - Optional eagerSecurityInterceptors) { + BuildProducer additionalBeanBuildItem, Capabilities capabilities) { providers.produce(new ResteasyJaxrsProviderBuildItem(UnauthorizedExceptionMapper.class.getName())); providers.produce(new ResteasyJaxrsProviderBuildItem(ForbiddenExceptionMapper.class.getName())); providers.produce(new ResteasyJaxrsProviderBuildItem(AuthenticationFailedExceptionMapper.class.getName())); @@ -102,10 +100,16 @@ void setUpSecurity(BuildProducer providers, if (capabilities.isPresent(Capability.SECURITY)) { providers.produce(new ResteasyJaxrsProviderBuildItem(SecurityContextFilter.class.getName())); additionalBeanBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(SecurityContextFilter.class)); - if (eagerSecurityInterceptors.isPresent()) { - providers.produce(new ResteasyJaxrsProviderBuildItem(EagerSecurityFilter.class.getName())); - additionalBeanBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(EagerSecurityFilter.class)); - } + providers.produce(new ResteasyJaxrsProviderBuildItem(EagerSecurityFilter.class.getName())); + additionalBeanBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(EagerSecurityFilter.class)); + additionalBeanBuildItem.produce( + AdditionalBeanBuildItem.unremovableOf(StandardSecurityCheckInterceptor.RolesAllowedInterceptor.class)); + additionalBeanBuildItem.produce(AdditionalBeanBuildItem + .unremovableOf(StandardSecurityCheckInterceptor.PermissionsAllowedInterceptor.class)); + additionalBeanBuildItem.produce( + AdditionalBeanBuildItem.unremovableOf(StandardSecurityCheckInterceptor.PermitAllInterceptor.class)); + additionalBeanBuildItem.produce( + AdditionalBeanBuildItem.unremovableOf(StandardSecurityCheckInterceptor.AuthenticatedInterceptor.class)); } } diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/EagerSecurityCheckTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/EagerSecurityCheckTest.java new file mode 100644 index 0000000000000..d71665b4f1292 --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/EagerSecurityCheckTest.java @@ -0,0 +1,176 @@ +package io.quarkus.resteasy.test.security; + +import jakarta.annotation.security.DenyAll; +import jakarta.annotation.security.PermitAll; +import jakarta.annotation.security.RolesAllowed; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.Authenticated; +import io.quarkus.security.test.utils.TestIdentityController; +import io.quarkus.security.test.utils.TestIdentityProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.vertx.core.json.JsonObject; + +/** + * Tests that {@link io.quarkus.security.spi.runtime.SecurityCheck}s are executed by Jakarta REST filters. + */ +public class EagerSecurityCheckTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(TestIdentityProvider.class, TestIdentityController.class, JsonResource.class, + AbstractJsonResource.class, JsonSubResource.class)); + + @BeforeAll + public static void setupUsers() { + TestIdentityController.resetRoles() + .add("admin", "admin", "admin") + .add("user", "user", "user"); + } + + @Test + public void testAuthenticated() { + testPostJson("auth", "admin", true).then().statusCode(400); + testPostJson("auth", null, true).then().statusCode(401); + testPostJson("auth", "admin", false).then().statusCode(200); + testPostJson("auth", null, false).then().statusCode(401); + } + + @Test + public void testRolesAllowed() { + testPostJson("roles", "admin", true).then().statusCode(400); + testPostJson("roles", "user", true).then().statusCode(403); + testPostJson("roles", "admin", false).then().statusCode(200); + testPostJson("roles", "user", false).then().statusCode(403); + } + + @Test + public void testRolesAllowedOverriddenMethod() { + testPostJson("/roles-overridden", "admin", true).then().statusCode(400); + testPostJson("/roles-overridden", "user", true).then().statusCode(403); + testPostJson("/roles-overridden", "admin", false).then().statusCode(200); + testPostJson("/roles-overridden", "user", false).then().statusCode(403); + } + + @Test + public void testDenyAll() { + testPostJson("deny", "admin", true).then().statusCode(403); + testPostJson("deny", null, true).then().statusCode(401); + testPostJson("deny", "admin", false).then().statusCode(403); + testPostJson("deny", null, false).then().statusCode(401); + } + + @Test + public void testDenyAllClassLevel() { + testPostJson("/sub-resource/deny-class-level-annotation", "admin", true).then().statusCode(403); + testPostJson("/sub-resource/deny-class-level-annotation", null, true).then().statusCode(401); + testPostJson("/sub-resource/deny-class-level-annotation", "admin", false).then().statusCode(403); + testPostJson("/sub-resource/deny-class-level-annotation", null, false).then().statusCode(401); + } + + @Test + public void testPermitAll() { + testPostJson("permit", "admin", true).then().statusCode(400); + testPostJson("permit", null, true).then().statusCode(400); + testPostJson("permit", "admin", false).then().statusCode(200); + testPostJson("permit", null, false).then().statusCode(200); + } + + @Test + public void testSubResource() { + testPostJson("/sub-resource/roles", "admin", true).then().statusCode(400); + testPostJson("/sub-resource/roles", "user", true).then().statusCode(403); + testPostJson("/sub-resource/roles", "admin", false).then().statusCode(200); + testPostJson("/sub-resource/roles", "user", false).then().statusCode(403); + } + + private static Response testPostJson(String path, String username, boolean invalid) { + var req = RestAssured.given(); + if (username != null) { + req = req.auth().preemptive().basic(username, username); + } + return req + .contentType(ContentType.JSON) + .body((invalid ? "}" : "") + "{\"simple\": \"obj\"}").post(path); + } + + @Path("/") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public static class JsonResource extends AbstractJsonResource { + + @Authenticated + @Path("/auth") + @POST + public JsonObject auth(JsonObject array) { + return array.put("test", "testval"); + } + + @RolesAllowed("admin") + @Path("/roles") + @POST + public JsonObject roles(JsonObject array) { + return array.put("test", "testval"); + } + + @PermitAll + @Path("/permit") + @POST + public JsonObject permit(JsonObject array) { + return array.put("test", "testval"); + } + + @PermitAll + @Path("/sub-resource") + public JsonSubResource subResource() { + return new JsonSubResource(); + } + + @RolesAllowed("admin") + @Override + public JsonObject rolesOverridden(JsonObject array) { + return array.put("test", "testval"); + } + } + + @DenyAll + public static class JsonSubResource { + @RolesAllowed("admin") + @Path("/roles") + @POST + public JsonObject roles(JsonObject array) { + return array.put("test", "testval"); + } + + @Path("/deny-class-level-annotation") + @POST + public JsonObject denyClassLevelAnnotation(JsonObject array) { + return array.put("test", "testval"); + } + } + + public static abstract class AbstractJsonResource { + @DenyAll + @Path("/deny") + @POST + public JsonObject deny(JsonObject array) { + return array.put("test", "testval"); + } + + @Path("/roles-overridden") + @POST + public abstract JsonObject rolesOverridden(JsonObject array); + } +} diff --git a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java index 81a138aaa974e..e05df7c8dc751 100644 --- a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java +++ b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java @@ -1,11 +1,15 @@ package io.quarkus.resteasy.runtime; +import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_FAILURE; +import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_SUCCESS; + import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import jakarta.annotation.Priority; +import jakarta.enterprise.event.Event; import jakarta.inject.Inject; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; @@ -14,7 +18,19 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.arc.Arc; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.identity.CurrentIdentityAssociation; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.AuthorizationController; +import io.quarkus.security.spi.runtime.AuthorizationFailureEvent; +import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent; import io.quarkus.security.spi.runtime.MethodDescription; +import io.quarkus.security.spi.runtime.SecurityCheck; +import io.quarkus.security.spi.runtime.SecurityCheckStorage; +import io.quarkus.security.spi.runtime.SecurityEventHelper; import io.quarkus.vertx.http.runtime.security.EagerSecurityInterceptorStorage; import io.vertx.ext.web.RoutingContext; @@ -29,33 +45,106 @@ public void accept(RoutingContext routingContext) { } }; private final Map> cache = new HashMap<>(); + private final EagerSecurityInterceptorStorage interceptorStorage; + private final SecurityEventHelper eventHelper; @Context ResourceInfo resourceInfo; @Inject - EagerSecurityInterceptorStorage interceptorStorage; + RoutingContext routingContext; @Inject - RoutingContext routingContext; + SecurityCheckStorage securityCheckStorage; + + @Inject + CurrentIdentityAssociation identityAssociation; + + @Inject + AuthorizationController authorizationController; + + public EagerSecurityFilter() { + var interceptorStorageHandle = Arc.container().instance(EagerSecurityInterceptorStorage.class); + this.interceptorStorage = interceptorStorageHandle.isAvailable() ? interceptorStorageHandle.get() : null; + Event event = Arc.container().beanManager().getEvent(); + this.eventHelper = new SecurityEventHelper<>(event.select(AuthorizationSuccessEvent.class), + event.select(AuthorizationFailureEvent.class), AUTHORIZATION_SUCCESS, + AUTHORIZATION_FAILURE, Arc.container().beanManager(), + ConfigProvider.getConfig().getOptionalValue("quarkus.security.events.enabled", Boolean.class).orElse(false)); + } @Override public void filter(ContainerRequestContext requestContext) throws IOException { - var description = MethodDescription.ofMethod(resourceInfo.getResourceMethod()); - var interceptor = cache.get(description); + if (authorizationController.isAuthorizationEnabled()) { + var description = MethodDescription.ofMethod(resourceInfo.getResourceMethod()); + if (interceptorStorage != null) { + applyEagerSecurityInterceptors(description); + } + applySecurityChecks(description); + } + } + + private void applySecurityChecks(MethodDescription description) { + SecurityCheck check = securityCheckStorage.getSecurityCheck(description); + if (check != null) { + if (check.isPermitAll()) { + fireEventOnAuthZSuccess(check, null); + } else { + if (check.requiresMethodArguments()) { + if (identityAssociation.getIdentity().isAnonymous()) { + var exception = new UnauthorizedException(); + if (eventHelper.fireEventOnFailure()) { + fireEventOnAuthZFailure(exception, check); + } + throw exception; + } + // security check will be performed by CDI interceptor + return; + } + if (eventHelper.fireEventOnFailure()) { + try { + check.apply(identityAssociation.getIdentity(), description, null); + } catch (Exception e) { + fireEventOnAuthZFailure(e, check); + throw e; + } + } else { + check.apply(identityAssociation.getIdentity(), description, null); + } + fireEventOnAuthZSuccess(check, identityAssociation.getIdentity()); + } + // prevent repeated security checks + routingContext.put(EagerSecurityFilter.class.getName(), resourceInfo.getResourceMethod()); + } + } + + private void fireEventOnAuthZFailure(Exception exception, SecurityCheck check) { + eventHelper.fireFailureEvent(new AuthorizationFailureEvent( + identityAssociation.getIdentity(), exception, check.getClass().getName(), + Map.of(RoutingContext.class.getName(), routingContext))); + } - if (interceptor == NULL_SENTINEL) { - return; - } else if (interceptor != null) { - interceptor.accept(routingContext); + private void fireEventOnAuthZSuccess(SecurityCheck check, SecurityIdentity securityIdentity) { + if (eventHelper.fireEventOnSuccess()) { + eventHelper.fireSuccessEvent(new AuthorizationSuccessEvent(securityIdentity, + check.getClass().getName(), Map.of(RoutingContext.class.getName(), routingContext))); } + } - interceptor = interceptorStorage.getInterceptor(description); - if (interceptor == null) { - cache.put(description, NULL_SENTINEL); - } else { - cache.put(description, interceptor); - interceptor.accept(routingContext); + private void applyEagerSecurityInterceptors(MethodDescription description) { + var interceptor = cache.get(description); + if (interceptor != NULL_SENTINEL) { + if (interceptor != null) { + interceptor.accept(routingContext); + } else { + interceptor = interceptorStorage.getInterceptor(description); + if (interceptor == null) { + cache.put(description, NULL_SENTINEL); + } else { + cache.put(description, interceptor); + interceptor.accept(routingContext); + } + } } } } diff --git a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java new file mode 100644 index 0000000000000..036ab8fcf3b74 --- /dev/null +++ b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java @@ -0,0 +1,85 @@ +package io.quarkus.resteasy.runtime; + +import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.EXECUTED; +import static io.quarkus.security.spi.runtime.SecurityHandlerConstants.SECURITY_HANDLER; + +import java.lang.reflect.Method; + +import jakarta.annotation.Priority; +import jakarta.annotation.security.DenyAll; +import jakarta.annotation.security.PermitAll; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; + +import io.quarkus.security.Authenticated; +import io.quarkus.security.PermissionsAllowed; +import io.quarkus.security.spi.runtime.AuthorizationController; +import io.vertx.ext.web.RoutingContext; + +/** + * Security checks for RBAC annotations on endpoints are done by the {@link EagerSecurityFilter}, this interceptor + * propagates the information to the SecurityHandler to prevent repeated checks. The {@link DenyAll} security check + * is performed just once. + */ +public abstract class StandardSecurityCheckInterceptor { + + @Inject + AuthorizationController controller; + + @Inject + RoutingContext routingContext; + + @AroundInvoke + public Object intercept(InvocationContext ic) throws Exception { + if (controller.isAuthorizationEnabled()) { + Method method = routingContext.get(EagerSecurityFilter.class.getName()); + if (method != null && method.equals(ic.getMethod())) { + ic.getContextData().put(SECURITY_HANDLER, EXECUTED); + } + } + return ic.proceed(); + } + + /** + * Prevent the SecurityHandler from performing {@link RolesAllowed} security checks + */ + @Interceptor + @RolesAllowed("") + @Priority(Interceptor.Priority.PLATFORM_BEFORE) + public static final class RolesAllowedInterceptor extends StandardSecurityCheckInterceptor { + + } + + /** + * Prevent the SecurityHandler from performing {@link PermissionsAllowed} security checks + */ + @Interceptor + @PermissionsAllowed("") + @Priority(Interceptor.Priority.PLATFORM_BEFORE) + public static final class PermissionsAllowedInterceptor extends StandardSecurityCheckInterceptor { + + } + + /** + * Prevent the SecurityHandler from performing {@link PermitAll} security checks + */ + @Interceptor + @PermitAll + @Priority(Interceptor.Priority.PLATFORM_BEFORE) + public static final class PermitAllInterceptor extends StandardSecurityCheckInterceptor { + + } + + /** + * Prevent the SecurityHandler from performing {@link Authenticated} security checks + */ + @Interceptor + @Authenticated + @Priority(Interceptor.Priority.PLATFORM_BEFORE) + public static final class AuthenticatedInterceptor extends StandardSecurityCheckInterceptor { + + } +} From bf2ef6c504b989f74ceb5947d823b6ab208f8b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 23 Jan 2024 16:40:33 +0100 Subject: [PATCH 205/212] Fix JAX-RS default security checks for inherited / transformed endpoints --- .../RestPathAnnotationProcessor.java | 2 +- .../deployment/ResteasyBuiltinsProcessor.java | 66 ++++++++++++++- .../DefaultRolesAllowedJaxRsTest.java | 16 +++- .../DefaultRolesAllowedStarJaxRsTest.java | 4 +- .../test/security/DenyAllJaxRsTest.java | 16 +++- .../security/UnsecuredParentResource.java | 14 ++++ .../test/security/UnsecuredResource.java | 7 +- .../security/UnsecuredResourceInterface.java | 14 ++++ .../resteasy/runtime/EagerSecurityFilter.java | 48 +---------- .../ResteasyReactiveCommonProcessor.java | 53 ++---------- .../DefaultRolesAllowedJaxRsTest.java | 16 +++- .../DefaultRolesAllowedStarJaxRsTest.java | 4 +- .../test/security/DenyAllJaxRsTest.java | 84 ++++++++++++++++++- .../security/UnsecuredParentResource.java | 14 ++++ .../test/security/UnsecuredResource.java | 2 +- .../security/UnsecuredResourceInterface.java | 14 ++++ .../security/EagerSecurityHandler.java | 16 +++- .../deployment/SecurityProcessor.java | 11 +++ .../spi/runtime/SecurityCheckStorage.java | 5 ++ .../runtime/SecurityCheckRecorder.java | 4 + .../SecurityCheckStorageBuilder.java | 13 +++ .../spi/DefaultSecurityCheckBuildItem.java | 28 +++++++ .../common/processor/EndpointIndexer.java | 19 ----- 23 files changed, 337 insertions(+), 133 deletions(-) create mode 100644 extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredParentResource.java create mode 100644 extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResourceInterface.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java create mode 100644 extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java index 5959d9e09c199..94b3a0993718a 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java @@ -182,7 +182,7 @@ static Optional searchPathAnnotationOnInterfaces(CombinedInd * @param resultAcc accumulator for tail-recursion * @return Collection of all interfaces und their parents. Never null. */ - private static Collection getAllClassInterfaces( + static Collection getAllClassInterfaces( CombinedIndexBuildItem index, Collection classInfos, List resultAcc) { diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java index 2001747fbb72b..e525ee2caddfd 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java @@ -1,17 +1,21 @@ package io.quarkus.resteasy.deployment; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import static io.quarkus.resteasy.deployment.RestPathAnnotationProcessor.getAllClassInterfaces; import static io.quarkus.resteasy.deployment.RestPathAnnotationProcessor.isRestEndpointMethod; import static io.quarkus.security.spi.SecurityTransformerUtils.hasSecurityAnnotation; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; +import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.deployment.Capabilities; @@ -50,6 +54,7 @@ public class ResteasyBuiltinsProcessor { protected static final String META_INF_RESOURCES = "META-INF/resources"; + private static final Logger LOG = Logger.getLogger(ResteasyBuiltinsProcessor.class); @BuildStep void setUpDenyAllJaxRs(CombinedIndexBuildItem index, @@ -65,10 +70,42 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index, ClassInfo classInfo = index.getIndex().getClassByName(DotName.createSimple(className)); if (classInfo == null) throw new IllegalStateException("Unable to find class info for " + className); - if (!hasSecurityAnnotation(classInfo)) { - for (MethodInfo methodInfo : classInfo.methods()) { - if (isRestEndpointMethod(index, methodInfo) && !hasSecurityAnnotation(methodInfo)) { - methods.add(methodInfo); + // add unannotated class endpoints as well as parent class unannotated endpoints + addAllUnannotatedEndpoints(index, classInfo, methods); + + // interface endpoints implemented on resources are already in, now we need to resolve default interface + // methods as there, CDI interceptors won't work, therefore neither will our additional secured methods + Collection interfaces = getAllClassInterfaces(index, List.of(classInfo), new ArrayList<>()); + if (!interfaces.isEmpty()) { + final List interfaceEndpoints = new ArrayList<>(); + for (ClassInfo anInterface : interfaces) { + addUnannotatedEndpoints(index, anInterface, interfaceEndpoints); + } + // look for implementors as implementors on resource classes are secured by CDI interceptors + if (!interfaceEndpoints.isEmpty()) { + interfaceBlock: for (MethodInfo interfaceEndpoint : interfaceEndpoints) { + if (interfaceEndpoint.isDefault()) { + for (MethodInfo endpoint : methods) { + boolean nameParamsMatch = endpoint.name().equals(interfaceEndpoint.name()) + && (interfaceEndpoint.parameterTypes().equals(endpoint.parameterTypes())); + if (nameParamsMatch) { + // whether matched method is declared on class that implements interface endpoint + Predicate isEndpointInterface = interfaceEndpoint.declaringClass() + .name()::equals; + if (endpoint.declaringClass().interfaceNames().stream().anyMatch(isEndpointInterface)) { + continue interfaceBlock; + } + } + } + String configProperty = config.denyJaxRs ? "quarkus.security.jaxrs.deny-unannotated-endpoints" + : "quarkus.security.jaxrs.default-roles-allowed"; + // this is logging only as I'm a bit worried about false positives and breaking things + // for what is very much edge case + LOG.warn("Default interface method '" + interfaceEndpoint + + "' cannot be secured with the '" + configProperty + + "' configuration property. Please implement this method for CDI " + + "interceptor binding to work"); + } } } } @@ -85,6 +122,27 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index, } } + private static void addAllUnannotatedEndpoints(CombinedIndexBuildItem index, ClassInfo classInfo, + List methods) { + if (classInfo == null) { + return; + } + addUnannotatedEndpoints(index, classInfo, methods); + if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotName.OBJECT_NAME)) { + addAllUnannotatedEndpoints(index, index.getIndex().getClassByName(classInfo.superClassType().name()), methods); + } + } + + private static void addUnannotatedEndpoints(CombinedIndexBuildItem index, ClassInfo classInfo, List methods) { + if (!hasSecurityAnnotation(classInfo)) { + for (MethodInfo methodInfo : classInfo.methods()) { + if (isRestEndpointMethod(index, methodInfo) && !hasSecurityAnnotation(methodInfo)) { + methods.add(methodInfo); + } + } + } + } + /** * Install the JAX-RS security provider. */ diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedJaxRsTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedJaxRsTest.java index fb13a3d50cbf6..b9e18eed5475c 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedJaxRsTest.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedJaxRsTest.java @@ -22,8 +22,8 @@ public class DefaultRolesAllowedJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, - TestIdentityController.class, + TestIdentityProvider.class, UnsecuredResourceInterface.class, + TestIdentityController.class, UnsecuredParentResource.class, UnsecuredSubResource.class, HelloResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.default-roles-allowed = admin\n"), "application.properties")); @@ -41,6 +41,18 @@ public void shouldDenyUnannotated() { assertStatus(path, 200, 403, 401); } + @Test + public void shouldDenyUnannotatedOnParentClass() { + String path = "/unsecured/defaultSecurityParent"; + assertStatus(path, 200, 403, 401); + } + + @Test + public void shouldDenyUnannotatedOnInterface() { + String path = "/unsecured/defaultSecurityInterface"; + assertStatus(path, 200, 403, 401); + } + @Test public void shouldDenyDenyAllMethod() { String path = "/unsecured/denyAll"; diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedStarJaxRsTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedStarJaxRsTest.java index 4e0fd8c7dd808..ddcc31f5ae87e 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedStarJaxRsTest.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DefaultRolesAllowedStarJaxRsTest.java @@ -17,8 +17,8 @@ public class DefaultRolesAllowedStarJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, - TestIdentityController.class, + TestIdentityProvider.class, UnsecuredParentResource.class, + TestIdentityController.class, UnsecuredResourceInterface.class, UnsecuredSubResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.default-roles-allowed = **\n"), "application.properties")); diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DenyAllJaxRsTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DenyAllJaxRsTest.java index 90cd2a9f77390..8ed4d8cbf445c 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DenyAllJaxRsTest.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/DenyAllJaxRsTest.java @@ -26,8 +26,8 @@ public class DenyAllJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, - TestIdentityController.class, + TestIdentityProvider.class, UnsecuredParentResource.class, + TestIdentityController.class, UnsecuredResourceInterface.class, UnsecuredSubResource.class, HelloResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.deny-unannotated-endpoints = true\n"), "application.properties")); @@ -58,6 +58,18 @@ public void shouldDenyUnannotated() { assertStatus(path, 403, 401); } + @Test + public void shouldDenyUnannotatedOnParentClass() { + String path = "/unsecured/defaultSecurityParent"; + assertStatus(path, 403, 401); + } + + @Test + public void shouldDenyUnannotatedOnInterface() { + String path = "/unsecured/defaultSecurityInterface"; + assertStatus(path, 403, 401); + } + @Test public void shouldDenyDenyAllMethod() { String path = "/unsecured/denyAll"; diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredParentResource.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredParentResource.java new file mode 100644 index 0000000000000..abf5b385e9a0d --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredParentResource.java @@ -0,0 +1,14 @@ +package io.quarkus.resteasy.test.security; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +public class UnsecuredParentResource { + + @Path("/defaultSecurityParent") + @GET + public String defaultSecurityParent() { + return "defaultSecurityParent"; + } + +} diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResource.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResource.java index 2d4eedc5fcfb5..e94a50c97a645 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResource.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResource.java @@ -10,13 +10,18 @@ * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com */ @Path("/unsecured") -public class UnsecuredResource { +public class UnsecuredResource extends UnsecuredParentResource implements UnsecuredResourceInterface { @Path("/defaultSecurity") @GET public String defaultSecurity() { return "defaultSecurity"; } + @Override + public String defaultSecurityInterface() { + return UnsecuredResourceInterface.super.defaultSecurityInterface(); + } + @Path("/permitAllPathParam/{index}") @GET @PermitAll diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResourceInterface.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResourceInterface.java new file mode 100644 index 0000000000000..d2498d46a8c63 --- /dev/null +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/security/UnsecuredResourceInterface.java @@ -0,0 +1,14 @@ +package io.quarkus.resteasy.test.security; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +public interface UnsecuredResourceInterface { + + @Path("/defaultSecurityInterface") + @GET + default String defaultSecurityInterface() { + return "defaultSecurityInterface"; + } + +} diff --git a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java index e05df7c8dc751..f218cc4eb84ba 100644 --- a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java +++ b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/EagerSecurityFilter.java @@ -1,15 +1,11 @@ package io.quarkus.resteasy.runtime; -import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_FAILURE; -import static io.quarkus.security.spi.runtime.SecurityEventHelper.AUTHORIZATION_SUCCESS; - import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import jakarta.annotation.Priority; -import jakarta.enterprise.event.Event; import jakarta.inject.Inject; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.container.ContainerRequestContext; @@ -18,19 +14,13 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; -import org.eclipse.microprofile.config.ConfigProvider; - import io.quarkus.arc.Arc; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.CurrentIdentityAssociation; -import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.spi.runtime.AuthorizationController; -import io.quarkus.security.spi.runtime.AuthorizationFailureEvent; -import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent; import io.quarkus.security.spi.runtime.MethodDescription; import io.quarkus.security.spi.runtime.SecurityCheck; import io.quarkus.security.spi.runtime.SecurityCheckStorage; -import io.quarkus.security.spi.runtime.SecurityEventHelper; import io.quarkus.vertx.http.runtime.security.EagerSecurityInterceptorStorage; import io.vertx.ext.web.RoutingContext; @@ -46,7 +36,6 @@ public void accept(RoutingContext routingContext) { }; private final Map> cache = new HashMap<>(); private final EagerSecurityInterceptorStorage interceptorStorage; - private final SecurityEventHelper eventHelper; @Context ResourceInfo resourceInfo; @@ -66,11 +55,6 @@ public void accept(RoutingContext routingContext) { public EagerSecurityFilter() { var interceptorStorageHandle = Arc.container().instance(EagerSecurityInterceptorStorage.class); this.interceptorStorage = interceptorStorageHandle.isAvailable() ? interceptorStorageHandle.get() : null; - Event event = Arc.container().beanManager().getEvent(); - this.eventHelper = new SecurityEventHelper<>(event.select(AuthorizationSuccessEvent.class), - event.select(AuthorizationFailureEvent.class), AUTHORIZATION_SUCCESS, - AUTHORIZATION_FAILURE, Arc.container().beanManager(), - ConfigProvider.getConfig().getOptionalValue("quarkus.security.events.enabled", Boolean.class).orElse(false)); } @Override @@ -87,50 +71,22 @@ public void filter(ContainerRequestContext requestContext) throws IOException { private void applySecurityChecks(MethodDescription description) { SecurityCheck check = securityCheckStorage.getSecurityCheck(description); if (check != null) { - if (check.isPermitAll()) { - fireEventOnAuthZSuccess(check, null); - } else { + if (!check.isPermitAll()) { if (check.requiresMethodArguments()) { if (identityAssociation.getIdentity().isAnonymous()) { var exception = new UnauthorizedException(); - if (eventHelper.fireEventOnFailure()) { - fireEventOnAuthZFailure(exception, check); - } throw exception; } // security check will be performed by CDI interceptor return; } - if (eventHelper.fireEventOnFailure()) { - try { - check.apply(identityAssociation.getIdentity(), description, null); - } catch (Exception e) { - fireEventOnAuthZFailure(e, check); - throw e; - } - } else { - check.apply(identityAssociation.getIdentity(), description, null); - } - fireEventOnAuthZSuccess(check, identityAssociation.getIdentity()); + check.apply(identityAssociation.getIdentity(), description, null); } // prevent repeated security checks routingContext.put(EagerSecurityFilter.class.getName(), resourceInfo.getResourceMethod()); } } - private void fireEventOnAuthZFailure(Exception exception, SecurityCheck check) { - eventHelper.fireFailureEvent(new AuthorizationFailureEvent( - identityAssociation.getIdentity(), exception, check.getClass().getName(), - Map.of(RoutingContext.class.getName(), routingContext))); - } - - private void fireEventOnAuthZSuccess(SecurityCheck check, SecurityIdentity securityIdentity) { - if (eventHelper.fireEventOnSuccess()) { - eventHelper.fireSuccessEvent(new AuthorizationSuccessEvent(securityIdentity, - check.getClass().getName(), Map.of(RoutingContext.class.getName(), routingContext))); - } - } - private void applyEagerSecurityInterceptors(MethodDescription description) { var interceptor = cache.get(description); if (interceptor != NULL_SENTINEL) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 1cd637de9d32f..987045e2b2656 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -1,13 +1,9 @@ package io.quarkus.resteasy.reactive.common.deployment; -import static io.quarkus.security.spi.SecurityTransformerUtils.hasSecurityAnnotation; import static org.jboss.resteasy.reactive.common.model.ResourceInterceptor.FILTER_SOURCE_METHOD_METADATA_KEY; -import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.collectClassEndpoints; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -75,7 +71,7 @@ import io.quarkus.resteasy.reactive.spi.MessageBodyWriterOverrideBuildItem; import io.quarkus.resteasy.reactive.spi.ReaderInterceptorBuildItem; import io.quarkus.resteasy.reactive.spi.WriterInterceptorBuildItem; -import io.quarkus.security.spi.AdditionalSecuredMethodsBuildItem; +import io.quarkus.security.spi.DefaultSecurityCheckBuildItem; public class ResteasyReactiveCommonProcessor { @@ -129,46 +125,13 @@ void searchForProviders(Capabilities capabilities, } @BuildStep - void setUpDenyAllJaxRs( - CombinedIndexBuildItem index, - JaxRsSecurityConfig securityConfig, - Optional resteasyDeployment, - BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, - ApplicationResultBuildItem applicationResultBuildItem, - BuildProducer additionalSecuredClasses) { - - if (resteasyDeployment.isPresent() - && (securityConfig.denyJaxRs() || securityConfig.defaultRolesAllowed().isPresent())) { - final List methods = new ArrayList<>(); - Map httpAnnotationToMethod = resteasyDeployment.get().getResult().getHttpAnnotationToMethod(); - Set resourceClasses = resteasyDeployment.get().getResult().getScannedResourcePaths().keySet(); - - for (DotName className : resourceClasses) { - ClassInfo classInfo = index.getIndex().getClassByName(className); - if (classInfo == null) - throw new IllegalStateException("Unable to find class info for " + className); - if (!hasSecurityAnnotation(classInfo)) { - // collect class endpoints - Collection classEndpoints = collectClassEndpoints(classInfo, httpAnnotationToMethod, - beanArchiveIndexBuildItem.getIndex(), applicationResultBuildItem.getResult()); - - // add endpoints - for (MethodInfo classEndpoint : classEndpoints) { - if (!hasSecurityAnnotation(classEndpoint)) { - methods.add(classEndpoint); - } - } - } - } - - if (!methods.isEmpty()) { - if (securityConfig.denyJaxRs()) { - additionalSecuredClasses.produce(new AdditionalSecuredMethodsBuildItem(methods)); - } else { - additionalSecuredClasses - .produce(new AdditionalSecuredMethodsBuildItem(methods, securityConfig.defaultRolesAllowed())); - } - } + void setUpDenyAllJaxRs(JaxRsSecurityConfig securityConfig, + BuildProducer defaultSecurityCheckProducer) { + if (securityConfig.denyJaxRs()) { + defaultSecurityCheckProducer.produce(DefaultSecurityCheckBuildItem.denyAll()); + } else if (securityConfig.defaultRolesAllowed().isPresent()) { + defaultSecurityCheckProducer + .produce(DefaultSecurityCheckBuildItem.rolesAllowed(securityConfig.defaultRolesAllowed().get())); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedJaxRsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedJaxRsTest.java index 4590ee9a9d02f..ca34474ad9052 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedJaxRsTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedJaxRsTest.java @@ -18,9 +18,9 @@ public class DefaultRolesAllowedJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, + TestIdentityProvider.class, UnsecuredResourceInterface.class, TestIdentityController.class, - UnsecuredSubResource.class, HelloResource.class) + UnsecuredSubResource.class, HelloResource.class, UnsecuredParentResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.default-roles-allowed=admin\n"), "application.properties")); @@ -37,6 +37,18 @@ public void shouldDenyUnannotated() { assertStatus(path, 200, 403, 401); } + @Test + public void shouldDenyUnannotatedParent() { + String path = "/unsecured/defaultSecurityParent"; + assertStatus(path, 200, 403, 401); + } + + @Test + public void shouldDenyUnannotatedInterface() { + String path = "/unsecured/defaultSecurityInterface"; + assertStatus(path, 200, 403, 401); + } + @Test public void shouldDenyDenyAllMethod() { String path = "/unsecured/denyAll"; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedStarJaxRsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedStarJaxRsTest.java index dcad10e88e0ce..6f77ef2fff88e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedStarJaxRsTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DefaultRolesAllowedStarJaxRsTest.java @@ -17,8 +17,8 @@ public class DefaultRolesAllowedStarJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, - TestIdentityController.class, + TestIdentityProvider.class, UnsecuredResourceInterface.class, + TestIdentityController.class, UnsecuredParentResource.class, UnsecuredSubResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.default-roles-allowed = **\n"), "application.properties")); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java index 2e10fb07015e4..76f23ddcc8056 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java @@ -3,12 +3,25 @@ import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; +import java.lang.reflect.Modifier; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + import org.hamcrest.Matchers; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.resteasy.reactive.server.spi.AnnotationsTransformerBuildItem; import io.quarkus.security.test.utils.TestIdentityController; import io.quarkus.security.test.utils.TestIdentityProvider; import io.quarkus.test.QuarkusUnitTest; @@ -21,11 +34,43 @@ public class DenyAllJaxRsTest { static QuarkusUnitTest runner = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(PermitAllResource.class, UnsecuredResource.class, - TestIdentityProvider.class, - TestIdentityController.class, - UnsecuredSubResource.class, HelloResource.class) + TestIdentityProvider.class, UnsecuredResourceInterface.class, + TestIdentityController.class, SpecialResource.class, + UnsecuredSubResource.class, HelloResource.class, UnsecuredParentResource.class) .addAsResource(new StringAsset("quarkus.security.jaxrs.deny-unannotated-endpoints = true\n"), - "application.properties")); + "application.properties")) + .addBuildChainCustomizer(builder -> { + builder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + // Here we add an AnnotationsTransformer in order to make sure that the security layer + // uses the proper set of transformers + context.produce( + new AnnotationsTransformerBuildItem( + AnnotationsTransformer.builder().appliesTo(AnnotationTarget.Kind.METHOD) + .transform(transformerContext -> { + // This transformer auto-adds @GET and @Path if missing, thus emulating Renarde + MethodInfo methodInfo = transformerContext.getTarget().asMethod(); + ClassInfo declaringClass = methodInfo.declaringClass(); + if (declaringClass.name().toString().equals(SpecialResource.class.getName()) + && !methodInfo.isConstructor() + && !Modifier.isStatic(methodInfo.flags())) { + if (methodInfo.declaredAnnotation(GET.class.getName()) == null) { + // auto-add it + transformerContext.transform().add(GET.class).done(); + } + if (methodInfo.declaredAnnotation(Path.class.getName()) == null) { + // auto-add it + transformerContext.transform().add(Path.class, + AnnotationValue.createStringValue("value", + methodInfo.name())) + .done(); + } + } + }))); + } + }).produces(AnnotationsTransformerBuildItem.class).build(); + }); @BeforeAll public static void setupUsers() { @@ -40,6 +85,18 @@ public void shouldDenyUnannotated() { assertStatus(path, 403, 401); } + @Test + public void shouldDenyUnannotatedOnParentClass() { + String path = "/unsecured/defaultSecurityParent"; + assertStatus(path, 403, 401); + } + + @Test + public void shouldDenyUnannotatedOnInterface() { + String path = "/unsecured/defaultSecurityInterface"; + assertStatus(path, 403, 401); + } + @Test public void shouldDenyUnannotatedNonBlocking() { String path = "/unsecured/defaultSecurityNonBlocking"; @@ -90,6 +147,14 @@ public void testServerExceptionMapper() { .body(Matchers.equalTo("unauthorizedExceptionMapper")); } + @Test + public void shouldDenyUnannotatedWithAnnotationTransformer() { + String path = "/special/explicit"; + assertStatus(path, 403, 401); + path = "/special/implicit"; + assertStatus(path, 403, 401); + } + private void assertStatus(String path, int status, int anonStatus) { given().auth().preemptive() .basic("admin", "admin").get(path) @@ -105,4 +170,15 @@ private void assertStatus(String path, int status, int anonStatus) { } + @Path("/special") + public static class SpecialResource { + @GET + public String explicit() { + return "explicit"; + } + + public String implicit() { + return "implicit"; + } + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java new file mode 100644 index 0000000000000..8250d5a9bf9a6 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java @@ -0,0 +1,14 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +public class UnsecuredParentResource { + + @Path("/defaultSecurityParent") + @GET + public String defaultSecurityParent() { + return "defaultSecurityParent"; + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java index 9129d5c4c9726..abbf1d327918f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java @@ -11,7 +11,7 @@ * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com */ @Path("/unsecured") -public class UnsecuredResource { +public class UnsecuredResource extends UnsecuredParentResource implements UnsecuredResourceInterface { @Path("/defaultSecurity") @GET public String defaultSecurity() { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java new file mode 100644 index 0000000000000..7be652f15ef8d --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java @@ -0,0 +1,14 @@ +package io.quarkus.resteasy.reactive.server.test.security; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +public interface UnsecuredResourceInterface { + + @Path("/defaultSecurityInterface") + @GET + default String defaultSecurityInterface() { + return "defaultSecurityInterface"; + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java index f0dc401fa867b..e27aa804934c4 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java @@ -59,9 +59,14 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti ResteasyReactiveResourceInfo lazyMethod = requestContext.getTarget().getLazyMethod(); MethodDescription methodDescription = lazyMethodToMethodDescription(lazyMethod); if (check == null) { - check = Arc.container().instance(SecurityCheckStorage.class).get().getSecurityCheck(methodDescription); + SecurityCheckStorage storage = Arc.container().instance(SecurityCheckStorage.class).get(); + check = storage.getSecurityCheck(methodDescription); if (check == null) { - check = NULL_SENTINEL; + if (storage.getDefaultSecurityCheck() == null || isRequestAlreadyChecked(requestContext)) { + check = NULL_SENTINEL; + } else { + check = storage.getDefaultSecurityCheck(); + } } this.check = check; } @@ -142,6 +147,13 @@ private void preventRepeatedSecurityChecks(ResteasyReactiveRequestContext reques requestContext.setProperty(STANDARD_SECURITY_CHECK_INTERCEPTOR, methodDescription); } + private boolean isRequestAlreadyChecked(ResteasyReactiveRequestContext requestContext) { + // when request has already been checked at least once (by another instance of this handler) + // then default security checks, like denied access to all JAX-RS resources by default + // shouldn't be applied; this doesn't mean security checks registered for methods shouldn't be applied + return requestContext.getProperty(STANDARD_SECURITY_CHECK_INTERCEPTOR) != null; + } + private InjectableInstance getCurrentIdentityAssociation() { InjectableInstance identityAssociation = this.currentIdentityAssociation; if (identityAssociation == null) { diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index d781085f835ac..8aa777f640b67 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -105,6 +105,7 @@ import io.quarkus.security.runtime.interceptor.SecurityHandler; import io.quarkus.security.spi.AdditionalSecuredClassesBuildItem; import io.quarkus.security.spi.AdditionalSecuredMethodsBuildItem; +import io.quarkus.security.spi.DefaultSecurityCheckBuildItem; import io.quarkus.security.spi.RolesAllowedConfigExpResolverBuildItem; import io.quarkus.security.spi.runtime.AuthorizationController; import io.quarkus.security.spi.runtime.DevModeDisabledAuthorizationController; @@ -527,6 +528,7 @@ void gatherSecurityChecks(BuildProducer syntheticBeans, BuildProducer configBuilderProducer, List additionalSecuredMethods, SecurityCheckRecorder recorder, + Optional defaultSecurityCheckBuildItem, BuildProducer reflectiveClassBuildItemBuildProducer, List additionalSecurityChecks, SecurityBuildTimeConfig config) { classPredicate.produce(new ApplicationClassPredicateBuildItem(new SecurityCheckStorageAppPredicate())); @@ -559,6 +561,15 @@ void gatherSecurityChecks(BuildProducer syntheticBeans, recorder.addMethod(builder, method.declaringClass().name().toString(), method.name(), params, methodEntry.getValue()); } + + if (defaultSecurityCheckBuildItem.isPresent()) { + var roles = defaultSecurityCheckBuildItem.get().getRolesAllowed(); + if (roles == null) { + recorder.registerDefaultSecurityCheck(builder, recorder.denyAll()); + } else { + recorder.registerDefaultSecurityCheck(builder, recorder.rolesAllowed(roles.toArray(new String[0]))); + } + } recorder.create(builder); syntheticBeans.produce( diff --git a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java index 3cfeaa2b44b80..2d72f5f8dc2e2 100644 --- a/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java +++ b/extensions/security/runtime-spi/src/main/java/io/quarkus/security/spi/runtime/SecurityCheckStorage.java @@ -10,4 +10,9 @@ default SecurityCheck getSecurityCheck(Method method) { SecurityCheck getSecurityCheck(MethodDescription methodDescription); + /** + * {@link SecurityCheck} that should be applied when there is no other check applied on incoming request. + */ + SecurityCheck getDefaultSecurityCheck(); + } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java index 8661d06f3f4bd..545b3249cd9b0 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityCheckRecorder.java @@ -351,4 +351,8 @@ private Class loadClass(String className) { throw new RuntimeException("Unable to load class '" + className + "' for creating permission", e); } } + + public void registerDefaultSecurityCheck(RuntimeValue builder, SecurityCheck securityCheck) { + builder.getValue().registerDefaultSecurityCheck(securityCheck); + } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java index 6be2f134e0057..25cecdb398e78 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityCheckStorageBuilder.java @@ -9,6 +9,7 @@ public class SecurityCheckStorageBuilder { private final Map securityChecks = new HashMap<>(); + private SecurityCheck defaultSecurityCheck; public void registerCheck(String className, String methodName, @@ -17,12 +18,24 @@ public void registerCheck(String className, securityChecks.put(new MethodDescription(className, methodName, parameterTypes), securityCheck); } + public void registerDefaultSecurityCheck(SecurityCheck defaultSecurityCheck) { + if (this.defaultSecurityCheck != null) { + throw new IllegalStateException("Default SecurityCheck has already been registered"); + } + this.defaultSecurityCheck = defaultSecurityCheck; + } + public SecurityCheckStorage create() { return new SecurityCheckStorage() { @Override public SecurityCheck getSecurityCheck(MethodDescription methodDescription) { return securityChecks.get(methodDescription); } + + @Override + public SecurityCheck getDefaultSecurityCheck() { + return defaultSecurityCheck; + } }; } } diff --git a/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java b/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java new file mode 100644 index 0000000000000..ed3dafe18de0d --- /dev/null +++ b/extensions/security/spi/src/main/java/io/quarkus/security/spi/DefaultSecurityCheckBuildItem.java @@ -0,0 +1,28 @@ +package io.quarkus.security.spi; + +import java.util.List; +import java.util.Objects; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class DefaultSecurityCheckBuildItem extends SimpleBuildItem { + + public final List rolesAllowed; + + private DefaultSecurityCheckBuildItem(List rolesAllowed) { + this.rolesAllowed = rolesAllowed; + } + + public static DefaultSecurityCheckBuildItem denyAll() { + return new DefaultSecurityCheckBuildItem(null); + } + + public static DefaultSecurityCheckBuildItem rolesAllowed(List rolesAllowed) { + Objects.requireNonNull(rolesAllowed); + return new DefaultSecurityCheckBuildItem(List.copyOf(rolesAllowed)); + } + + public List getRolesAllowed() { + return rolesAllowed; + } +} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index b8dedcde357e2..2e0dd67f68775 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -419,25 +419,6 @@ protected List createEndpoints(ClassInfo currentClassInfo, return ret; } - /** - * Return endpoints defined directly on classInfo. - * - * @param classInfo resource class - * @return classInfo endpoint method info - */ - public static Collection collectClassEndpoints(ClassInfo classInfo, - Map httpAnnotationToMethod, IndexView index, ApplicationScanningResult applicationScanningResult) { - Collection endpoints = collectEndpoints(classInfo, classInfo, new HashSet<>(), new HashSet<>(), true, - httpAnnotationToMethod, index, applicationScanningResult, new AnnotationStore(null)); - Collection ret = new HashSet<>(); - for (FoundEndpoint endpoint : endpoints) { - if (endpoint.classInfo.equals(classInfo)) { - ret.add(endpoint.methodInfo); - } - } - return ret; - } - private static List collectEndpoints(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Set seenMethods, Set existingClassNameBindings, boolean considerApplication, Map httpAnnotationToMethod, IndexView index, ApplicationScanningResult applicationScanningResult, From b042be40b26242360264a1f2a25f1e36ce4ff662 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 23 Jan 2024 14:31:33 +0100 Subject: [PATCH 206/212] Bump to Netty 4.1.106.Final (cherry picked from commit 9a12df45348dc61da36b22acc56bdaca6dbfc8fd) --- 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 bb93702b95237..114690dd51ef7 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -141,7 +141,7 @@ 14.0.21.Final 4.6.5.Final 3.1.5 - 4.1.100.Final + 4.1.106.Final 1.12.0 1.0.4 3.5.3.Final From 832024cd2afc09244a097cfd296234926833c09e Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 23 Jan 2024 20:36:12 -0300 Subject: [PATCH 207/212] Register JDBC RowSet required bundle (cherry picked from commit 966126615030c26567e31253ce509886fd755004) --- .../java/io/quarkus/agroal/deployment/AgroalProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index f7a7040fe886d..14aa3898d6407 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -58,6 +58,7 @@ import io.quarkus.deployment.builditem.RemovedResourceBuildItem; import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.maven.dependency.ArtifactKey; @@ -405,4 +406,9 @@ void adaptOpenTelemetryJdbcInstrumentationForNative(BuildProducer Date: Wed, 24 Jan 2024 15:03:16 +0200 Subject: [PATCH 208/212] Ensure that response body of unsuccessful SSE request can be read Fixes: #38325 (cherry picked from commit e2668c5892a4c7e50e83f971a36047601f4526d2) --- .../reactive/jackson/test/MultiSseTest.java | 48 ++++++++++++++++++- .../handlers/ClientSendRequestHandler.java | 4 +- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java index 629b881a93bec..6e8774f400e36 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java @@ -8,20 +8,26 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Predicate; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.sse.OutboundSseEvent; import jakarta.ws.rs.sse.Sse; import jakarta.ws.rs.sse.SseEventSink; +import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.RestHeader; import org.jboss.resteasy.reactive.RestStreamElementType; import org.jboss.resteasy.reactive.client.SseEvent; import org.jboss.resteasy.reactive.client.SseEventFilter; @@ -31,6 +37,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.rest.client.reactive.ClientExceptionMapper; import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; @@ -53,6 +60,29 @@ void shouldConsume() { await().atMost(5, TimeUnit.SECONDS) .untilAsserted( () -> assertThat(resultList).containsExactly("foo", "bar")); + + } + + @Test + void shouldReadBodyFromFailedResponse() { + var errorBody = new AtomicReference(); + createClient() + .fail() + .subscribe().with(new Consumer() { + @Override + public void accept(Object o) { + + } + }, new Consumer<>() { + @Override + public void accept(Throwable t) { + errorBody.set(t.getMessage()); + } + }); + + await().atMost(5, TimeUnit.SECONDS) + .untilAsserted( + () -> assertThat(errorBody.get()).isEqualTo("invalid input provided")); } @Test @@ -209,6 +239,11 @@ public interface SseClient { @Produces(MediaType.SERVER_SENT_EVENTS) Multi get(); + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + @ClientHeaderParam(name = "fail", value = "true") + Multi fail(); + @GET @Path("/json") @Produces(MediaType.SERVER_SENT_EVENTS) @@ -239,6 +274,14 @@ public interface SseClient { @Produces(MediaType.SERVER_SENT_EVENTS) @SseEventFilter(CustomFilter.class) Multi> eventWithFilter(); + + @ClientExceptionMapper + static RuntimeException toException(Response response) { + if (response.getStatusInfo().getStatusCode() == 400) { + return new IllegalArgumentException(response.readEntity(String.class)); + } + return null; + } } public static class CustomFilter implements Predicate> { @@ -260,7 +303,10 @@ public static class SseResource { @GET @Produces(MediaType.SERVER_SENT_EVENTS) - public Multi get() { + public Multi get(@DefaultValue("false") @RestHeader boolean fail) { + if (fail) { + throw new WebApplicationException(Response.status(400).entity("invalid input provided").build()); + } return Multi.createFrom().items("foo", "bar"); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 06f916d109038..06e2ba8da2477 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -19,6 +19,7 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Variant; import org.jboss.logging.Logger; @@ -246,7 +247,8 @@ public void handle(Void event) { requestContext.resume(); } }); - } else if (!requestContext.isRegisterBodyHandler()) { + } else if (!requestContext.isRegisterBodyHandler() + && (Response.Status.Family.familyOf(status) == Response.Status.Family.SUCCESSFUL)) { // we force the registration of a body handler if there was an error, so we can ensure the body can be read clientResponse.pause(); if (loggingScope != LoggingScope.NONE) { clientLogger.logResponse(clientResponse, false); From d8904b421f428d56eb2daee6e9d1d782659cafaf Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 24 Jan 2024 10:20:58 -0300 Subject: [PATCH 209/212] Include RowSet properties file in native image This fixes the `Resource javax/sql/rowset/rowset.properties not found` error (cherry picked from commit 131a1223de230f16efb45b714c66e140a146a0da) --- .../io/quarkus/agroal/deployment/AgroalProcessor.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 14aa3898d6407..a3508bb57ef60 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -408,7 +408,14 @@ void adaptOpenTelemetryJdbcInstrumentationForNative(BuildProducer resourceBundleProducer, + BuildProducer nativeResourceProducer, + BuildProducer reflectiveClassProducer) { + resourceBundleProducer.produce(new NativeImageResourceBundleBuildItem("com.sun.rowset.RowSetResourceBundle")); + nativeResourceProducer.produce(new NativeImageResourceBuildItem("javax/sql/rowset/rowset.properties")); + reflectiveClassProducer.produce(ReflectiveClassBuildItem.builder( + "com.sun.rowset.providers.RIOptimisticProvider", + "com.sun.rowset.providers.RIXMLProvider").build()); } } From efc6ffa2211e6d679c3cd4e4f4fa6e73ab3ec8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Fri, 26 Jan 2024 18:22:49 +0100 Subject: [PATCH 210/212] Do not require RoutingContext outside or RESTEasy handler (cherry picked from commit 36bfe4cda3129e242f95d7e2b14703d531aa29e0) --- .../runtime/StandardSecurityCheckInterceptor.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java index 036ab8fcf3b74..10ad1effe0416 100644 --- a/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java +++ b/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/StandardSecurityCheckInterceptor.java @@ -17,7 +17,7 @@ import io.quarkus.security.Authenticated; import io.quarkus.security.PermissionsAllowed; import io.quarkus.security.spi.runtime.AuthorizationController; -import io.vertx.ext.web.RoutingContext; +import io.quarkus.vertx.http.runtime.CurrentVertxRequest; /** * Security checks for RBAC annotations on endpoints are done by the {@link EagerSecurityFilter}, this interceptor @@ -30,12 +30,14 @@ public abstract class StandardSecurityCheckInterceptor { AuthorizationController controller; @Inject - RoutingContext routingContext; + CurrentVertxRequest currentVertxRequest; @AroundInvoke public Object intercept(InvocationContext ic) throws Exception { - if (controller.isAuthorizationEnabled()) { - Method method = routingContext.get(EagerSecurityFilter.class.getName()); + // RoutingContext can be null if RESTEasy is used together with other stacks that do not rely on it (e.g. gRPC) + // and this is not invoked from RESTEasy route handler + if (controller.isAuthorizationEnabled() && currentVertxRequest.getCurrent() != null) { + Method method = currentVertxRequest.getCurrent().get(EagerSecurityFilter.class.getName()); if (method != null && method.equals(ic.getMethod())) { ic.getContextData().put(SECURITY_HANDLER, EXECUTED); } From 485fbeb9a26367270eb8d013b907de92c6b000b4 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 7 Feb 2024 09:00:17 +0100 Subject: [PATCH 211/212] Update Vert.x to version 4.4.8 Fix CVE-2024-1300 io.vertx:vertx-core: memory leak when a TCP server is configured with TLS and SNI support --- bom/application/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 114690dd51ef7..292082e7c70fd 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -118,7 +118,7 @@ 1.0.1.Final 2.2.2.Final 3.5.1.Final - 4.4.6 + 4.4.8 4.5.14 4.4.16 4.1.5 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index f91324942e673..3ec455fa1f133 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -64,7 +64,7 @@ 3.1.2 2.5.1 2.1.2 - 4.4.6 + 4.4.8 5.3.2 1.0.0.Final 2.15.3 From ce901c9994f3bdbb51323270247f208e4bf3bc1b Mon Sep 17 00:00:00 2001 From: Lorenzo Vannucchi Date: Fri, 12 Apr 2024 01:13:45 +0200 Subject: [PATCH 212/212] feat: #39979 Init skipMethodParameter with defined predicate by user [ResteasyReactiveProcessor inits skipMethodParameter if there is a predicate annotated with @RestEasyParamsFilter by user. This allows to ignore params chosen by user logic marking them as SKIPPED in order to use different annotated params with respect JAXRS api] --- .../deployment/ResteasyReactiveProcessor.java | 24 +++++++++++++++++++ .../reactive/RestEasyParamsFilter.java | 11 +++++++++ .../startup/RuntimeResourceDeployment.java | 2 ++ 3 files changed, 37 insertions(+) create mode 100644 independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestEasyParamsFilter.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 26a848224c7ed..e83dfbd200fd8 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -12,6 +12,7 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; @@ -64,6 +65,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.RestEasyParamsFilter; import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.core.SingletonBeanFactory; import org.jboss.resteasy.reactive.common.model.InjectableBean; @@ -229,6 +231,7 @@ public class ResteasyReactiveProcessor { DotName.createSimple(RoutingContext.class.getName())); private static final DotName FILE = DotName.createSimple(File.class.getName()); private static final DotName ENDPOINT_DISABLED = DotName.createSimple(EndpointDisabled.class.getName()); + private static final DotName RESTEASY_PARAM_FILTER = DotName.createSimple(RestEasyParamsFilter.class.getName()); private static final int SECURITY_EXCEPTION_MAPPERS_PRIORITY = Priorities.USER + 1; private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -634,6 +637,19 @@ public Supplier apply(ClassInfo classInfo) { } }); + for (AnnotationInstance ann : index.getAnnotations(RESTEASY_PARAM_FILTER)) { + Class>> predicate = loadClass(ann.target().asClass().name()); + if (predicate == null) { + break; + } + try { + serverEndpointIndexerBuilder.setSkipMethodParameter(predicate.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + break; + } + if (!serverDefaultProducesHandlers.isEmpty()) { List handlers = new ArrayList<>(serverDefaultProducesHandlers.size()); for (ServerDefaultProducesHandlerBuildItem bi : serverDefaultProducesHandlers) { @@ -740,6 +756,14 @@ public Supplier apply(ClassInfo classInfo) { handleDateFormatReflection(reflectiveClassBuildItemBuildProducer, index); } + private static Class loadClass(DotName filterDotName) { + try { + return (Class) Class.forName(filterDotName.toString(), false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException classNotFoundException) { + return null; + } + } + private boolean filtersAccessResourceMethod(ResourceInterceptors resourceInterceptors) { AtomicBoolean ab = new AtomicBoolean(false); ResourceInterceptors.FiltersVisitor visitor = new ResourceInterceptors.FiltersVisitor() { diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestEasyParamsFilter.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestEasyParamsFilter.java new file mode 100644 index 0000000000000..ed2cfa8c464b8 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/RestEasyParamsFilter.java @@ -0,0 +1,11 @@ +package org.jboss.resteasy.reactive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RestEasyParamsFilter { +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index b8697b15158c5..ecaa51d224e85 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -352,6 +352,8 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, addHandlers(handlers, clazz, method, info, HandlerChainCustomizer.Phase.RESOLVE_METHOD_PARAMETERS); for (int i = 0; i < parameters.length; i++) { ServerMethodParameter param = (ServerMethodParameter) parameters[i]; + if (param.parameterType.equals(ParameterType.SKIPPED)) + continue; ParameterExtractor extractor = parameterExtractor(pathParameterIndexes, locatableResource, param); ParameterConverter converter = null; ParamConverterProviders paramConverterProviders = info.getParamConverterProviders();