From d543733e430fba6b595d67ab77ecdea141680405 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 07:31:48 +0000 Subject: [PATCH 01/28] Bump grpc.version from 1.56.1 to 1.57.2 Bumps `grpc.version` from 1.56.1 to 1.57.2. Updates `io.grpc:grpc-bom` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:linux-aarch_64` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:linux-x86_32` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:linux-x86_64` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:osx-x86_64` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:osx-aarch_64` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:windows-x86_32` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) Updates `io.grpc:protoc-gen-grpc-java:windows-x86_64` from 1.56.1 to 1.57.2 - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.56.1...v1.57.2) --- updated-dependencies: - dependency-name: io.grpc:grpc-bom dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-aarch_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_32 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:linux-x86_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:osx-x86_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:osx-aarch_64 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_32 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.grpc:protoc-gen-grpc-java:windows-x86_64 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3d1cfd638d3b4..0e7345904947e 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 6.7.2 - 1.56.1 + 1.57.2 1.2.1 3.22.0 ${protoc.version} From 7a8d51e99a5350775313066e2e560000aa2d7037 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 28 Aug 2023 13:29:48 +0200 Subject: [PATCH 02/28] Upgrade to Jandex 3.1.3 --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 11701c4e562c9..53385fd825a43 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -20,7 +20,7 @@ 1.0.16 5.0.0 3.0.2 - 3.1.2 + 3.1.3 1.3.2 1 1.1.2 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 3cd7c2b80fba6..2e5e9e5715977 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -33,7 +33,7 @@ ${version.surefire.plugin} - 3.1.2 + 3.1.3 1.0.0 2.5.10 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index efd2ccf4a662a..b10bcde2f5233 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -48,7 +48,7 @@ 2.0.1 1.6.1.Final - 3.1.2 + 3.1.3 3.5.3.Final 2.2.0 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 4f42d646d72e1..774b3e8be812b 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -41,7 +41,7 @@ 3.2.1 3.1.2 1.6.8 - 3.1.2 + 3.1.3 3.24.2 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index b5f4a88371db7..767d6a20c0fdc 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -43,7 +43,7 @@ 11 5.9.3 3.24.2 - 3.1.2 + 3.1.3 1.6.1.Final 3.5.3.Final 3.11.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index bc6841307eb50..a53964d90c45b 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.2 + 3.1.3 1.12.12 5.9.3 3.9.3 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 0417510abaf27..6a9fe57c2dcda 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -63,7 +63,7 @@ 1.6.8 ${project.version} 25 - 3.1.2 + 3.1.3 2.0.2 4.2.0 From 335651b2f741c8f65ef181b0e0fde9db5fa6fde4 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 29 Aug 2023 12:54:38 +0200 Subject: [PATCH 03/28] ArC: fix binary compatibility problem with ManagedContext - follow-up to #34310 --- independent-projects/arc/pom.xml | 1 + independent-projects/arc/runtime/pom.xml | 19 +++++++++++++++++++ .../java/io/quarkus/arc/ManagedContext.java | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index efd2ccf4a662a..215db4b5c39c0 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -51,6 +51,7 @@ 3.1.2 3.5.3.Final 2.2.0 + 1.6.Final 3.24.2 5.9.3 diff --git a/independent-projects/arc/runtime/pom.xml b/independent-projects/arc/runtime/pom.xml index c8e62a8ab7ec4..91061affda06c 100644 --- a/independent-projects/arc/runtime/pom.xml +++ b/independent-projects/arc/runtime/pom.xml @@ -47,5 +47,24 @@ + + + + + org.jboss.bridger + bridger + ${version.bridger} + + + weave + process-classes + + transform + + + + + + diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java index 4e7037255e73f..69e7d09cbc08c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java @@ -17,6 +17,11 @@ default ContextState activate() { return activate(null); } + // Maintain binary compatibility with Quarkus 3.2 + default void activate$$bridge() { + activate(null); + } + /** * Activate the context. *

@@ -28,6 +33,11 @@ default ContextState activate() { */ ContextState activate(ContextState initialState); + // Maintain binary compatibility with Quarkus 3.2 + default void activate$$bridge(ContextState initialState) { + activate(initialState); + } + /** * Deactivate the context - do not destoy existing contextual instances. */ From 5bde49eb11eb733d547d2194780a1e02d61aa99f Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 29 Aug 2023 14:24:19 +0200 Subject: [PATCH 04/28] Update bytebuddy as we need a version working with Java 21 --- 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 11701c4e562c9..77908124f6eb8 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -100,7 +100,7 @@ 6.2.7.Final - 1.12.18 + 1.14.7 6.0.6.Final 2.0.4.Final 8.0.1.Final From 6cfb450ab8e2d3ef2db6606c88b681c2b6a954a1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 30 Aug 2023 10:05:15 +0300 Subject: [PATCH 05/28] Move @Generated annotation from gRPC to core --- .../runtime/src/main/java/io/quarkus}/Generated.java | 2 +- .../java/io/quarkus/grpc/deployment/GrpcPostProcessing.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {extensions/grpc-common/runtime/src/main/java/io/quarkus/grpc/common => core/runtime/src/main/java/io/quarkus}/Generated.java (98%) diff --git a/extensions/grpc-common/runtime/src/main/java/io/quarkus/grpc/common/Generated.java b/core/runtime/src/main/java/io/quarkus/Generated.java similarity index 98% rename from extensions/grpc-common/runtime/src/main/java/io/quarkus/grpc/common/Generated.java rename to core/runtime/src/main/java/io/quarkus/Generated.java index 1c744b8fd5102..332307aa646b0 100644 --- a/extensions/grpc-common/runtime/src/main/java/io/quarkus/grpc/common/Generated.java +++ b/core/runtime/src/main/java/io/quarkus/Generated.java @@ -1,4 +1,4 @@ -package io.quarkus.grpc.common; +package io.quarkus; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; diff --git a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java index 84cf4429aa1b6..abe19f5de0934 100644 --- a/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java +++ b/extensions/grpc/codegen/src/main/java/io/quarkus/grpc/deployment/GrpcPostProcessing.java @@ -25,7 +25,7 @@ public class GrpcPostProcessing { private static final String POST_PROCESS_NO_FINAL = "quarkus.generate-code.grpc-post-processing.no-final"; // this is intentionally split so that it doesn't get replaced by the Jakarta transformer public static final String JAVAX_GENERATED = "javax" + ".annotation.Generated"; - public static final String QUARKUS_GENERATED = "io.quarkus.grpc.common.Generated"; + public static final String QUARKUS_GENERATED = "io.quarkus.Generated"; public static final String STUB = "Stub"; public static final String BIND_METHOD = "bindService"; From 9a5ede8ffcfe3155874c8846d5d5bcee1019fa6f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 30 Aug 2023 10:06:59 +0300 Subject: [PATCH 06/28] Fix small Javadoc issues in @Generated --- core/runtime/src/main/java/io/quarkus/Generated.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/Generated.java b/core/runtime/src/main/java/io/quarkus/Generated.java index 332307aa646b0..0d8ad98fc6e02 100644 --- a/core/runtime/src/main/java/io/quarkus/Generated.java +++ b/core/runtime/src/main/java/io/quarkus/Generated.java @@ -17,7 +17,7 @@ * The {@code @Generated} annotation is used to mark source code that has been generated during the gRPC generation phase. * Classes generated by {@code protoc} are post-processed to remove the {@code jakarta.annotation.Generated} and use * this annotation instead, avoiding a dependency on a pre-jakarta annotation. - * + *

* This class is a direct copy of {@code jakarta.annotation.Generated}. */ @Documented @@ -38,14 +38,14 @@ * Date when the source was generated. The date element must follow the ISO * 8601 standard. For example the date element would have the following * value 2017-07-04T12:08:56.235-0700 which represents 2017-07-04 12:08:56 - * local time in the U.S. Pacific Time time zone. + * local time in the U.S. Pacific Time zone. * * @return The date the source was generated */ String date() default ""; /** - * A place holder for any comments that the code generator may want to + * A placeholder for any comments that the code generator may want to * include in the generated code. * * @return Comments that the code generated included From 03f5f0641701ff2c347bcd1335acd7ebc7f83a48 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 30 Aug 2023 10:23:51 +0300 Subject: [PATCH 07/28] Add the @Generated annotation $$accessor classes This is done to keep code coverage classes happy Closes: #35601 --- .../annotation/processor/ExtensionAnnotationProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index ddd722a0e3254..665fa57ad594c 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -80,6 +80,7 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor { private static final Pattern REMOVE_LEADING_SPACE = Pattern.compile("^ ", Pattern.MULTILINE); + private static final String QUARKUS_GENERATED = "io.quarkus.Generated"; private final ConfigDocWriter configDocWriter = new ConfigDocWriter(); private final ConfigDocItemScanner configDocItemScanner = new ConfigDocItemScanner(); @@ -711,6 +712,7 @@ private void generateAccessor(final TypeElement clazz) { } final JClassDef classDef = sourceFile._class(JMod.PUBLIC | JMod.FINAL, className); classDef.constructor(JMod.PRIVATE); // no construction + classDef.annotate(QUARKUS_GENERATED).value("Quarkus annotation processor"); final JAssignableExpr instanceName = JExprs.name(Constants.INSTANCE_SYM); boolean isEnclosingClassPublic = clazz.getModifiers().contains(Modifier.PUBLIC); // iterate fields From 98c57e148c5e52682585dd28bd0927daded28795 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Wed, 30 Aug 2023 12:07:03 +0200 Subject: [PATCH 08/28] Add minor clarifications and cleanup to CreateMockitoSpiesCallback --- .../mockito/internal/CreateMockitoSpiesCallback.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java index 1aaea0ba7ccff..181aeaf18f7f7 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/CreateMockitoSpiesCallback.java @@ -8,6 +8,7 @@ import org.mockito.Mockito; import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InjectableContext; import io.quarkus.arc.InstanceHandle; @@ -18,16 +19,18 @@ public class CreateMockitoSpiesCallback implements QuarkusTestAfterConstructCallback, QuarkusTestAfterAllCallback { - // in nested tests, there are multiple states created before destruction is triggered + // Set is here because in nested tests, there are multiple states created before destruction is triggered + // This field needs to be static because each implemented callback created a new instance of this class private static Set statesToDestroy = new HashSet<>(); @Override public void afterConstruct(Object testInstance) { Class current = testInstance.getClass(); // QuarkusTestAfterConstructCallback can be used in @QuarkusIntegrationTest where there is no Arc - boolean contextPreviouslyActive = Arc.container() != null && Arc.container().requestContext().isActive(); + ArcContainer container = Arc.container(); + boolean contextPreviouslyActive = container != null && container.requestContext().isActive(); if (!contextPreviouslyActive) { - Arc.container().requestContext().activate(); + statesToDestroy.add(container.requestContext().activate()); } while (current.getSuperclass() != null) { for (Field field : current.getDeclaredFields()) { @@ -44,8 +47,7 @@ public void afterConstruct(Object testInstance) { } if (!contextPreviouslyActive) { // only deactivate; we will destroy them in QuarkusTestAfterAllCallback - statesToDestroy.add(Arc.container().requestContext().getState()); - Arc.container().requestContext().deactivate(); + container.requestContext().deactivate(); } } From 651d391d6925e778c9800d767d556e7c5f271549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 30 Aug 2023 12:53:04 +0200 Subject: [PATCH 09/28] Refactor Hibernate config mapping to use groups instead of dotted names (take 2) Follows up on commit ad94afb --- .../orm/deployment/HibernateOrmConfig.java | 22 +++++++++--- .../HibernateOrmConfigPersistenceUnit.java | 36 +++++++++++++------ .../orm/deployment/HibernateOrmProcessor.java | 10 +++--- .../metrics/HibernateOrmMetricsProcessor.java | 2 +- .../HibernateReactiveProcessor.java | 10 +++--- 5 files changed, 55 insertions(+), 25 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java index a342c1a770ffa..e2be11c863d71 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfig.java @@ -79,10 +79,10 @@ public class HibernateOrmConfig { public Optional logSessionMetrics; /** - * Whether metrics are published if a metrics extension is enabled. + * Configuration related to metrics. */ - @ConfigItem(name = "metrics.enabled") - public boolean metricsEnabled; + @ConfigItem + public HibernateOrmConfigMetric metrics; public boolean isAnyNonPersistenceXmlPropertySet() { // Do NOT include persistenceXml in here. @@ -91,7 +91,7 @@ public boolean isAnyNonPersistenceXmlPropertySet() { log.isAnyPropertySet() || statistics.isPresent() || logSessionMetrics.isPresent() || - metricsEnabled; + metrics.isAnyPropertySet(); } public Map getAllPersistenceUnitConfigsAsMap() { @@ -170,4 +170,18 @@ public static class HibernateOrmConfigDatabase { public DatabaseOrmCompatibilityVersion ormCompatibilityVersion; } + @ConfigGroup + public static class HibernateOrmConfigMetric { + + /** + * Whether metrics are published if a metrics extension is enabled. + */ + @ConfigItem + public boolean enabled; + + public boolean isAnyPropertySet() { + return enabled; + } + } + } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index be2da22d14f81..f87622b5957a0 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -209,15 +209,10 @@ public class HibernateOrmConfigPersistenceUnit { public HibernateOrmConfigPersistenceUnitDiscriminator discriminator; /** - * Identifiers can be quoted using one of the available strategies. - *

- * Set to {@code none} by default, meaning no identifiers will be quoted. If set to {@code all}, all identifiers and column - * definitions will be quoted. Additionally, setting it to {@code all-except-column-definitions} will skip the column - * definitions, which can usually be required when they exist, or else use the option {@code only-keywords} to quote only - * identifiers deemed SQL keywords by the Hibernate ORM dialect. + * Config related to identifier quoting. */ - @ConfigItem(defaultValue = "none", name = "quote-identifiers.strategy") - public IdentifierQuotingStrategy identifierQuotingStrategy; + @ConfigItem(defaultValue = "none") + public HibernateOrmConfigPersistenceUnitQuoteIdentifiers quoteIdentifiers; /** * The default in Quarkus is for 2nd level caching to be enabled, @@ -288,7 +283,7 @@ public boolean isAnyPropertySet() { multitenantSchemaDatasource.isPresent() || fetch.isAnyPropertySet() || discriminator.isAnyPropertySet() || - identifierQuotingStrategy != IdentifierQuotingStrategy.NONE || + quoteIdentifiers.isAnyPropertySet() || !unsupportedProperties.isEmpty(); } @@ -550,7 +545,7 @@ public static class HibernateOrmConfigPersistenceUnitDatabase { /** * Whether Hibernate should quote all identifiers. * - * @deprecated {@link #identifierQuotingStrategy} should be used to configure quoting strategy. + * @deprecated {@link #quoteIdentifiers} should be used to configure quoting strategy. */ @ConfigItem @Deprecated @@ -652,6 +647,27 @@ public boolean isAnyPropertySet() { } + @ConfigGroup + public static class HibernateOrmConfigPersistenceUnitQuoteIdentifiers { + + /** + * Identifiers can be quoted using one of the available strategies. + *

+ * Set to {@code none} by default, meaning no identifiers will be quoted. If set to {@code all}, all identifiers and + * column + * definitions will be quoted. Additionally, setting it to {@code all-except-column-definitions} will skip the column + * definitions, which can usually be required when they exist, or else use the option {@code only-keywords} to quote + * only + * identifiers deemed SQL keywords by the Hibernate ORM dialect. + */ + @ConfigItem(defaultValue = "none") + public IdentifierQuotingStrategy strategy; + + public boolean isAnyPropertySet() { + return strategy != IdentifierQuotingStrategy.NONE; + } + } + /** * Discriminator configuration. * 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 a2117baab9210..cd4fa7da36329 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 @@ -966,15 +966,15 @@ private static void producePersistenceUnitDescriptorFromConfig( persistenceUnitConfig.database.charset.name()); // Quoting strategy - if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL - || persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS + if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL + || persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS || persistenceUnitConfig.database.globallyQuotedIdentifiers) { descriptor.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); } - if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { + if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { descriptor.getProperties().setProperty( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); - } else if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ONLY_KEYWORDS) { + } else if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ONLY_KEYWORDS) { descriptor.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); } @@ -1019,7 +1019,7 @@ private static void producePersistenceUnitDescriptorFromConfig( String.valueOf(fetchSize))); // Statistics - if (hibernateOrmConfig.metricsEnabled + if (hibernateOrmConfig.metrics.enabled || (hibernateOrmConfig.statistics.isPresent() && hibernateOrmConfig.statistics.get())) { descriptor.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); //When statistics are enabled, the default in Hibernate ORM is to also log them after each diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/metrics/HibernateOrmMetricsProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/metrics/HibernateOrmMetricsProcessor.java index de04d1dc662a4..155c09b7e3957 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/metrics/HibernateOrmMetricsProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/metrics/HibernateOrmMetricsProcessor.java @@ -33,7 +33,7 @@ public void metrics(HibernateOrmConfig config, // IF Hibernate metrics and Hibernate statistics are enabled // then define a consumer. It will only be invoked if metrics is enabled - if (config.metricsEnabled && config.statistics.orElse(true) && metricsConfiguration.isPresent()) { + if (config.metrics.enabled && config.statistics.orElse(true) && metricsConfiguration.isPresent()) { datasourceMetrics.produce(new MetricsFactoryConsumerBuildItem(metricsRecorder.consumeMetricsFactory())); } } diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index a6ac9c7e45224..9dff2f96a0122 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -303,14 +303,14 @@ private static ParsedPersistenceXmlDescriptor generateReactivePersistenceUnit( persistenceUnitConfig.database.charset.name()); // Quoting strategy - if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL - || persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS + if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL + || persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS || persistenceUnitConfig.database.globallyQuotedIdentifiers) { desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, "true"); } - if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { + if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ALL_EXCEPT_COLUMN_DEFINITIONS) { desc.getProperties().setProperty(AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, "true"); - } else if (persistenceUnitConfig.identifierQuotingStrategy == IdentifierQuotingStrategy.ONLY_KEYWORDS) { + } else if (persistenceUnitConfig.quoteIdentifiers.strategy == IdentifierQuotingStrategy.ONLY_KEYWORDS) { desc.getProperties().setProperty(AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, "true"); } @@ -353,7 +353,7 @@ private static ParsedPersistenceXmlDescriptor generateReactivePersistenceUnit( String.valueOf(statementBatchSize))); // Statistics - if (hibernateOrmConfig.metricsEnabled + if (hibernateOrmConfig.metrics.enabled || (hibernateOrmConfig.statistics.isPresent() && hibernateOrmConfig.statistics.get())) { desc.getProperties().setProperty(AvailableSettings.GENERATE_STATISTICS, "true"); } From 46d460d3177855bdf5d8dfd1f0fbf17e05de5b28 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 30 Aug 2023 09:39:41 -0500 Subject: [PATCH 10/28] Preserve format style when adding after-shutdown message --- .../java/io/quarkus/runtime/logging/LogCleanupFilter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java index 7f60c667dc50d..8d94f2a78160c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java @@ -8,6 +8,7 @@ import java.util.logging.LogRecord; import org.jboss.logging.Logger; +import org.jboss.logmanager.ExtLogRecord; public class LogCleanupFilter implements Filter { @@ -30,7 +31,12 @@ public boolean isLoggable(LogRecord record) { //we also use this filter to add a warning about errors generated after shutdown if (record.getLevel().intValue() >= org.jboss.logmanager.Level.ERROR.intValue() && shutdownNotifier.shutdown) { if (!record.getMessage().endsWith(SHUTDOWN_MESSAGE)) { - record.setMessage(record.getMessage() + SHUTDOWN_MESSAGE); + if (record instanceof ExtLogRecord) { + ExtLogRecord elr = (ExtLogRecord) record; + elr.setMessage(record.getMessage() + SHUTDOWN_MESSAGE, elr.getFormatStyle()); + } else { + record.setMessage(record.getMessage() + SHUTDOWN_MESSAGE); + } } } // Only allow filtering messages of warning level and lower From e4113aa63ea04171ed4c978779945f3f52bacf73 Mon Sep 17 00:00:00 2001 From: Romain LE BARO Date: Wed, 30 Aug 2023 17:03:13 +0200 Subject: [PATCH 11/28] Allow using a servicePath with capital letters when testing with the OIDC provider test Service --- .../deployment/src/main/resources/dev-ui/qwc-oidc-provider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js b/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js index d1fbd800b7880..6ecb76ad16901 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js +++ b/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js @@ -780,7 +780,7 @@ export class QwcOidcProvider extends QwcHotReloadElement { Date: Wed, 30 Aug 2023 19:15:32 +0000 Subject: [PATCH 14/28] Bump io.smallrye.config:smallrye-config-source-yaml in /devtools/gradle Bumps io.smallrye.config:smallrye-config-source-yaml from 3.3.3 to 3.3.4. --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config-source-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- 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 8a3c70b474deb..32a3400f6b711 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -3,7 +3,7 @@ plugin-publish = "1.2.1" # updating Kotlin here makes QuarkusPluginTest > shouldNotFailOnProjectDependenciesWithoutMain(Path) fail kotlin = "1.8.10" -smallrye-config = "3.3.3" +smallrye-config = "3.3.4" junit5 = "5.10.0" assertj = "3.24.2" From 7b9cb7d25a0e5e6f313a0dc59abd42d7b79231be Mon Sep 17 00:00:00 2001 From: Marc Savy Date: Wed, 30 Aug 2023 21:20:51 +0100 Subject: [PATCH 15/28] Unwrap `AssertionError` from `InvocationTargetException` during Quarkus JUnit5 callbacks. Previously, only `RuntimeException` derived throwables were unwrapped, but many test frameworks use throwables deriving from `AssertionError`, so this should be handled also. --- .../AbstractTestWithCallbacksExtension.java | 7 ++- .../test/junit/ErrorThrowingCallback.java | 34 +++++++++++++ ...QuarkusTestCallbacksErrorHandlingTest.java | 50 +++++++++++++++++++ ...unit.callback.QuarkusTestAfterEachCallback | 1 + 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java create mode 100644 test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java create mode 100644 test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java index 2717fe0fa4a40..c48fdc5624a9e 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java @@ -158,7 +158,12 @@ private void invokeCallbacks(List callbacks, String methodName, Class .invoke(callback, classInstance); } } catch (InvocationTargetException e) { - throw e.getCause() instanceof Exception ? (Exception) e.getCause() : e; + if (e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); + } else if (e.getCause() instanceof AssertionError) { + throw (AssertionError) e.getCause(); + } + throw e; } } } diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java new file mode 100644 index 0000000000000..a87f495c8ad8d --- /dev/null +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java @@ -0,0 +1,34 @@ +package io.quarkus.test.junit; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.junit.jupiter.api.AssertionFailureBuilder; + +import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback; +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; + +/** + * Test handling of {@link AssertionError}s and {@link RuntimeException} in callbacks. + * + * @see QuarkusTestCallbacksErrorHandlingTest + */ +public class ErrorThrowingCallback implements QuarkusTestAfterEachCallback { + @Override + public void afterEach(QuarkusTestMethodContext context) { + String throwableType = System.getProperty("quarkus.test.callback.throwableType"); + + if ("AssertionError".equalsIgnoreCase(throwableType)) { + AssertionFailureBuilder + .assertionFailure() + .expected("a") + .actual("b") + .reason("Oh no, it broke! Here's an assertion error") + .buildAndThrow(); + } + + if ("RuntimeException".equalsIgnoreCase(throwableType)) { + throw new UncheckedIOException(new IOException("Oh dear, it broke again")); + } + } +} diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java new file mode 100644 index 0000000000000..c56ff9877b43f --- /dev/null +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java @@ -0,0 +1,50 @@ +package io.quarkus.test.junit; + +import java.io.UncheckedIOException; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; + +/** + * Generally, {@link AssertionError} is thrown by testing frameworks such as JUnit. However, + * it does not derive from {@link RuntimeException} hierarchy, and hence separate unwrapping of each type + * must be handled in {@link io.quarkus.test.junit.AbstractTestWithCallbacksExtension}. + *

+ * Ensuring that AssertionError is at the top of any stack track allows tooling to properly + * parse and display expected vs actual diffs, etc. + * + * @see ErrorThrowingCallback + */ +public class QuarkusTestCallbacksErrorHandlingTest { + + @Test + public void testAssertionErrorsAreUnwrappedFromCallback() { + Assertions.assertThrows(AssertionError.class, () -> { + System.setProperty("quarkus.test.callback.throwableType", "AssertionError"); + MockCallbackExtension extension = new MockCallbackExtension(); + QuarkusTestMethodContext mockContext = new QuarkusTestMethodContext(new Object(), List.of(), null, null); + extension.invokeAfterEachCallbacks(mockContext); + }); + } + + @Test + public void testRuntimeExceptionsAreUnwrappedFromCallback() { + Assertions.assertThrows(UncheckedIOException.class, () -> { + System.setProperty("quarkus.test.callback.throwableType", "RuntimeException"); + MockCallbackExtension extension = new MockCallbackExtension(); + QuarkusTestMethodContext mockContext = new QuarkusTestMethodContext(new Object(), List.of(), null, null); + extension.invokeAfterEachCallbacks(mockContext); + }); + } + + public static class MockCallbackExtension extends AbstractTestWithCallbacksExtension { + + public MockCallbackExtension() throws ClassNotFoundException { + populateCallbacks(Thread.currentThread().getContextClassLoader()); + } + + } +} diff --git a/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback b/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback new file mode 100644 index 0000000000000..833d325d836f8 --- /dev/null +++ b/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback @@ -0,0 +1 @@ +io.quarkus.test.junit.ErrorThrowingCallback From d69a95abceb4ba79d40bbfd66309fbbad487da23 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 31 Aug 2023 06:50:41 +0200 Subject: [PATCH 16/28] test: OpenShiftClient beans replace those produced by KubernetesClientProducer Signed-off-by: Marc Nuri --- .../client/deployment/BeanDefaultsTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanDefaultsTest.java diff --git a/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanDefaultsTest.java b/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanDefaultsTest.java new file mode 100644 index 0000000000000..fbfffb1f1c4a0 --- /dev/null +++ b/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanDefaultsTest.java @@ -0,0 +1,37 @@ +package io.quarkus.openshift.client.deployment; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.openshift.client.OpenShiftClient; +import io.quarkus.test.QuarkusUnitTest; + +public class BeanDefaultsTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .overrideConfigKey("quarkus.kubernetes-client.devservices.enabled", "false"); + + @Inject + KubernetesClient kubernetesClient; + + @Inject + OpenShiftClient openShiftClient; + + @Test + public void openShiftClientIsInstantiated() { + assertNotNull(openShiftClient); + } + + @Test + public void kubernetesClientIsInstantiatedAsOpenShiftClient() { + assertNotNull(kubernetesClient); + assertTrue(kubernetesClient instanceof OpenShiftClient); + } +} From 65e4451676d1134f33660e397cd2383586796ad9 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 31 Aug 2023 06:51:00 +0200 Subject: [PATCH 17/28] test: OpenShiftClient beans can be overridden with no additional config Signed-off-by: Marc Nuri --- .../client/deployment/BeanOverridesTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanOverridesTest.java diff --git a/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanOverridesTest.java b/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanOverridesTest.java new file mode 100644 index 0000000000000..58838f0133e40 --- /dev/null +++ b/extensions/openshift-client/deployment/src/test/java/io/quarkus/openshift/client/deployment/BeanOverridesTest.java @@ -0,0 +1,52 @@ +package io.quarkus.openshift.client.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.openshift.client.OpenShiftClient; +import io.quarkus.test.QuarkusUnitTest; + +public class BeanOverridesTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .overrideConfigKey("quarkus.kubernetes-client.devservices.enabled", "false"); + + @Inject + KubernetesClient kubernetesClient; + + @Inject + OpenShiftClient openShiftClient; + + @Test + public void openShiftClientCanBeOverridden() { + assertEquals("https://example.com/overridden/", openShiftClient.getConfiguration().getMasterUrl()); + } + + @Test + public void kubernetesClientCanIsOpenShiftInstance() { + assertEquals("https://example.com/overridden/", kubernetesClient.getConfiguration().getMasterUrl()); + } + + @Singleton + public static class BeanOverridesConfig { + @Produces + public OpenShiftClient openShiftClient() { + return new KubernetesClientBuilder() + .withConfig(new ConfigBuilder(Config.empty()) + .withMasterUrl("https://example.com/overridden/") + .build()) + .build().adapt(OpenShiftClient.class); + } + } +} From 9dfb5fb21c70eb17719b1793da282e916ab65c71 Mon Sep 17 00:00:00 2001 From: Fouad Almalki Date: Thu, 31 Aug 2023 08:55:59 +0300 Subject: [PATCH 18/28] Bump Wiremock to 3.0.0 --- .github/dependabot.yml | 3 ++- build-parent/pom.xml | 14 +++++++------- docs/src/main/asciidoc/rest-client.adoc | 10 +++++----- docs/src/main/asciidoc/security-oauth2.adoc | 10 +++++----- .../security-openid-connect-client-reference.adoc | 4 ++-- .../rest-client-reactive/deployment/pom.xml | 4 ++-- .../spring-cloud-config-client/runtime/pom.xml | 4 ++-- .../tools/analytics-common/pom.xml | 8 ++++---- integration-tests/elytron-security-oauth2/pom.xml | 4 ++-- integration-tests/gradle/pom.xml | 6 +++--- integration-tests/oidc-client-wiremock/pom.xml | 4 ++-- integration-tests/opentelemetry-reactive/pom.xml | 4 ++-- .../rest-client-reactive-stork/pom.xml | 4 ++-- integration-tests/rest-client-reactive/pom.xml | 4 ++-- .../virtual-threads/amqp-virtual-threads/pom.xml | 4 ++-- .../virtual-threads/jms-virtual-threads/pom.xml | 4 ++-- .../virtual-threads/kafka-virtual-threads/pom.xml | 4 ++-- test-framework/oidc-server/pom.xml | 4 ++-- .../test/oidc/server/OidcWiremockTestResource.java | 12 ++++++++---- 19 files changed, 58 insertions(+), 53 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 01d16b19ceb59..41f7c311c5f6a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -88,7 +88,8 @@ updates: # Agroal - dependency-name: io.agroal:* # WireMock - - dependency-name: com.github.tomakehurst:wiremock-jre8-standalone + - dependency-name: org.wiremock:wiremock + - dependency-name: org.wiremock:wiremock-standalone - dependency-name: uk.co.automatictester:wiremock-maven-plugin # Picocli - dependency-name: info.picocli:* diff --git a/build-parent/pom.xml b/build-parent/pom.xml index f2a0744857fdb..975f50b66cc91 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -110,7 +110,7 @@ 3.24.2 - 2.35.0 + 3.0.0 7.3.0 @@ -262,9 +262,9 @@ ${unboundid-ldap.version} - com.github.tomakehurst - wiremock-jre8-standalone - ${wiremock-jre8.version} + org.wiremock + wiremock-standalone + ${wiremock.version} io.quarkus @@ -689,9 +689,9 @@ ${wiremock-maven-plugin.version} - com.github.tomakehurst - wiremock-jre8-standalone - ${wiremock-jre8.version} + org.wiremock + wiremock-standalone + ${wiremock.version} diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index f629c87d24939..bfdfb027ec1c0 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -669,20 +669,20 @@ First, Wiremock needs to be added as a test dependency. For a Maven project that .pom.xml ---- - com.github.tomakehurst - wiremock-jre8 + org.wiremock + wiremock test ${wiremock.version} <1> ---- -<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/com.github.tomakehurst/wiremock-jre8[here]. +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -testImplementation("com.github.tomakehurst:wiremock-jre8:$wiremockVersion") <1> +testImplementation("org.wiremock:wiremock:$wiremockVersion") <1> ---- -<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/com.github.tomakehurst/wiremock-jre8[here]. +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. In Quarkus tests when some service needs to be started before the Quarkus tests are ran, we utilize the `@io.quarkus.test.common.QuarkusTestResource` annotation to specify a `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` which can start the service and supply configuration diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index c2e4e5b81fd87..91dbb82a9cc5a 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -332,20 +332,20 @@ First, Wiremock needs to be added as a test dependency. For a Maven project that .pom.xml ---- - com.github.tomakehurst - wiremock-jre8 + org.wiremock + wiremock test ${wiremock.version} // <1> ---- -<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/com.github.tomakehurst/wiremock-jre8[here]. +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -testImplementation("com.github.tomakehurst:wiremock-jre8:${wiremock.version}") <1> +testImplementation("org.wiremock:wiremock:${wiremock.version}") <1> ---- -<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/com.github.tomakehurst/wiremock-jre8[here]. +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. In Quarkus tests when some service needs to be started before the Quarkus tests are ran, we utilize the `@io.quarkus.test.common.QuarkusTestResource` annotation to specify a `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` which can start the service and supply configuration diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index c06de7492625f..0363757984663 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -766,8 +766,8 @@ Add the following dependencies to your test project: [source,xml] ---- - com.github.tomakehurst - wiremock-jre8 + org.wiremock + wiremock test ---- diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml b/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml index 3d48dda2d0c4c..01308c8933fd8 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/pom.xml @@ -69,8 +69,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/extensions/spring-cloud-config-client/runtime/pom.xml b/extensions/spring-cloud-config-client/runtime/pom.xml index ec47f9fb1fc03..3ce10e3d3c56f 100644 --- a/extensions/spring-cloud-config-client/runtime/pom.xml +++ b/extensions/spring-cloud-config-client/runtime/pom.xml @@ -51,8 +51,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/independent-projects/tools/analytics-common/pom.xml b/independent-projects/tools/analytics-common/pom.xml index dde098bbf5f0c..3767b7b09bf4e 100644 --- a/independent-projects/tools/analytics-common/pom.xml +++ b/independent-projects/tools/analytics-common/pom.xml @@ -16,7 +16,7 @@ 3.3.1 4.5.14 - 2.35.0 + 3.0.0 1.0.0.Final @@ -45,9 +45,9 @@ test - com.github.tomakehurst - wiremock-jre8-standalone - ${wiremock-jre8.version} + org.wiremock + wiremock-standalone + ${wiremock.version} test diff --git a/integration-tests/elytron-security-oauth2/pom.xml b/integration-tests/elytron-security-oauth2/pom.xml index 297bde2840d48..662fd798c9328 100644 --- a/integration-tests/elytron-security-oauth2/pom.xml +++ b/integration-tests/elytron-security-oauth2/pom.xml @@ -36,8 +36,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml index 673cdaa35ac3a..c434a33d00579 100644 --- a/integration-tests/gradle/pom.xml +++ b/integration-tests/gradle/pom.xml @@ -52,9 +52,9 @@ quarkus-devmode-test-utils - com.github.tomakehurst - wiremock-jre8-standalone - ${wiremock-jre8.version} + org.wiremock + wiremock-standalone + ${wiremock.version} commons-logging diff --git a/integration-tests/oidc-client-wiremock/pom.xml b/integration-tests/oidc-client-wiremock/pom.xml index b28120d892680..629c610be3376 100644 --- a/integration-tests/oidc-client-wiremock/pom.xml +++ b/integration-tests/oidc-client-wiremock/pom.xml @@ -22,8 +22,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/opentelemetry-reactive/pom.xml b/integration-tests/opentelemetry-reactive/pom.xml index 460516176fcd3..51ae9f692b532 100644 --- a/integration-tests/opentelemetry-reactive/pom.xml +++ b/integration-tests/opentelemetry-reactive/pom.xml @@ -64,8 +64,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/rest-client-reactive-stork/pom.xml b/integration-tests/rest-client-reactive-stork/pom.xml index a2bfe30102652..90f50aadf71f5 100644 --- a/integration-tests/rest-client-reactive-stork/pom.xml +++ b/integration-tests/rest-client-reactive-stork/pom.xml @@ -27,8 +27,8 @@ quarkus-resteasy-reactive-jackson - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml index 55c61d25554a1..05fc6fb2cf4c5 100644 --- a/integration-tests/rest-client-reactive/pom.xml +++ b/integration-tests/rest-client-reactive/pom.xml @@ -80,8 +80,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/virtual-threads/amqp-virtual-threads/pom.xml b/integration-tests/virtual-threads/amqp-virtual-threads/pom.xml index 077972f0c96c7..d05b0088e1546 100644 --- a/integration-tests/virtual-threads/amqp-virtual-threads/pom.xml +++ b/integration-tests/virtual-threads/amqp-virtual-threads/pom.xml @@ -58,8 +58,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/virtual-threads/jms-virtual-threads/pom.xml b/integration-tests/virtual-threads/jms-virtual-threads/pom.xml index 2a3bb4d4725c2..f31f186aac14d 100644 --- a/integration-tests/virtual-threads/jms-virtual-threads/pom.xml +++ b/integration-tests/virtual-threads/jms-virtual-threads/pom.xml @@ -83,8 +83,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/integration-tests/virtual-threads/kafka-virtual-threads/pom.xml b/integration-tests/virtual-threads/kafka-virtual-threads/pom.xml index a2c0d1f7fc8f2..a9fac7750e8ab 100644 --- a/integration-tests/virtual-threads/kafka-virtual-threads/pom.xml +++ b/integration-tests/virtual-threads/kafka-virtual-threads/pom.xml @@ -58,8 +58,8 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/test-framework/oidc-server/pom.xml b/test-framework/oidc-server/pom.xml index df88289506742..84b923627046f 100644 --- a/test-framework/oidc-server/pom.xml +++ b/test-framework/oidc-server/pom.xml @@ -14,8 +14,8 @@ Quarkus - Test Framework - OIDC Wiremock Server support - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone jakarta.servlet diff --git a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java index 4af6425fae550..f6f695e0c490c 100644 --- a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java +++ b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java @@ -12,6 +12,7 @@ import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -25,7 +26,9 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; import com.google.common.collect.ImmutableSet; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; @@ -52,10 +55,11 @@ public class OidcWiremockTestResource implements QuarkusTestResourceLifecycleMan @Override public Map start() { - server = new WireMockServer( - wireMockConfig() - .extensions(new ResponseTemplateTransformer(false)) - .dynamicPort()); + WireMockConfiguration wireMockConfiguration = wireMockConfig(); + server = new WireMockServer(wireMockConfiguration + .extensions(new ResponseTemplateTransformer(TemplateEngine.defaultTemplateEngine(), false, + wireMockConfiguration.filesRoot(), Collections.emptyList())) + .dynamicPort()); server.start(); server.stubFor( From 3b3f0f05131191e8536b8bbc1691473707c06c1b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 10:38:09 +0300 Subject: [PATCH 19/28] Properly support @JsonView on Resource class Fixes: #35639 --- .../ResteasyReactiveJacksonProcessor.java | 2 +- .../deployment/test/JsonViewOnClassTest.java | 81 +++++++++++++++++++ .../deployment/test/SimpleJsonResource.java | 15 +--- .../jackson/deployment/test/User.java | 7 ++ ...ResteasyReactiveServerJacksonRecorder.java | 8 +- ...eaturedServerJacksonMessageBodyReader.java | 7 ++ ...eaturedServerJacksonMessageBodyWriter.java | 7 ++ 7 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/JsonViewOnClassTest.java 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 564b1ed477e7a..74f4cc444bc45 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 @@ -237,7 +237,7 @@ void handleJsonAnnotations(Optional resourceSca if ((jsonViews == null) || (jsonViews.length == 0)) { continue; } - recorder.recordJsonView(getMethodId(instance.target().asMethod()), jsonViews[0].name().toString()); + recorder.recordJsonView(getTargetId(instance.target()), jsonViews[0].name().toString()); } } if (resourceClass.annotationsMap().containsKey(CUSTOM_SERIALIZATION)) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/JsonViewOnClassTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/JsonViewOnClassTest.java new file mode 100644 index 0000000000000..a57668d4aef4c --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/JsonViewOnClassTest.java @@ -0,0 +1,81 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +import java.util.function.Supplier; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.fasterxml.jackson.annotation.JsonView; + +import io.quarkus.test.QuarkusUnitTest; + +class JsonViewOnClassTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(User.class, Views.class, Public.class, Private.class, Mixed.class); + } + }); + + @Test + void test() { + given().accept("application/json").get("public") + .then() + .statusCode(200) + .body(not(containsString("1")), containsString("test")); + + given().accept("application/json").get("mixed") + .then() + .statusCode(200) + .body(containsString("1"), containsString("test")); + + given().accept("application/json").get("private") + .then() + .statusCode(200) + .body(containsString("1"), containsString("test")); + } + + @JsonView(Views.Public.class) + @Path("public") + public static class Public { + + @GET + public User get() { + return User.testUser(); + } + } + + @JsonView(Views.Private.class) + @Path("private") + public static class Private { + + @GET + public User get() { + return User.testUser(); + } + } + + @JsonView(Views.Public.class) + @Path("mixed") + public static class Mixed { + + @GET + @JsonView(Views.Private.class) + public User get() { + return User.testUser(); + } + } +} 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 8e5f215debb6b..b538997b62f25 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 @@ -255,35 +255,28 @@ public void run() { @GET @Path("/user-without-view") public User userWithoutView() { - return testUser(); + return User.testUser(); } @JsonView(Views.Public.class) @GET @Path("/user-with-public-view") public User userWithPublicView() { - return testUser(); + return User.testUser(); } @JsonView(Views.Private.class) @GET @Path("/user-with-private-view") public User userWithPrivateView() { - return testUser(); + return User.testUser(); } @CustomSerialization(UnquotedFieldsPersonSerialization.class) @GET @Path("/invalid-use-of-custom-serializer") public User invalidUseOfCustomSerializer() { - return testUser(); - } - - private User testUser() { - User user = new User(); - user.id = 1; - user.name = "test"; - return user; + return User.testUser(); } @GET diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/User.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/User.java index 795da2ebfd2b4..f588592f23b3c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/User.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/User.java @@ -9,4 +9,11 @@ public class User { @JsonView(Views.Public.class) public String name; + + public static User testUser() { + User user = new User(); + user.id = 1; + user.name = "test"; + return user; + } } 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 eed5cd09521f6..2e86508b9f498 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 @@ -19,8 +19,8 @@ public class ResteasyReactiveServerJacksonRecorder { private static final Map> customSerializationMap = new HashMap<>(); private static final Map> customDeserializationMap = new HashMap<>(); - public void recordJsonView(String methodId, String className) { - jsonViewMap.put(methodId, loadClass(className)); + public void recordJsonView(String targetId, String className) { + jsonViewMap.put(targetId, loadClass(className)); } public void recordCustomSerialization(String target, String className) { @@ -42,6 +42,10 @@ public void run() { }); } + public static Class jsonViewForClass(Class clazz) { + return jsonViewMap.get(clazz.getName()); + } + public static Class jsonViewForMethod(String methodId) { return jsonViewMap.get(methodId); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java index dab95f4f402be..89dbe47c6d36a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyReader.java @@ -175,6 +175,13 @@ public ObjectReader apply(ObjectMapper objectMapper) { Class jsonViewValue = ResteasyReactiveServerJacksonRecorder.jsonViewForMethod(resourceInfo.getMethodId()); if (jsonViewValue != null) { return effectiveReader.withView(jsonViewValue); + } else { + jsonViewValue = ResteasyReactiveServerJacksonRecorder + .jsonViewForClass(resourceInfo.getResourceClass()); + if (jsonViewValue != null) { + return effectiveReader.withView(jsonViewValue); + } + } } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java index abe3896f9a33d..171b6843fa62d 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java @@ -66,6 +66,13 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte Class jsonViewValue = ResteasyReactiveServerJacksonRecorder.jsonViewForMethod(resourceInfo.getMethodId()); if (jsonViewValue != null) { effectiveWriter = effectiveWriter.withView(jsonViewValue); + } else { + jsonViewValue = ResteasyReactiveServerJacksonRecorder + .jsonViewForClass(resourceInfo.getResourceClass()); + if (jsonViewValue != null) { + effectiveWriter = effectiveWriter.withView(jsonViewValue); + } + } } effectiveWriter.writeValue(stream, o); From a9ddd130bfec9d029d3ab8378a502a7f69cb6051 Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Wed, 30 Aug 2023 10:56:58 +0100 Subject: [PATCH 20/28] Fix doc link asciidoc change link to xref where applicable --- docs/src/main/asciidoc/security-architecture.adoc | 4 ++-- .../src/main/asciidoc/security-authentication-mechanisms.adoc | 4 ++-- .../main/asciidoc/security-basic-authentication-howto.adoc | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/security-architecture.adoc b/docs/src/main/asciidoc/security-architecture.adoc index f74b2e68a47a2..ef3bcebd2dffa 100644 --- a/docs/src/main/asciidoc/security-architecture.adoc +++ b/docs/src/main/asciidoc/security-architecture.adoc @@ -73,11 +73,11 @@ You can customize the following core security components of Quarkus: * `IdentityProvider` * `SecurityidentityAugmentor` -For more information about customizing Quarkus Security, including reactive security and how to register a security provider, see the Quarkus link:{url-quarkusio-guides}security-customization[Security tips and tricks] guide. +For more information about customizing Quarkus Security, including reactive security and how to register a security provider, see the Quarkus xref:security-customization.adoc[Security tips and tricks] guide. == References * xref:security-overview.adoc[Quarkus Security overview] * xref:security-authentication-mechanisms.adoc#other-supported-authentication-mechanisms[Other supported authentication mechanisms] * xref:security-identity-providers.adoc[Identity providers] -* xref:security-authorize-web-endpoints-reference.adoc[Authorization of web endpoints] \ No newline at end of file +* xref:security-authorize-web-endpoints-reference.adoc[Authorization of web endpoints] diff --git a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc index 5f10459eb9091..0ccbb5e426867 100644 --- a/docs/src/main/asciidoc/security-authentication-mechanisms.adoc +++ b/docs/src/main/asciidoc/security-authentication-mechanisms.adoc @@ -150,7 +150,7 @@ X509Certificate certificate = credential.getCertificate(); The information from the client certificate can be used to enhance Quarkus `SecurityIdentity`. For example, you can add new roles after checking a client certificate subject name, and so on. -For more information about customizing `SecurityIdentity`, see the link:{url-quarkusio-guides}security-customization#security-identity-customization[Security identity customization] section in the Quarkus "Security tips and tricks" guide. +For more information about customizing `SecurityIdentity`, see the xref:security-customization.adoc#security-identity-customization[Security identity customization] section in the Quarkus "Security tips and tricks" guide. [[other-supported-authentication-mechanisms]] == Other supported authentication mechanisms @@ -209,7 +209,7 @@ For more information about OIDC authentication and authorization methods that yo To enable the Quarkus OIDC extension at runtime, set `quarkus.oidc.tenant-enabled=false` at build time. Then re-enable it at runtime by using a system property. -For more information about managing the individual tenant configurations in multitenant OIDC deployments, see the link:{url-quarkusio-guides}security-openid-connect-multitenancy#disable-tenant[Disabling tenant configurations] section in the "Using OpenID Connect (OIDC) multi-tenancy" guide. +For more information about managing the individual tenant configurations in multitenant OIDC deployments, see the xref:security-openid-connect-multitenancy.adoc#disable-tenant[Disabling tenant configurations] section in the "Using OpenID Connect (OIDC) multi-tenancy" guide. ==== ==== OpenID Connect client and filters diff --git a/docs/src/main/asciidoc/security-basic-authentication-howto.adoc b/docs/src/main/asciidoc/security-basic-authentication-howto.adoc index b58298fa1a9f2..2f620a217c871 100644 --- a/docs/src/main/asciidoc/security-basic-authentication-howto.adoc +++ b/docs/src/main/asciidoc/security-basic-authentication-howto.adoc @@ -8,7 +8,7 @@ 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 link:{url-quarkusio-guides}security-jdbc[Elytron JDBC]. +* You have installed at least one extension that provides an `IdentityProvider` based on username and password, such as xref:security-jdbc.adoc[Elytron JDBC]. == Procedure From a6c87e906e7c1b485af665e77631fd32cff9e034 Mon Sep 17 00:00:00 2001 From: domkun <1139208+domkun@users.noreply.github.com> Date: Fri, 1 Sep 2023 08:07:37 +0200 Subject: [PATCH 21/28] Fix custom codec registration for more than one codec --- .../client/datasource/CustomCodecTest.java | 90 ++++++++++++++----- .../runtime/client/RedisClientRecorder.java | 5 +- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/CustomCodecTest.java b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/CustomCodecTest.java index cc5e86c6d0c8b..39b9bd89e77de 100644 --- a/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/CustomCodecTest.java +++ b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/CustomCodecTest.java @@ -28,7 +28,8 @@ public class CustomCodecTest { @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() .setArchiveProducer( - () -> ShrinkWrap.create(JavaArchive.class).addClass(Jedi.class).addClass(MyCustomCodec.class)) + () -> ShrinkWrap.create(JavaArchive.class).addClass(Jedi.class).addClass(Sith.class) + .addClass(CustomJediCodec.class).addClass(CustomSithCodec.class)) .overrideConfigKey("quarkus.redis.hosts", "${quarkus.redis.tr}"); @Inject @@ -37,28 +38,45 @@ public class CustomCodecTest { @Test void testCustomCodecs() { String key1 = UUID.randomUUID().toString(); - // Check that the codec is registered - assertThat(Codecs.getDefaultCodecFor(Jedi.class)).isInstanceOf(MyCustomCodec.class); - - HashCommands hash1 = ds.hash(Jedi.class); - hash1.hset(key1, "test", new Jedi("luke", "skywalker")); - var retrieved = hash1.hget(key1, "test"); - assertThat(retrieved.firstName).isEqualTo("luke"); - assertThat(retrieved.lastName).isEqualTo("SKYWALKER"); - - HashCommands hash2 = ds.hash(String.class, Jedi.class, String.class); - hash2.hset(key1, new Jedi("luke", "skywalker"), "test"); - var retrieved2 = hash2.hget(key1, new Jedi("luke", "skywalker")); - assertThat(retrieved2).isEqualTo("test"); - - HashCommands hash3 = ds.hash(Jedi.class, String.class, String.class); - hash3.hset(new Jedi("luke", "skywalker"), "key", "value"); - var retrieved3 = hash3.hget(new Jedi("luke", "skywalker"), "key"); - assertThat(retrieved3).isEqualTo("value"); + // Check that all codec are registered + assertThat(Codecs.getDefaultCodecFor(Jedi.class)).isInstanceOf(CustomJediCodec.class); + assertThat(Codecs.getDefaultCodecFor(Sith.class)).isInstanceOf(CustomSithCodec.class); + + HashCommands jediHash1 = ds.hash(Jedi.class); + jediHash1.hset(key1, "test", new Jedi("luke", "skywalker")); + var jediRetrieved = jediHash1.hget(key1, "test"); + assertThat(jediRetrieved.firstName).isEqualTo("luke"); + assertThat(jediRetrieved.lastName).isEqualTo("SKYWALKER"); + + HashCommands jediHash2 = ds.hash(String.class, Jedi.class, String.class); + jediHash2.hset(key1, new Jedi("luke", "skywalker"), "test"); + var jediRetrieved2 = jediHash2.hget(key1, new Jedi("luke", "skywalker")); + assertThat(jediRetrieved2).isEqualTo("test"); + + HashCommands jediHash3 = ds.hash(Jedi.class, String.class, String.class); + jediHash3.hset(new Jedi("luke", "skywalker"), "key", "value"); + var jediRetrieved3 = jediHash3.hget(new Jedi("luke", "skywalker"), "key"); + assertThat(jediRetrieved3).isEqualTo("value"); + + HashCommands sithHash1 = ds.hash(Sith.class); + sithHash1.hset(key1, "test", new Sith("darth", "sidious")); + var sithRetrieved = sithHash1.hget(key1, "test"); + assertThat(sithRetrieved.firstName).isEqualTo("darth"); + assertThat(sithRetrieved.lastName).isEqualTo("SIDIOUS"); + + HashCommands sithHash2 = ds.hash(String.class, Sith.class, String.class); + sithHash2.hset(key1, new Sith("darth", "sidious"), "test"); + var sithRetrieved2 = sithHash2.hget(key1, new Sith("darth", "sidious")); + assertThat(sithRetrieved2).isEqualTo("test"); + + HashCommands sithHash3 = ds.hash(Sith.class, String.class, String.class); + sithHash3.hset(new Sith("darth", "sidious"), "key", "value"); + var sithRetrieved3 = sithHash3.hget(new Sith("darth", "sidious"), "key"); + assertThat(sithRetrieved3).isEqualTo("value"); } @ApplicationScoped - public static class MyCustomCodec implements Codec { + public static class CustomJediCodec implements Codec { @Override public boolean canHandle(Type clazz) { @@ -79,6 +97,28 @@ public Object decode(byte[] item) { } } + @ApplicationScoped + public static class CustomSithCodec implements Codec { + + @Override + public boolean canHandle(Type clazz) { + return clazz.equals(Sith.class); + } + + @Override + public byte[] encode(Object item) { + var sith = (Sith) item; + return (sith.firstName + ";" + sith.lastName).getBytes(StandardCharsets.UTF_8); + } + + @Override + public Object decode(byte[] item) { + String s = new String(item, StandardCharsets.UTF_8); + String[] strings = s.split(";"); + return new Sith(strings[0], strings[1].toUpperCase()); + } + } + public static class Jedi { public final String firstName; public final String lastName; @@ -89,4 +129,14 @@ public Jedi(String firstName, String lastName) { } } + public static class Sith { + public final String firstName; + public final String lastName; + + public Sith(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + } + } diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java index 1060772089656..cec469fc5d52d 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/RedisClientRecorder.java @@ -65,9 +65,8 @@ public void initialize(RuntimeValue vertx, Set name private static void _registerCodecs() { Instance codecs = CDI.current().select(Codec.class); - if (codecs.isResolvable()) { - Codecs.register(codecs.stream()); - } + + Codecs.register(codecs.stream()); } public void _initialize(io.vertx.core.Vertx vertx, Set names) { From c9516292cbb2bf69d93d2c63fc1bc775a00d6a97 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Thu, 24 Aug 2023 16:50:36 +0200 Subject: [PATCH 22/28] Integrate @RunOnVirtualThread with the Quarkus scheduler (quarkus-scheduler and Quartz) Allows @Scheduled methods to run on a virtual thread. Also extend the programmatic API to allow defining jobs that will run on virtual thread if the JVM offers this possibility. --- .github/virtual-threads-tests.json | 4 +- docs/src/main/asciidoc/quartz.adoc | 12 +++ .../main/asciidoc/scheduler-reference.adoc | 8 ++ .../quartz/runtime/QuartzSchedulerImpl.java | 47 +++++++--- .../java/io/quarkus/scheduler/Scheduler.java | 13 ++- .../common/runtime/AbstractJobDefinition.java | 4 +- .../common/runtime/DelegateInvoker.java | 4 + .../common/runtime/ScheduledInvoker.java | 11 ++- extensions/scheduler/deployment/pom.xml | 4 + .../ScheduledBusinessMethodItem.java | 10 ++- .../deployment/SchedulerDotNames.java | 3 + .../deployment/SchedulerProcessor.java | 18 +++- extensions/scheduler/runtime/pom.xml | 4 + .../scheduler/runtime/SimpleScheduler.java | 40 +++++++-- integration-tests/virtual-threads/pom.xml | 2 + .../quartz-virtual-threads/pom.xml | 89 +++++++++++++++++++ .../virtual/scheduler/AssertHelper.java | 71 +++++++++++++++ .../virtual/scheduler/ScheduledResource.java | 50 +++++++++++ .../src/main/resources/application.properties | 3 + .../quarkus/virtual/mail/NoPinningVerify.java | 76 ++++++++++++++++ .../virtual/mail/RunOnVirtualThreadIT.java | 8 ++ .../virtual/mail/RunOnVirtualThreadTest.java | 43 +++++++++ .../scheduler-virtual-threads/pom.xml | 89 +++++++++++++++++++ .../virtual/scheduler/AssertHelper.java | 71 +++++++++++++++ .../virtual/scheduler/ScheduledResource.java | 50 +++++++++++ .../src/main/resources/application.properties | 3 + .../quarkus/virtual/mail/NoPinningVerify.java | 76 ++++++++++++++++ .../virtual/mail/RunOnVirtualThreadIT.java | 8 ++ .../virtual/mail/RunOnVirtualThreadTest.java | 43 +++++++++ 29 files changed, 836 insertions(+), 28 deletions(-) create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/pom.xml create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/main/resources/application.properties create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java create mode 100644 integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/pom.xml create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/main/resources/application.properties create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java create mode 100644 integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java diff --git a/.github/virtual-threads-tests.json b/.github/virtual-threads-tests.json index 7230a628bf3c9..30b0dbe464441 100644 --- a/.github/virtual-threads-tests.json +++ b/.github/virtual-threads-tests.json @@ -2,8 +2,8 @@ "include": [ { "category": "Main", - "timeout": 45, - "test-modules": "grpc-virtual-threads, mailer-virtual-threads, redis-virtual-threads, rest-client-reactive-virtual-threads, resteasy-reactive-virtual-threads, vertx-event-bus-virtual-threads", + "timeout": 50, + "test-modules": "grpc-virtual-threads, mailer-virtual-threads, redis-virtual-threads, rest-client-reactive-virtual-threads, resteasy-reactive-virtual-threads, vertx-event-bus-virtual-threads, scheduler-virtual-threads, quartz-virtual-threads", "os-name": "ubuntu-latest" }, { diff --git a/docs/src/main/asciidoc/quartz.adoc b/docs/src/main/asciidoc/quartz.adoc index 5f8182ac9b96c..7bb4f8b913ef7 100644 --- a/docs/src/main/asciidoc/quartz.adoc +++ b/docs/src/main/asciidoc/quartz.adoc @@ -464,6 +464,18 @@ public class MyListenerManager { } ---- +[[virtual-threads]] +== Run scheduled methods on virtual threads + +Methods annotated with `@Scheduled` can also be annotated with `@RunOnVirtualThread`. +In this case, the method is invoked on a virtual thread. + +The method must return `void` and your Java runtime must provide support for virtual threads. +Read xref:./virtual-threads.adoc[the virtual thread guide] for more details. + +WARNING: This feature cannot be combined with the `run-blocking-method-on-quartz-thread` option. +If `run-blocking-method-on-quartz-thread` is set, the scheduled method runs on a (platform) thread managed by Quartz. + [[quartz-configuration-reference]] == Quartz Configuration Reference diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc index 93ea045bf50bd..f32e6f534d0e0 100644 --- a/docs/src/main/asciidoc/scheduler-reference.adoc +++ b/docs/src/main/asciidoc/scheduler-reference.adoc @@ -417,6 +417,14 @@ If the xref:smallrye-metrics.adoc[SmallRye Metrics extension] is present, then a If `quarkus.scheduler.tracing.enabled` is set to `true` and the xref:opentelemetry.adoc[OpenTelemetry extension] is present then the `@io.opentelemetry.instrumentation.annotations.WithSpan` annotation is added automatically to every `@Scheduled` method. As a result, each execution of this method has a new `io.opentelemetry.api.trace.Span` associated. +== Run @Scheduled methods on virtual threads + +Methods annotated with `@Scheduled` can also be annotated with `@RunOnVirtualThread`. +In this case, the method is invoked on a virtual thread. + +The method must return `void` and your Java runtime must provide support for virtual threads. +Read xref:./virtual-threads.adoc[the virtual thread guide] for more details. + == Configuration Reference include::{generated-dir}/config/quarkus-scheduler.adoc[leveloffset=+1, opts=optional] diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java index 0490e6f68ef1b..bbd7c36e9adc3 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSchedulerImpl.java @@ -88,6 +88,7 @@ import io.quarkus.scheduler.runtime.SchedulerRuntimeConfig.StartMode; import io.quarkus.scheduler.runtime.SimpleScheduler; import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle; +import io.quarkus.virtual.threads.VirtualThreadsRecorder; import io.smallrye.common.vertx.VertxContext; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -782,6 +783,11 @@ public CompletionStage invokeBean(ScheduledExecution execution) { return CompletableFuture.failedStage(e); } } + + @Override + public boolean isRunningOnVirtualThread() { + return runOnVirtualThread; + } }; } else { invoker = new DefaultInvoker() { @@ -868,17 +874,38 @@ public void execute(JobExecutionContext jobExecutionContext) throws JobExecution } else { Context context = VertxContext.getOrCreateDuplicatedContext(vertx); VertxContextSafetyToggle.setContextSafe(context, true); - context.executeBlocking(new Handler>() { - @Override - public void handle(Promise p) { - try { - trigger.invoker.invoke(new QuartzScheduledExecution(trigger, jobExecutionContext)); - p.complete(); - } catch (Exception e) { - p.tryFail(e); + if (trigger.invoker.isRunningOnVirtualThread()) { + // While counter-intuitive, we switch to a safe context, so that context is captured and attached + // to the virtual thread. + context.runOnContext(new Handler() { + @Override + public void handle(Void event) { + VirtualThreadsRecorder.getCurrent().execute(new Runnable() { + @Override + public void run() { + try { + trigger.invoker + .invoke(new QuartzScheduledExecution(trigger, jobExecutionContext)); + } catch (Exception ignored) { + // already logged by the StatusEmitterInvoker + } + } + }); } - } - }, false); + }); + } else { + context.executeBlocking(new Handler>() { + @Override + public void handle(Promise p) { + try { + trigger.invoker.invoke(new QuartzScheduledExecution(trigger, jobExecutionContext)); + p.complete(); + } catch (Exception e) { + p.tryFail(e); + } + } + }, false); + } } } else { Context context = VertxContext.getOrCreateDuplicatedContext(vertx); diff --git a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Scheduler.java b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Scheduler.java index 861123f297b08..86a506b94d108 100644 --- a/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Scheduler.java +++ b/extensions/scheduler/api/src/main/java/io/quarkus/scheduler/Scheduler.java @@ -168,7 +168,18 @@ interface JobDefinition { * @param task * @return self */ - JobDefinition setTask(Consumer task); + default JobDefinition setTask(Consumer task) { + return setTask(task, false); + } + + /** + * Configures the task to schedule. + * + * @param task the task, must not be {@code null} + * @param runOnVirtualThread whether the task must be run on a virtual thread if the JVM allows it. + * @return self the current job definition + */ + JobDefinition setTask(Consumer task, boolean runOnVirtualThread); /** * diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/AbstractJobDefinition.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/AbstractJobDefinition.java index 257288d5d649e..5b192d6c4305b 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/AbstractJobDefinition.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/AbstractJobDefinition.java @@ -23,6 +23,7 @@ public abstract class AbstractJobDefinition implements JobDefinition { protected Function> asyncTask; protected boolean scheduled = false; protected String timeZone = Scheduled.DEFAULT_TIMEZONE; + protected boolean runOnVirtualThread; public AbstractJobDefinition(String identity) { this.identity = identity; @@ -78,12 +79,13 @@ public JobDefinition setTimeZone(String timeZone) { } @Override - public JobDefinition setTask(Consumer task) { + public JobDefinition setTask(Consumer task, boolean runOnVirtualThread) { checkScheduled(); if (asyncTask != null) { throw new IllegalStateException("Async task was already set"); } this.task = task; + this.runOnVirtualThread = runOnVirtualThread; return this; } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java index 36ca1d9c66fbd..ff7571ab10352 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java @@ -13,4 +13,8 @@ public boolean isBlocking() { return delegate.isBlocking(); } + @Override + public boolean isRunningOnVirtualThread() { + return delegate.isRunningOnVirtualThread(); + } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java index 16e9cee3d5e71..a7f1f6a80e702 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java @@ -10,7 +10,6 @@ public interface ScheduledInvoker { /** - * * @param execution * @return the result * @throws Exception @@ -27,4 +26,14 @@ default boolean isBlocking() { return true; } + /** + * Indicates that the invoker used the virtual thread executor to execute the tasks. + * Note that the method must use a synchronous signature. + * + * @return {@code true} if the scheduled method runs on a virtual thread. + */ + default boolean isRunningOnVirtualThread() { + return false; + } + } diff --git a/extensions/scheduler/deployment/pom.xml b/extensions/scheduler/deployment/pom.xml index 18123b3c006d6..8271a16616d4a 100644 --- a/extensions/scheduler/deployment/pom.xml +++ b/extensions/scheduler/deployment/pom.xml @@ -18,6 +18,10 @@ io.quarkus quarkus-arc-deployment + + io.quarkus + quarkus-virtual-threads-deployment + io.quarkus quarkus-vertx-deployment diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java index b303fc18369e0..65f34de4c12db 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java @@ -14,18 +14,20 @@ public final class ScheduledBusinessMethodItem extends MultiBuildItem { private final List schedules; private final MethodInfo method; private final boolean nonBlocking; + private final boolean runOnVirtualThread; public ScheduledBusinessMethodItem(BeanInfo bean, MethodInfo method, List schedules) { - this(bean, method, schedules, false); + this(bean, method, schedules, false, false); } public ScheduledBusinessMethodItem(BeanInfo bean, MethodInfo method, List schedules, - boolean hasNonBlockingAnnotation) { + boolean hasNonBlockingAnnotation, boolean hasRunOnVirtualThreadAnnotation) { this.bean = bean; this.method = method; this.schedules = schedules; this.nonBlocking = hasNonBlockingAnnotation || SchedulerDotNames.COMPLETION_STAGE.equals(method.returnType().name()) || SchedulerDotNames.UNI.equals(method.returnType().name()) || KotlinUtil.isSuspendMethod(method); + this.runOnVirtualThread = hasRunOnVirtualThreadAnnotation; } /** @@ -48,6 +50,10 @@ public boolean isNonBlocking() { return nonBlocking; } + public boolean isRunOnVirtualThread() { + return runOnVirtualThread; + } + public String getMethodDescription() { return method.declaringClass().name() + "#" + method.name() + "()"; } diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerDotNames.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerDotNames.java index 639d1247c25fa..c1fedd5f1cb37 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerDotNames.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerDotNames.java @@ -6,6 +6,7 @@ import io.quarkus.scheduler.Scheduled; import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.common.annotation.RunOnVirtualThread; class SchedulerDotNames { @@ -23,4 +24,6 @@ class SchedulerDotNames { static final DotName ABSTRACT_COROUTINE_INVOKER = DotName .createSimple("io.quarkus.scheduler.kotlin.runtime.AbstractCoroutineInvoker"); + static final DotName RUN_ON_VIRTUAL_THREAD = DotName.createSimple(RunOnVirtualThread.class); + } 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 068f4b21748dc..60196c55e4190 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 @@ -136,7 +136,8 @@ void collectScheduledMethods(BeanArchiveIndexBuildItem beanArchives, BeanDiscove MethodInfo method = annotationInstance.target().asMethod(); if (Modifier.isStatic(method.flags()) && !KotlinUtil.isSuspendMethod(method)) { scheduledBusinessMethods.produce(new ScheduledBusinessMethodItem(null, method, schedules, - transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING))); + transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING), + transformedAnnotations.hasAnnotation(method, SchedulerDotNames.RUN_ON_VIRTUAL_THREAD))); LOGGER.debugf("Found scheduled static method %s declared on %s", method, method.declaringClass().name()); } } @@ -176,7 +177,8 @@ private void collectScheduledMethods(IndexView index, TransformedAnnotationsBuil } if (schedules != null) { scheduledBusinessMethods.produce(new ScheduledBusinessMethodItem(bean, method, schedules, - transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING))); + transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING), + transformedAnnotations.hasAnnotation(method, SchedulerDotNames.RUN_ON_VIRTUAL_THREAD))); LOGGER.debugf("Found scheduled business method %s declared on %s", method, bean); } } @@ -207,6 +209,11 @@ void validateScheduledBusinessMethods(SchedulerConfig config, Listio.quarkus quarkus-scheduler-kotlin + + io.quarkus + quarkus-virtual-threads + io.quarkus quarkus-arc diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java index 3ab9cff4e6294..eca7095ac87f6 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/SimpleScheduler.java @@ -64,6 +64,7 @@ import io.quarkus.scheduler.common.runtime.util.SchedulerUtils; import io.quarkus.scheduler.runtime.SchedulerRuntimeConfig.StartMode; import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle; +import io.quarkus.virtual.threads.VirtualThreadsRecorder; import io.smallrye.common.vertx.VertxContext; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -390,16 +391,32 @@ void execute(ZonedDateTime now, Vertx vertx) { Context context = VertxContext.getOrCreateDuplicatedContext(vertx); VertxContextSafetyToggle.setContextSafe(context, true); if (invoker.isBlocking()) { - context.executeBlocking(new Handler>() { - @Override - public void handle(Promise p) { - try { - doInvoke(now, scheduledFireTime); - } finally { - p.complete(); + if (invoker.isRunningOnVirtualThread()) { + // While counter-intuitive, we switch to a safe context, so that context is captured and attached + // to the virtual thread. + context.runOnContext(new Handler() { + @Override + public void handle(Void event) { + VirtualThreadsRecorder.getCurrent().execute(new Runnable() { + @Override + public void run() { + doInvoke(now, scheduledFireTime); + } + }); } - } - }, false); + }); + } else { + context.executeBlocking(new Handler>() { + @Override + public void handle(Promise p) { + try { + doInvoke(now, scheduledFireTime); + } finally { + p.complete(); + } + } + }, false); + } } else { context.runOnContext(new Handler() { @Override @@ -639,6 +656,11 @@ public CompletionStage invokeBean(ScheduledExecution execution) { return CompletableFuture.failedStage(e); } } + + @Override + public boolean isRunningOnVirtualThread() { + return runOnVirtualThread; + } }; } else { invoker = new DefaultInvoker() { diff --git a/integration-tests/virtual-threads/pom.xml b/integration-tests/virtual-threads/pom.xml index 43d45f5191b9b..3b56ac4f95d08 100644 --- a/integration-tests/virtual-threads/pom.xml +++ b/integration-tests/virtual-threads/pom.xml @@ -33,6 +33,8 @@ amqp-virtual-threads jms-virtual-threads vertx-event-bus-virtual-threads + scheduler-virtual-threads + quartz-virtual-threads diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/pom.xml b/integration-tests/virtual-threads/quartz-virtual-threads/pom.xml new file mode 100644 index 0000000000000..ba61edfaeb22b --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + quarkus-virtual-threads-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-integration-test-virtual-threads-quartz + Quarkus - Integration Tests - Virtual Threads - Quartz Scheduler + + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-quartz + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-quartz-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java new file mode 100644 index 0000000000000..c967414c5b3f8 --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java @@ -0,0 +1,71 @@ +package io.quarkus.virtual.scheduler; + +import java.lang.reflect.Method; + +import io.quarkus.arc.Arc; +import io.smallrye.common.vertx.VertxContext; +import io.vertx.core.Vertx; + +public class AssertHelper { + + /** + * Asserts that the current method: + * - runs on a duplicated context + * - runs on a virtual thread + * - has the request scope activated + */ + public static void assertEverything() { + assertThatTheRequestScopeIsActive(); + assertThatItRunsOnVirtualThread(); + assertThatItRunsOnADuplicatedContext(); + } + + public static void assertThatTheRequestScopeIsActive() { + if (!Arc.container().requestContext().isActive()) { + throw new AssertionError(("Expected the request scope to be active")); + } + } + + public static void assertThatItRunsOnADuplicatedContext() { + var context = Vertx.currentContext(); + if (context == null) { + throw new AssertionError("The method does not run on a Vert.x context"); + } + if (!VertxContext.isOnDuplicatedContext()) { + throw new AssertionError("The method does not run on a Vert.x **duplicated** context"); + } + } + + public static void assertThatItRunsOnVirtualThread() { + // We cannot depend on a Java 20. + try { + Method isVirtual = Thread.class.getMethod("isVirtual"); + isVirtual.setAccessible(true); + boolean virtual = (Boolean) isVirtual.invoke(Thread.currentThread()); + if (!virtual) { + throw new AssertionError("Thread " + Thread.currentThread() + " is not a virtual thread"); + } + } catch (Exception e) { + throw new AssertionError( + "Thread " + Thread.currentThread() + " is not a virtual thread - cannot invoke Thread.isVirtual()", e); + } + } + + public static void assertNotOnVirtualThread() { + // We cannot depend on a Java 20. + try { + Method isVirtual = Thread.class.getMethod("isVirtual"); + isVirtual.setAccessible(true); + boolean virtual = (Boolean) isVirtual.invoke(Thread.currentThread()); + if (virtual) { + throw new AssertionError("Thread " + Thread.currentThread() + " is a virtual thread"); + } + } catch (Exception e) { + // Trying using Thread name. + var name = Thread.currentThread().toString(); + if (name.toLowerCase().contains("virtual")) { + throw new AssertionError("Thread " + Thread.currentThread() + " seems to be a virtual thread"); + } + } + } +} diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java new file mode 100644 index 0000000000000..0b4588c16dbfb --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java @@ -0,0 +1,50 @@ +package io.quarkus.virtual.scheduler; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import jakarta.enterprise.event.Observes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; +import io.smallrye.common.annotation.RunOnVirtualThread; + +@Path("/") +public class ScheduledResource { + + Set executions = new CopyOnWriteArraySet<>(); + Set programmaticExecutions = new CopyOnWriteArraySet<>(); + + public void init(@Observes StartupEvent ev, Scheduler scheduler) { + scheduler.newJob("my-programmatic-job") + .setInterval("1s") + .setTask(ex -> { + AssertHelper.assertEverything(); + // Quarkus specific - each VT has a unique name + programmaticExecutions.add(Thread.currentThread().getName()); + }, true) + .schedule(); + } + + @Scheduled(every = "1s") + @RunOnVirtualThread + void run() { + AssertHelper.assertEverything(); + // Quarkus specific - each VT has a unique name + executions.add(Thread.currentThread().getName()); + } + + @GET + public Set getExecutions() { + return executions; + } + + @GET + @Path("/programmatic") + public Set getProgrammaticExecutions() { + return programmaticExecutions; + } +} diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/main/resources/application.properties b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/resources/application.properties new file mode 100644 index 0000000000000..43b1e230c2184 --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/main/resources/application.properties @@ -0,0 +1,3 @@ +quarkus.native.additional-build-args=--enable-preview + +quarkus.package.quiltflower.enabled=true \ No newline at end of file diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java new file mode 100644 index 0000000000000..99ce0563fdbb4 --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java @@ -0,0 +1,76 @@ +package io.quarkus.virtual.mail; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * An integration test reading the output of the unit test to verify that no tests where pinning the carrier thread. + * It reads the reports generated by surefire. + */ +public class NoPinningVerify { + + @Test + void verify() throws IOException, ParserConfigurationException, SAXException { + var reports = new File("target", "surefire-reports"); + Assertions.assertTrue(reports.isDirectory(), + "Unable to find " + reports.getAbsolutePath() + ", did you run the tests with Maven before?"); + var list = reports.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith("TEST") && name.endsWith("Test.xml"); + } + }); + Assertions.assertNotNull(list, + "Unable to find " + reports.getAbsolutePath() + ", did you run the tests with Maven before?"); + + for (File report : list) { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(report); + var suite = document.getFirstChild(); + var cases = getChildren(suite.getChildNodes(), "testcase"); + for (Node c : cases) { + verify(report, c); + } + } + + } + + private void verify(File file, Node ca) { + var fullname = ca.getAttributes().getNamedItem("classname").getTextContent() + "." + + ca.getAttributes().getNamedItem("name").getTextContent(); + var output = getChildren(ca.getChildNodes(), "system-out"); + if (output.isEmpty()) { + return; + } + var sout = output.get(0).getTextContent(); + if (sout.contains("VThreadContinuation.onPinned")) { + throw new AssertionError("The test case " + fullname + " pinned the carrier thread, check " + file.getAbsolutePath() + + " for details (or the log of the test)"); + } + + } + + private List getChildren(NodeList nodes, String name) { + List list = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + var node = nodes.item(i); + if (node.getNodeName().equalsIgnoreCase(name)) { + list.add(node); + } + } + return list; + } + +} diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java new file mode 100644 index 0000000000000..22abcdce9792e --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java @@ -0,0 +1,8 @@ +package io.quarkus.virtual.mail; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class RunOnVirtualThreadIT extends RunOnVirtualThreadTest { + +} diff --git a/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java new file mode 100644 index 0000000000000..7f2bec0ae9ba7 --- /dev/null +++ b/integration-tests/virtual-threads/quartz-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java @@ -0,0 +1,43 @@ +package io.quarkus.virtual.mail; + +import java.time.Duration; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +class RunOnVirtualThreadTest { + + @Test + void testScheduledMethods() { + Awaitility.await() + .pollDelay(Duration.ofSeconds(2)) + .untilAsserted(() -> { + var list = RestAssured.get().then() + .assertThat().statusCode(200) + .extract().as(new TypeRef>() { + }); + Assertions.assertTrue(list.size() > 3); + }); + } + + @Test + void testScheduledMethodsUsingApi() { + Awaitility.await() + .pollDelay(Duration.ofSeconds(2)) + .untilAsserted(() -> { + var list = RestAssured.get("/programmatic").then() + .assertThat().statusCode(200) + .extract().as(new TypeRef>() { + }); + Assertions.assertTrue(list.size() > 3); + }); + } + +} diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/pom.xml b/integration-tests/virtual-threads/scheduler-virtual-threads/pom.xml new file mode 100644 index 0000000000000..0811268cd06e2 --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + quarkus-virtual-threads-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-integration-test-virtual-threads-scheduler + Quarkus - Integration Tests - Virtual Threads - Scheduler + + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-scheduler + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-scheduler-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java new file mode 100644 index 0000000000000..c967414c5b3f8 --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/AssertHelper.java @@ -0,0 +1,71 @@ +package io.quarkus.virtual.scheduler; + +import java.lang.reflect.Method; + +import io.quarkus.arc.Arc; +import io.smallrye.common.vertx.VertxContext; +import io.vertx.core.Vertx; + +public class AssertHelper { + + /** + * Asserts that the current method: + * - runs on a duplicated context + * - runs on a virtual thread + * - has the request scope activated + */ + public static void assertEverything() { + assertThatTheRequestScopeIsActive(); + assertThatItRunsOnVirtualThread(); + assertThatItRunsOnADuplicatedContext(); + } + + public static void assertThatTheRequestScopeIsActive() { + if (!Arc.container().requestContext().isActive()) { + throw new AssertionError(("Expected the request scope to be active")); + } + } + + public static void assertThatItRunsOnADuplicatedContext() { + var context = Vertx.currentContext(); + if (context == null) { + throw new AssertionError("The method does not run on a Vert.x context"); + } + if (!VertxContext.isOnDuplicatedContext()) { + throw new AssertionError("The method does not run on a Vert.x **duplicated** context"); + } + } + + public static void assertThatItRunsOnVirtualThread() { + // We cannot depend on a Java 20. + try { + Method isVirtual = Thread.class.getMethod("isVirtual"); + isVirtual.setAccessible(true); + boolean virtual = (Boolean) isVirtual.invoke(Thread.currentThread()); + if (!virtual) { + throw new AssertionError("Thread " + Thread.currentThread() + " is not a virtual thread"); + } + } catch (Exception e) { + throw new AssertionError( + "Thread " + Thread.currentThread() + " is not a virtual thread - cannot invoke Thread.isVirtual()", e); + } + } + + public static void assertNotOnVirtualThread() { + // We cannot depend on a Java 20. + try { + Method isVirtual = Thread.class.getMethod("isVirtual"); + isVirtual.setAccessible(true); + boolean virtual = (Boolean) isVirtual.invoke(Thread.currentThread()); + if (virtual) { + throw new AssertionError("Thread " + Thread.currentThread() + " is a virtual thread"); + } + } catch (Exception e) { + // Trying using Thread name. + var name = Thread.currentThread().toString(); + if (name.toLowerCase().contains("virtual")) { + throw new AssertionError("Thread " + Thread.currentThread() + " seems to be a virtual thread"); + } + } + } +} diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java new file mode 100644 index 0000000000000..0b4588c16dbfb --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/java/io/quarkus/virtual/scheduler/ScheduledResource.java @@ -0,0 +1,50 @@ +package io.quarkus.virtual.scheduler; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import jakarta.enterprise.event.Observes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.Scheduler; +import io.smallrye.common.annotation.RunOnVirtualThread; + +@Path("/") +public class ScheduledResource { + + Set executions = new CopyOnWriteArraySet<>(); + Set programmaticExecutions = new CopyOnWriteArraySet<>(); + + public void init(@Observes StartupEvent ev, Scheduler scheduler) { + scheduler.newJob("my-programmatic-job") + .setInterval("1s") + .setTask(ex -> { + AssertHelper.assertEverything(); + // Quarkus specific - each VT has a unique name + programmaticExecutions.add(Thread.currentThread().getName()); + }, true) + .schedule(); + } + + @Scheduled(every = "1s") + @RunOnVirtualThread + void run() { + AssertHelper.assertEverything(); + // Quarkus specific - each VT has a unique name + executions.add(Thread.currentThread().getName()); + } + + @GET + public Set getExecutions() { + return executions; + } + + @GET + @Path("/programmatic") + public Set getProgrammaticExecutions() { + return programmaticExecutions; + } +} diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/resources/application.properties b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/resources/application.properties new file mode 100644 index 0000000000000..43b1e230c2184 --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/main/resources/application.properties @@ -0,0 +1,3 @@ +quarkus.native.additional-build-args=--enable-preview + +quarkus.package.quiltflower.enabled=true \ No newline at end of file diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java new file mode 100644 index 0000000000000..99ce0563fdbb4 --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/NoPinningVerify.java @@ -0,0 +1,76 @@ +package io.quarkus.virtual.mail; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * An integration test reading the output of the unit test to verify that no tests where pinning the carrier thread. + * It reads the reports generated by surefire. + */ +public class NoPinningVerify { + + @Test + void verify() throws IOException, ParserConfigurationException, SAXException { + var reports = new File("target", "surefire-reports"); + Assertions.assertTrue(reports.isDirectory(), + "Unable to find " + reports.getAbsolutePath() + ", did you run the tests with Maven before?"); + var list = reports.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith("TEST") && name.endsWith("Test.xml"); + } + }); + Assertions.assertNotNull(list, + "Unable to find " + reports.getAbsolutePath() + ", did you run the tests with Maven before?"); + + for (File report : list) { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(report); + var suite = document.getFirstChild(); + var cases = getChildren(suite.getChildNodes(), "testcase"); + for (Node c : cases) { + verify(report, c); + } + } + + } + + private void verify(File file, Node ca) { + var fullname = ca.getAttributes().getNamedItem("classname").getTextContent() + "." + + ca.getAttributes().getNamedItem("name").getTextContent(); + var output = getChildren(ca.getChildNodes(), "system-out"); + if (output.isEmpty()) { + return; + } + var sout = output.get(0).getTextContent(); + if (sout.contains("VThreadContinuation.onPinned")) { + throw new AssertionError("The test case " + fullname + " pinned the carrier thread, check " + file.getAbsolutePath() + + " for details (or the log of the test)"); + } + + } + + private List getChildren(NodeList nodes, String name) { + List list = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + var node = nodes.item(i); + if (node.getNodeName().equalsIgnoreCase(name)) { + list.add(node); + } + } + return list; + } + +} diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java new file mode 100644 index 0000000000000..22abcdce9792e --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadIT.java @@ -0,0 +1,8 @@ +package io.quarkus.virtual.mail; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class RunOnVirtualThreadIT extends RunOnVirtualThreadTest { + +} diff --git a/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java new file mode 100644 index 0000000000000..7f2bec0ae9ba7 --- /dev/null +++ b/integration-tests/virtual-threads/scheduler-virtual-threads/src/test/java/io/quarkus/virtual/mail/RunOnVirtualThreadTest.java @@ -0,0 +1,43 @@ +package io.quarkus.virtual.mail; + +import java.time.Duration; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +class RunOnVirtualThreadTest { + + @Test + void testScheduledMethods() { + Awaitility.await() + .pollDelay(Duration.ofSeconds(2)) + .untilAsserted(() -> { + var list = RestAssured.get().then() + .assertThat().statusCode(200) + .extract().as(new TypeRef>() { + }); + Assertions.assertTrue(list.size() > 3); + }); + } + + @Test + void testScheduledMethodsUsingApi() { + Awaitility.await() + .pollDelay(Duration.ofSeconds(2)) + .untilAsserted(() -> { + var list = RestAssured.get("/programmatic").then() + .assertThat().statusCode(200) + .extract().as(new TypeRef>() { + }); + Assertions.assertTrue(list.size() > 3); + }); + } + +} From ada6761763bd9dcc0d814327d66a8d9e35e78bac Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 1 Sep 2023 09:14:01 +0200 Subject: [PATCH 23/28] ArC: fix decorators and interface default methods - fixes #35664 --- .../io/quarkus/arc/processor/Methods.java | 2 +- .../arc/processor/SubclassGenerator.java | 26 +++----- .../DecoratorDefaultMethodTest.java | 59 +++++++++++++++++++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/DecoratorDefaultMethodTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index d48294834bc68..a5290ed4c1ce9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -140,7 +140,7 @@ private static boolean skipForClientProxy(MethodInfo method, boolean transformUn } static boolean skipForDelegateSubclass(MethodInfo method) { - if (Modifier.isStatic(method.flags()) || method.isSynthetic() || isDefault(method)) { + if (Modifier.isStatic(method.flags()) || method.isSynthetic()) { return true; } if (IGNORED_METHODS.contains(method.name())) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 126e917a878b8..f665300bc4444 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -637,13 +637,10 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi } List constructorParameterTypes = new ArrayList<>(); - // Fields and constructor - FieldCreator subclassField = null; - if (decoratedMethods.size() != nextDecoratorsValues.size()) { - subclassField = delegateSubclass.getFieldCreator("subclass", subclass.getClassName()) - .setModifiers(ACC_PRIVATE | ACC_FINAL); - constructorParameterTypes.add(subclass.getClassName()); - } + // Holds a reference to the subclass of the decorated bean + FieldCreator subclassField = delegateSubclass.getFieldCreator("subclass", subclass.getClassName()) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + constructorParameterTypes.add(subclass.getClassName()); Map nextDecoratorToField = new HashMap<>(); for (DecoratorInfo nextDecorator : decoratorParameters) { FieldCreator nextDecoratorField = delegateSubclass @@ -653,6 +650,7 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi nextDecoratorToField.put(nextDecorator, nextDecoratorField.getFieldDescriptor()); } + // Constructor MethodCreator constructor = delegateSubclass.getMethodCreator(Methods.INIT, "V", constructorParameterTypes.toArray(new String[0])); int param = 0; @@ -664,10 +662,8 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi constructor.getThis()); } // Set fields - if (subclassField != null) { - constructor.writeInstanceField( - subclassField.getFieldDescriptor(), constructor.getThis(), constructor.getMethodParam(param++)); - } + constructor.writeInstanceField( + subclassField.getFieldDescriptor(), constructor.getThis(), constructor.getMethodParam(param++)); for (FieldDescriptor field : nextDecoratorToField.values()) { constructor.writeInstanceField( field, constructor.getThis(), constructor.getMethodParam(param++)); @@ -700,10 +696,6 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi for (MethodKey m : methods) { MethodInfo method = m.method; - if (Methods.skipForDelegateSubclass(method)) { - continue; - } - MethodDescriptor methodDescriptor = MethodDescriptor.of(method); MethodCreator forward = delegateSubclass.getMethodCreator(methodDescriptor); // Exceptions @@ -818,9 +810,7 @@ && isDecorated(decoratedMethodDescriptors, methodDescriptor, resolvedMethodDescr // Create new delegate subclass instance and set the DecoratorDelegateProvider to satisfy the delegate IP ResultHandle[] paramHandles = new ResultHandle[constructorParameterTypes.size()]; int paramIdx = 0; - if (subclassField != null) { - paramHandles[paramIdx++] = subclassConstructor.getThis(); - } + paramHandles[paramIdx++] = subclassConstructor.getThis(); for (DecoratorInfo decoratorParameter : decoratorParameters) { ResultHandle decoratorHandle = decoratorToResultHandle.get(decoratorParameter.getIdentifier()); if (decoratorHandle == null) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/DecoratorDefaultMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/DecoratorDefaultMethodTest.java new file mode 100644 index 0000000000000..ca072e9ae26cd --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/DecoratorDefaultMethodTest.java @@ -0,0 +1,59 @@ +package io.quarkus.arc.test.decorators; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.util.TypeLiteral; +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.test.ArcTestContainer; + +public class DecoratorDefaultMethodTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Converter.class, ToLengthConverter.class, + NoopConverterDecorator.class); + + @SuppressWarnings("serial") + @Test + public void testDecoration() { + Converter converter = Arc.container().instance(new TypeLiteral>() { + }).get(); + assertEquals(5, converter.convert("Hola!")); + } + + interface Converter { + default int convert(T value) { + return Integer.MAX_VALUE; + } + } + + @ApplicationScoped + static class ToLengthConverter implements Converter { + @Override + public int convert(String value) { + return value.length(); + } + } + + @Priority(1) + @Decorator + static class NoopConverterDecorator implements Converter { + + @Inject + @Delegate + Converter delegate; + + @Override + public int convert(String value) { + return delegate.convert(value); + } + } + +} From 9fd055c02ecba1fea846b30ee92e496ce129f65d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 1 Sep 2023 11:43:54 +0300 Subject: [PATCH 24/28] Make minor improvements to CDI handling of AsyncHealthCheckFactory --- .../deployment/SmallRyeHealthProcessor.java | 4 ++++ .../runtime/QuarkusAsyncHealthCheckFactory.java | 16 +++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java index e26b0c7da8f33..616a4a3cfadc9 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java @@ -29,6 +29,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; +import io.quarkus.arc.deployment.ExcludedTypeBuildItem; import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Capabilities; @@ -71,6 +72,7 @@ import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; +import io.smallrye.health.AsyncHealthCheckFactory; import io.smallrye.health.SmallRyeHealthReporter; import io.smallrye.health.api.HealthGroup; import io.smallrye.health.api.HealthGroups; @@ -154,6 +156,7 @@ void healthCheck(BuildProducer buildItemBuildProducer, @SuppressWarnings("unchecked") void build(SmallRyeHealthRecorder recorder, BuildProducer feature, + BuildProducer excludedTypes, BuildProducer additionalBean, BuildProducer beanDefiningAnnotation) throws IOException, ClassNotFoundException { @@ -171,6 +174,7 @@ void build(SmallRyeHealthRecorder recorder, // Add additional beans additionalBean.produce(new AdditionalBeanBuildItem(QuarkusAsyncHealthCheckFactory.class)); + excludedTypes.produce(new ExcludedTypeBuildItem(AsyncHealthCheckFactory.class.getName())); additionalBean.produce(new AdditionalBeanBuildItem(SmallRyeHealthReporter.class)); // Make ArC discover @HealthGroup as a qualifier diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java index 31ebd6dda6afc..40ac4774701c7 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java @@ -1,9 +1,6 @@ package io.quarkus.smallrye.health.runtime; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Alternative; -import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; @@ -19,13 +16,14 @@ * Quarkus specific health check factory that runs blocking and reactive * health checks with different executors provided by {@link MutinyHelper}. */ -@ApplicationScoped -@Alternative -@Priority(1) +@Singleton public class QuarkusAsyncHealthCheckFactory extends AsyncHealthCheckFactory { - @Inject - Vertx vertx; + private final Vertx vertx; + + public QuarkusAsyncHealthCheckFactory(Vertx vertx) { + this.vertx = vertx; + } @Override public Uni callSync(HealthCheck healthCheck) { From c1e624823cba1120fb130b5776b9d36c5626916a Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 1 Sep 2023 11:11:18 +0200 Subject: [PATCH 25/28] ArC: log warning about removed beans for BeanContainer operations - this warning got accidentally removed in https://github.com/quarkusio/quarkus/pull/28112 (quarkus 2.14) --- ...rcContainerLookupProblemDetectedTest.java} | 6 +- ...inerSupplierLookupProblemDetectedTest.java | 57 +++++++++++++++++++ ...CDIProviderLookupProblemDetectedTest.java} | 6 +- .../io/quarkus/arc/impl/ArcContainerImpl.java | 6 +- 4 files changed, 68 insertions(+), 7 deletions(-) rename extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/{ApiLookupProblemDetectedTest.java => ArcContainerLookupProblemDetectedTest.java} (92%) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerSupplierLookupProblemDetectedTest.java rename extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/{ArcLookupProblemDetectedTest.java => CDIProviderLookupProblemDetectedTest.java} (93%) diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ApiLookupProblemDetectedTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerLookupProblemDetectedTest.java similarity index 92% rename from extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ApiLookupProblemDetectedTest.java rename to extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerLookupProblemDetectedTest.java index 1506823c865ef..7eb1d41361030 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ApiLookupProblemDetectedTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerLookupProblemDetectedTest.java @@ -17,7 +17,7 @@ import io.quarkus.arc.impl.ArcContainerImpl; import io.quarkus.test.QuarkusUnitTest; -public class ApiLookupProblemDetectedTest { +public class ArcContainerLookupProblemDetectedTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() @@ -31,10 +31,10 @@ public class ApiLookupProblemDetectedTest { Formatter fmt = new PatternFormatter("%m"); String message = fmt.format(warning); assertTrue(message.contains( - "Stack frame: io.quarkus.arc.test.unused.ApiLookupProblemDetectedTest.testWarning"), + "Stack frame: io.quarkus.arc.test.unused.ArcContainerLookupProblemDetectedTest.testWarning"), message); assertTrue(message.contains( - "Required type: class io.quarkus.arc.test.unused.ApiLookupProblemDetectedTest$Alpha"), + "Required type: class io.quarkus.arc.test.unused.ArcContainerLookupProblemDetectedTest$Alpha"), message); }); diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerSupplierLookupProblemDetectedTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerSupplierLookupProblemDetectedTest.java new file mode 100644 index 0000000000000..0a03a33d3279e --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcContainerSupplierLookupProblemDetectedTest.java @@ -0,0 +1,57 @@ +package io.quarkus.arc.test.unused; + +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 java.util.logging.Formatter; +import java.util.logging.LogRecord; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.logmanager.formatters.PatternFormatter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.impl.ArcContainerImpl; +import io.quarkus.test.QuarkusUnitTest; + +public class ArcContainerSupplierLookupProblemDetectedTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(Alpha.class)) + .setLogRecordPredicate(log -> ArcContainerImpl.class.getPackage().getName().equals(log.getLoggerName())) + .assertLogRecords(records -> { + LogRecord warning = records.stream() + .filter(l -> l.getMessage().contains("programmatic lookup problem detected")).findAny().orElse(null); + assertNotNull(warning); + Formatter fmt = new PatternFormatter("%m"); + String message = fmt.format(warning); + assertTrue(message.contains( + "Stack frame: io.quarkus.arc.test.unused.ArcContainerSupplierLookupProblemDetectedTest.testWarning"), + message); + assertTrue(message.contains( + "Required type: class io.quarkus.arc.test.unused.ArcContainerSupplierLookupProblemDetectedTest$Alpha"), + message); + }); + + @Test + public void testWarning() { + // Note that the warning is only displayed once, subsequent calls use a cached result + assertNull(Arc.container().beanInstanceSupplier(Alpha.class)); + } + + // unused bean, will be removed + @ApplicationScoped + static class Alpha { + + public String ping() { + return "ok"; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcLookupProblemDetectedTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/CDIProviderLookupProblemDetectedTest.java similarity index 93% rename from extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcLookupProblemDetectedTest.java rename to extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/CDIProviderLookupProblemDetectedTest.java index 664b4eeac7ece..6c83c69748da6 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/ArcLookupProblemDetectedTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/CDIProviderLookupProblemDetectedTest.java @@ -18,7 +18,7 @@ import io.quarkus.arc.impl.ArcContainerImpl; import io.quarkus.test.QuarkusUnitTest; -public class ArcLookupProblemDetectedTest { +public class CDIProviderLookupProblemDetectedTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() @@ -32,10 +32,10 @@ public class ArcLookupProblemDetectedTest { Formatter fmt = new PatternFormatter("%m"); String message = fmt.format(warning); assertTrue(message.contains( - "Stack frame: io.quarkus.arc.test.unused.ArcLookupProblemDetectedTest"), + "Stack frame: io.quarkus.arc.test.unused.CDIProviderLookupProblemDetectedTest"), message); assertTrue(message.contains( - "Required type: class io.quarkus.arc.test.unused.ArcLookupProblemDetectedTest$Alpha"), + "Required type: class io.quarkus.arc.test.unused.CDIProviderLookupProblemDetectedTest$Alpha"), message); }); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index cc12467b83c15..12f6a9ad0a65b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -304,7 +304,11 @@ private Supplier> createInstanceSupplier(boolean resolveAm if (qualifiers == null || qualifiers.length == 0) { qualifiers = new Annotation[] { Default.Literal.INSTANCE }; } - Set> resolvedBeans = resolved.getValue(new Resolvable(type, qualifiers)); + Resolvable resolvable = new Resolvable(type, qualifiers); + Set> resolvedBeans = resolved.getValue(resolvable); + if (resolvedBeans.isEmpty()) { + scanRemovedBeans(resolvable); + } Set> filteredBean = resolvedBeans; if (resolvedBeans.size() > 1) { if (resolveAmbiguities) { From 613e1641644ec597f888b17eb0fba1df148fd5aa Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Fri, 1 Sep 2023 21:53:53 +1000 Subject: [PATCH 26/28] Fix DevUI fix serialization error on Throwable Signed-off-by: Phillip Kruger --- .../ByteArrayInputStreamDeserializer.java | 27 +++++++++++++++++++ .../ByteArrayInputStreamSerializer.java | 21 +++++++++++++++ .../jsonrpc/DevUIDatabindCodec.java | 3 +++ .../menu/ContinuousTestingProcessor.java | 9 +------ .../testrunner/TestRunnerSmokeTestCase.java | 27 +++++++++---------- .../ContinuousTestingJsonRPCService.java | 10 +++---- 6 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamDeserializer.java create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamSerializer.java diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamDeserializer.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamDeserializer.java new file mode 100644 index 0000000000000..49c8509bdde59 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamDeserializer.java @@ -0,0 +1,27 @@ +package io.quarkus.devui.deployment.jsonrpc; + +import static io.quarkus.vertx.runtime.jackson.JsonUtil.BASE64_DECODER; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +public class ByteArrayInputStreamDeserializer extends JsonDeserializer { + + @Override + public ByteArrayInputStream deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + String text = p.getText(); + try { + byte[] decode = BASE64_DECODER.decode(text); + return new ByteArrayInputStream(decode); + } catch (IllegalArgumentException e) { + throw new InvalidFormatException(p, "Expected a base64 encoded byte array", text, ByteArrayInputStream.class); + } + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamSerializer.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamSerializer.java new file mode 100644 index 0000000000000..c9321eb745455 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/ByteArrayInputStreamSerializer.java @@ -0,0 +1,21 @@ +package io.quarkus.devui.deployment.jsonrpc; + +import static io.quarkus.vertx.runtime.jackson.JsonUtil.BASE64_ENCODER; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import io.quarkus.deployment.util.IoUtil; + +public class ByteArrayInputStreamSerializer extends JsonSerializer { + + @Override + public void serialize(ByteArrayInputStream value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + byte[] readBytes = IoUtil.readBytes(value); + jgen.writeString(BASE64_ENCODER.encodeToString(readBytes)); + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/DevUIDatabindCodec.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/DevUIDatabindCodec.java index b9b8af66a9d74..877c25a895a90 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/DevUIDatabindCodec.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/jsonrpc/DevUIDatabindCodec.java @@ -1,5 +1,6 @@ package io.quarkus.devui.deployment.jsonrpc; +import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.time.Instant; @@ -137,6 +138,8 @@ public JsonMapper create(JsonTypeAdapter> jsonObjectAdapt module.addDeserializer(Instant.class, new InstantDeserializer()); module.addSerializer(byte[].class, new ByteArraySerializer()); module.addDeserializer(byte[].class, new ByteArrayDeserializer()); + module.addSerializer(ByteArrayInputStream.class, new ByteArrayInputStreamSerializer()); + module.addDeserializer(ByteArrayInputStream.class, new ByteArrayInputStreamDeserializer()); mapper.registerModule(module); SimpleModule runtimeModule = new SimpleModule("vertx-module-runtime"); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ContinuousTestingProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ContinuousTestingProcessor.java index 48a8fa798fd12..d9c4fd83d582a 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ContinuousTestingProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ContinuousTestingProcessor.java @@ -1,13 +1,10 @@ package io.quarkus.devui.deployment.menu; import java.io.IOException; -import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import com.fasterxml.jackson.databind.ObjectMapper; - import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; @@ -233,7 +230,6 @@ private void registerGetStatusMethod(LaunchModeBuildItem launchModeBuildItem) { private void registerGetResultsMethod(LaunchModeBuildItem launchModeBuildItem) { DevConsoleManager.register(NAMESPACE + DASH + "getResults", ignored -> { - ObjectMapper objectMapper = new ObjectMapper(); // Remove in favior of build in. try { Optional ts = TestSupport.instance(); if (testsDisabled(launchModeBuildItem, ts)) { @@ -246,10 +242,7 @@ private void registerGetResultsMethod(LaunchModeBuildItem launchModeBuildItem) { return null; } - try (StringWriter sw = new StringWriter()) { - objectMapper.writeValue(sw, testRunResults); - return sw.toString(); - } + return testRunResults; } catch (Exception e) { throw new RuntimeException(e); diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/TestRunnerSmokeTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/TestRunnerSmokeTestCase.java index 2a248f7ecea50..42ce03667c735 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/TestRunnerSmokeTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/TestRunnerSmokeTestCase.java @@ -1,6 +1,6 @@ package io.quarkus.vertx.http.testrunner; -import java.util.List; +import java.util.Iterator; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; @@ -56,22 +56,21 @@ public void checkTestsAreRun() throws InterruptedException, Exception { Assertions.assertEquals(1L, ts.getTotalTestsPassed()); Assertions.assertEquals(0L, ts.getTotalTestsSkipped()); - JsonNode jsonRPCResultString = super.executeJsonRPCMethod("getResults"); - Assertions.assertNotNull(jsonRPCResultString); + JsonNode jsonRPCResult = super.executeJsonRPCMethod("getResults"); + Assertions.assertNotNull(jsonRPCResult); + Assertions.assertTrue(jsonRPCResult.has("results")); - @SuppressWarnings("unchecked") - Map> jsonRPCResult = mapper.readValue(jsonRPCResultString.textValue(), Map.class); + JsonNode results = jsonRPCResult.get("results"); + Assertions.assertNotNull(results); - Assertions.assertTrue(jsonRPCResult.containsKey("results")); + Iterator> fields = results.fields(); - Map results = jsonRPCResult.get("results"); - Assertions.assertNotNull(results); - for (Map.Entry result : results.entrySet()) { - @SuppressWarnings("unchecked") - Map testResult = result.getValue(); - String className = (String) testResult.get("className"); - List passing = (List) testResult.get("passing"); - List failing = (List) testResult.get("failing"); + while (fields.hasNext()) { + JsonNode testResult = fields.next().getValue(); + String className = testResult.get("className").asText(); + + JsonNode passing = testResult.get("passing"); + JsonNode failing = testResult.get("failing"); if (className.equals(SimpleET.class.getName())) { Assertions.assertEquals(1, failing.size(), className); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/continuoustesting/ContinuousTestingJsonRPCService.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/continuoustesting/ContinuousTestingJsonRPCService.java index 932c71ba61226..1700e58105032 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/continuoustesting/ContinuousTestingJsonRPCService.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/continuoustesting/ContinuousTestingJsonRPCService.java @@ -16,10 +16,10 @@ public class ContinuousTestingJsonRPCService implements Consumer { private final BroadcastProcessor stateBroadcaster = BroadcastProcessor.create(); - private final BroadcastProcessor resultBroadcaster = BroadcastProcessor.create(); + private final BroadcastProcessor resultBroadcaster = BroadcastProcessor.create(); private String lastKnownState = ""; - private String lastKnownResults = ""; + private Object lastKnownResults = ""; @Override public void accept(ContinuousTestingSharedStateManager.State state) { @@ -46,7 +46,7 @@ public Multi streamTestState() { return stateBroadcaster; } - public Multi streamTestResults() { + public Multi streamTestResults() { return resultBroadcaster; } @@ -56,7 +56,7 @@ public String lastKnownState() { } @NonBlocking - public String lastKnownResults() { + public Object lastKnownResults() { return this.lastKnownResults; } @@ -89,7 +89,7 @@ public boolean toggleInstrumentation() { return invokeAction("toggleInstrumentation"); } - public String getResults() { + public Object getResults() { return invokeAction("getResults"); } From 5c46d30456ba3d62970ce685bcfdb8a996724017 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 1 Sep 2023 14:01:16 +0100 Subject: [PATCH 27/28] Fix typo which affects OIDC Dev UI when either client credentials or password grant is used --- .../io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java index 411c2a6161609..5d9fc9335c0a8 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java @@ -185,9 +185,9 @@ public static Uni testServiceWithPassword(String tokenUrl, String servic return token.eventually(client::close); } - private static Uni testServiceInternal(WebClient client, String serviceUrl, Uni token) { - return token - .flatMap(t -> { + private static Uni testServiceInternal(WebClient client, String serviceUrl, Uni tokenUni) { + return tokenUni + .flatMap(token -> { LOG.infof("Sending token to '%s'", serviceUrl); return client .getAbs(serviceUrl) From 4cf8befd88a36a68e6e84dc36bec1db977f961f3 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 1 Sep 2023 15:55:08 +0100 Subject: [PATCH 28/28] Do not convert OIDC DevUI id and secret fields to lower case --- .../src/main/resources/dev-ui/qwc-oidc-provider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js b/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js index 6ecb76ad16901..0f3ab5f10ea74 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js +++ b/extensions/oidc/deployment/src/main/resources/dev-ui/qwc-oidc-provider.js @@ -396,7 +396,7 @@ export class QwcOidcProvider extends QwcHotReloadElement { @@ -405,7 +405,7 @@ export class QwcOidcProvider extends QwcHotReloadElement { @@ -595,7 +595,7 @@ export class QwcOidcProvider extends QwcHotReloadElement { @@ -619,7 +619,7 @@ export class QwcOidcProvider extends QwcHotReloadElement {