From 8fd669fc350843a3c74e7d5b3fe9d06ac1f2889a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 14 Feb 2024 12:10:34 +0100 Subject: [PATCH 01/29] Use the right MongoDB ClientSession interface (cherry picked from commit 03ed847a247173a3bc57acd7a7b54c9b70064a2e) --- .../main/kotlin/io/quarkus/mongodb/panache/kotlin/Panache.kt | 2 +- .../src/main/java/io/quarkus/mongodb/panache/Panache.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/Panache.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/Panache.kt index 817c55f54bd43..6bf7902775936 100644 --- a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/Panache.kt +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/Panache.kt @@ -1,6 +1,6 @@ package io.quarkus.mongodb.panache.kotlin -import com.mongodb.session.ClientSession +import com.mongodb.client.ClientSession import io.quarkus.mongodb.panache.kotlin.runtime.KotlinMongoOperations object Panache { diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/Panache.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/Panache.java index 2e1f900dee367..e84fa4cb2201d 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/Panache.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/Panache.java @@ -1,6 +1,6 @@ package io.quarkus.mongodb.panache; -import com.mongodb.session.ClientSession; +import com.mongodb.client.ClientSession; import io.quarkus.mongodb.panache.runtime.JavaMongoOperations; From 098c2df9b88440243805d27ae9915bb737b88b33 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 14 Feb 2024 12:36:26 +0000 Subject: [PATCH 02/29] Fix OidcEndpoint annotation processing (cherry picked from commit 7c74b900318e6edff8af99e49bc39fb7f33fbf77) --- .../java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java | 3 ++- .../quarkus/it/keycloak/OidcDiscoveryRequestCustomizer.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 92b12d8ed569e..ad353b7d1dc58 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -30,6 +30,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.ClientProxy; import io.quarkus.credentials.CredentialsProvider; import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.oidc.common.OidcEndpoint; @@ -496,7 +497,7 @@ public static Map> getOidcRequestFilt Map> map = new HashMap<>(); for (OidcRequestFilter filter : container.listAll(OidcRequestFilter.class).stream().map(handle -> handle.get()) .collect(Collectors.toList())) { - OidcEndpoint endpoint = filter.getClass().getAnnotation(OidcEndpoint.class); + OidcEndpoint endpoint = ClientProxy.unwrap(filter).getClass().getAnnotation(OidcEndpoint.class); OidcEndpoint.Type type = endpoint != null ? endpoint.value() : OidcEndpoint.Type.ALL; map.computeIfAbsent(type, k -> new ArrayList()).add(filter); } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryRequestCustomizer.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryRequestCustomizer.java index 069450ce91ec2..66eb4c9ef77e0 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryRequestCustomizer.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/OidcDiscoveryRequestCustomizer.java @@ -3,6 +3,7 @@ import jakarta.enterprise.context.ApplicationScoped; import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.common.OidcEndpoint; import io.quarkus.oidc.common.OidcEndpoint.Type; import io.quarkus.oidc.common.OidcRequestContextProperties; @@ -17,6 +18,9 @@ public class OidcDiscoveryRequestCustomizer implements OidcRequestFilter { @Override public void filter(HttpRequest request, Buffer buffer, OidcRequestContextProperties contextProps) { + if (!request.uri().endsWith(".well-known/openid-configuration")) { + throw new OIDCException("Filter is applied to the wrong endpoint: " + request.uri()); + } request.putHeader("Discovery", "OK"); } } From 830e964ad9399fb0fe6c674a01b99eeec440eefb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 14 Feb 2024 16:32:41 +0100 Subject: [PATCH 03/29] Fix guide URL in RESTEasy Client extension This is a consequence of renaming the extension in 3.7. I also added the guide to the subextensions. (cherry picked from commit ccf5512952d89f8beadff8f4dcb4189f6ff43df2) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 1 + .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-classic/resteasy-client-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-client-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 19c07bdc39da1..0d7995b8bc726 100644 --- a/extensions/resteasy-classic/resteasy-client-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-client-jackson/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,6 +9,7 @@ metadata: - "microprofile-rest-client" - "json" - "jackson" + guide: "https://quarkus.io/guides/resteasy-client" categories: - "web" - "serialization" diff --git a/extensions/resteasy-classic/resteasy-client-jaxb/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-client-jaxb/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 65e94f02a670f..f88c33e985fba 100644 --- a/extensions/resteasy-classic/resteasy-client-jaxb/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-client-jaxb/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,6 +8,7 @@ metadata: - "web-client" - "microprofile-rest-client" - "jaxb" + guide: "https://quarkus.io/guides/resteasy-client" categories: - "web" - "serialization" diff --git a/extensions/resteasy-classic/resteasy-client-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-client-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 74a6ec142a263..39deb9f94dd86 100644 --- a/extensions/resteasy-classic/resteasy-client-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-client-jsonb/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,6 +9,7 @@ metadata: - "microprofile-rest-client" - "json" - "jsonb" + guide: "https://quarkus.io/guides/resteasy-client" categories: - "web" - "serialization" diff --git a/extensions/resteasy-classic/resteasy-client-mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-client-mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml index fa5a971e2b3f0..172571c11af83 100644 --- a/extensions/resteasy-classic/resteasy-client-mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-client-mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,6 +8,7 @@ metadata: - "web-client" - "microprofile-rest-client" - "mutiny" + guide: "https://quarkus.io/guides/resteasy-client" categories: - "web" - "reactive" diff --git a/extensions/resteasy-classic/resteasy-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-classic/resteasy-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 7a30def101491..709a305eef669 100644 --- a/extensions/resteasy-classic/resteasy-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy-classic/resteasy-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -6,7 +6,7 @@ metadata: - "rest-client" - "web-client" - "microprofile-rest-client" - guide: "https://quarkus.io/guides/rest-client" + guide: "https://quarkus.io/guides/resteasy-client" categories: - "web" codestart: From a563de4daa9cf587131bd30709d8f296d12a3df5 Mon Sep 17 00:00:00 2001 From: SpaceFox Date: Wed, 14 Feb 2024 00:14:03 +0100 Subject: [PATCH 04/29] Adds an implementation note about @VirtualThreadUnit limitations Closes #38721 (cherry picked from commit 2b3e0717e47fa2ed9e33b315b7cc7bbfa4bf3f14) --- .../test/junit5/virtual/VirtualThreadUnit.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/VirtualThreadUnit.java b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/VirtualThreadUnit.java index b37efa15ef7fc..4af392208fdb4 100644 --- a/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/VirtualThreadUnit.java +++ b/independent-projects/junit5-virtual-threads/src/main/java/io/quarkus/test/junit5/virtual/VirtualThreadUnit.java @@ -11,6 +11,21 @@ /** * Extends the test case to detect pinned carrier thread. + *
+ *
+ * Implementation notes: current implementation uses JFR under the hood, with two consequences: + *
    + *
  1. This test won’t work on JVM without JFR support, e.g. OpenJ9.
  2. + *
  3. Each test that uses this annotation is several seconds longer than versions without it.
  4. + *
+ * This annotation uses JFR recording to detect pinning and analyze when a specific event is fired. + * Unfortunately, to ensure no events are missed, the test must ensure the JFR recording is on and off. + * It fires a mock event and wait until it read it. + * Due to JFR recording API limitations, it takes a lot of time to do these loops as there are many file reads. + * This adds several seconds to start and to stop to each test with pinned carrier thread detection enabled; and this + * additional work is mandatory to avoid missing event. + *
+ * This behaviour is not part of API and may change in future version. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) From 20a4811de2d43e3b3a62ce1746a6e2dbc1e18082 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 15 Feb 2024 16:24:50 +0200 Subject: [PATCH 05/29] Don't warn about @NotBody use in @GET methods in REST Client Fixes: #38798 (cherry picked from commit c18c33f22a17ceec8aaf8b29e27900ed6ea7d9de) --- .../reactive/deployment/QuarkusClientEndpointIndexer.java | 5 +++++ .../reactive/common/processor/EndpointIndexer.java | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java index fbebc7af0636e..ba76b9388c4e9 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java @@ -72,6 +72,11 @@ protected void logMissingJsonWarning(MethodInfo info) { + "' but no JSON extension has been added. Consider adding 'quarkus-rest-client-reactive-jackson' (recommended) or 'quarkus-rest-client-reactive-jsonb'."); } + @Override + protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo methodInfo) { + // do nothing in the case of the client + } + public static final class Builder extends AbstractBuilder { private final Capabilities capabilities; diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index a16267eab7b54..c2eb42017bb18 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -611,8 +611,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf + currentMethodInfo.parameterName(i)); bodyParamType = paramType; if (GET.equals(httpMethod) || HEAD.equals(httpMethod) || OPTIONS.equals(httpMethod)) { - log.warn("Using a body parameter with " + httpMethod + " is strongly discouraged. Offending method is '" - + currentMethodInfo.declaringClass().name() + "#" + currentMethodInfo + "'"); + warnAboutMissUsedBodyParameter(httpMethod, currentMethodInfo); } } String elementType = parameterResult.getElementType(); @@ -778,6 +777,11 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf } } + protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo methodInfo) { + log.warn("Using a body parameter with " + httpMethod + " is strongly discouraged. Offending method is '" + + methodInfo.declaringClass().name() + "#" + methodInfo + "'"); + } + protected boolean skipParameter(Map anns) { return skipMethodParameter != null && skipMethodParameter.test(anns); } From 5b26d5f2185128de360c26e4539bb19be12034f6 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 14 Feb 2024 17:05:53 +0100 Subject: [PATCH 06/29] ArC: fix interception when some methods return void This commit fixes 2 cases when invalid bytecode is generated: - when a `void`-returning method is intercepted and also decorated - when an interceptor declared on a target class returns `void` (cherry picked from commit 13972414d77e4bc8a1930937bc8c637ba9f9e32b) --- .../arc/processor/SubclassGenerator.java | 8 +- .../decorators/VoidMethodDecoratorTest.java | 69 ++++++++++++ ...VoidMethodInterceptorAndDecoratorTest.java | 101 ++++++++++++++++++ 3 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java 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 d5b558580a9ff..1574f72bc58be 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 @@ -341,7 +341,7 @@ public String apply(Set bindings) { applicationClassPredicate.test(bean.getBeanClass()), funBytecode.getMethodParam(1), funBytecode.getMethodParam(0)); - funBytecode.returnValue(ret); + funBytecode.returnValue(ret != null ? ret : funBytecode.loadNull()); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, methodsList, fun.getInstance()); } constructor.writeInstanceField(field.getFieldDescriptor(), constructor.getThis(), methodsList); @@ -461,9 +461,9 @@ public String apply(Set bindings) { MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod(declaringClass, originalMethodDescriptor.getName(), decoratorMethodDescriptor.getReturnType(), decoratorMethodDescriptor.getParameterTypes()); - funcBytecode - .returnValue(funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance, - superParamHandles)); + ResultHandle superResult = funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance, + superParamHandles); + funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); } else { ResultHandle superResult = funcBytecode.invokeVirtualMethod(forwardDescriptor, targetHandle, superParamHandles); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java new file mode 100644 index 0000000000000..f39ed5510e120 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/VoidMethodDecoratorTest.java @@ -0,0 +1,69 @@ +package io.quarkus.arc.test.decorators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +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 VoidMethodDecoratorTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Performer.class, MainPerformer.class, + PerformerDecorator.class); + + @Test + public void testDecoration() { + MainPerformer performer = Arc.container().instance(MainPerformer.class).get(); + + assertFalse(MainPerformer.DONE.get()); + assertFalse(PerformerDecorator.DONE.get()); + + performer.doSomething(); + + assertTrue(MainPerformer.DONE.get()); + assertTrue(PerformerDecorator.DONE.get()); + } + + interface Performer { + void doSomething(); + } + + @ApplicationScoped + static class MainPerformer implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Override + public void doSomething() { + DONE.set(true); + } + } + + @Dependent + @Priority(1) + @Decorator + static class PerformerDecorator implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Inject + @Delegate + Performer delegate; + + @Override + public void doSomething() { + DONE.set(true); + delegate.doSomething(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java new file mode 100644 index 0000000000000..b17de441bd387 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/decorators/interceptor/VoidMethodInterceptorAndDecoratorTest.java @@ -0,0 +1,101 @@ +package io.quarkus.arc.test.decorators.interceptor; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.Priority; +import jakarta.decorator.Decorator; +import jakarta.decorator.Delegate; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class VoidMethodInterceptorAndDecoratorTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Performer.class, MainPerformer.class, + PerformerDecorator.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void testDecoration() { + MainPerformer performer = Arc.container().instance(MainPerformer.class).get(); + + assertFalse(MainPerformer.DONE.get()); + assertFalse(PerformerDecorator.DONE.get()); + assertFalse(MyInterceptor.INTERCEPTED.get()); + + performer.doSomething(); + + assertTrue(MainPerformer.DONE.get()); + assertTrue(PerformerDecorator.DONE.get()); + assertTrue(MyInterceptor.INTERCEPTED.get()); + } + + interface Performer { + void doSomething(); + } + + @ApplicationScoped + @MyInterceptorBinding + static class MainPerformer implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Override + public void doSomething() { + DONE.set(true); + } + } + + @Dependent + @Priority(1) + @Decorator + static class PerformerDecorator implements Performer { + static final AtomicBoolean DONE = new AtomicBoolean(); + + @Inject + @Delegate + Performer delegate; + + @Override + public void doSomething() { + DONE.set(true); + delegate.doSomething(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Priority(1) + @Interceptor + static class MyInterceptor { + static final AtomicBoolean INTERCEPTED = new AtomicBoolean(); + + @AroundInvoke + Object log(InvocationContext ctx) throws Exception { + INTERCEPTED.set(true); + return ctx.proceed(); + } + } +} From 0da361e0f036f9c1a8ebbccf75eb6b4138fb2285 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 16 Feb 2024 08:47:43 +0200 Subject: [PATCH 07/29] Expand types which are considered text in multipart handling Fixes: #38802 (cherry picked from commit b3a9aa735f68763dbe0f15ed522c8045157f946d) --- .../MultipartTextWithoutFilenameTest.java | 52 +++++++++++++++++++ .../reactive/common/util/MediaTypeHelper.java | 8 +-- .../server/core/EncodedMediaType.java | 9 +--- .../multipart/MultiPartParserDefinition.java | 3 +- 4 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartTextWithoutFilenameTest.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartTextWithoutFilenameTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartTextWithoutFilenameTest.java new file mode 100644 index 0000000000000..c774985eaf5fb --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartTextWithoutFilenameTest.java @@ -0,0 +1,52 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static io.restassured.RestAssured.given; + +import java.io.IOException; +import java.util.function.Supplier; + +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.resteasy.reactive.RestResponse; +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 io.quarkus.test.QuarkusUnitTest; + +public class MultipartTextWithoutFilenameTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class); + } + }); + + @Test + public void test() throws IOException { + given() + .contentType("multipart/form-data") + .multiPart("firstParam", "{\"id\":\"myId\",\"name\":\"myName\"}", "application/json") + .when() + .post("/test") + .then() + .statusCode(200); + } + + @Path("/test") + public static class Resource { + + @POST + public RestResponse testMultipart(@FormParam("firstParam") final String firstParam, + @FormParam("secondParam") final String secondParam) { + return RestResponse.ok(); + } + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java index 81cedd7b62c60..ce799d31a24d6 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java @@ -267,10 +267,12 @@ public static boolean equivalentParams(MediaType m1, MediaType m2) { return true; } + // TODO: does this need to be more complex? public static boolean isTextLike(MediaType mediaType) { - return "text".equalsIgnoreCase(mediaType.getType()) - || ("application".equalsIgnoreCase(mediaType.getType()) - && mediaType.getSubtype().toLowerCase().startsWith("xml")); + String type = mediaType.getType(); + String subtype = mediaType.getSubtype(); + return (type.equals("application") && (subtype.contains("json") || subtype.contains("xml") || subtype.contains("yaml"))) + || type.equals("text"); } public static boolean isUnsupportedWildcardSubtype(MediaType mediaType) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/EncodedMediaType.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/EncodedMediaType.java index c219865d5efa8..1c3c4310be4c9 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/EncodedMediaType.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/EncodedMediaType.java @@ -5,6 +5,7 @@ import jakarta.ws.rs.core.MediaType; +import org.jboss.resteasy.reactive.common.util.MediaTypeHelper; import org.jboss.resteasy.reactive.server.spi.ContentType; /** @@ -21,7 +22,7 @@ public EncodedMediaType(MediaType mediaType) { MediaType effectiveMediaType = mediaType; String effectiveCharset; String originalCharset = mediaType.getParameters().get("charset"); - if (isStringMediaType(mediaType)) { + if (MediaTypeHelper.isTextLike(mediaType)) { effectiveCharset = originalCharset; if (effectiveCharset == null) { effectiveCharset = StandardCharsets.UTF_8.name(); @@ -38,12 +39,6 @@ public EncodedMediaType(MediaType mediaType) { } // TODO: does this need to be more complex? - private boolean isStringMediaType(MediaType mediaType) { - String type = mediaType.getType(); - String subtype = mediaType.getSubtype(); - return (type.equals("application") && (subtype.contains("json") || subtype.contains("xml") || subtype.contains("yaml"))) - || type.equals("text"); - } @Override public String toString() { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java index 42ed5899e7356..1cea966a9e0b1 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/multipart/MultiPartParserDefinition.java @@ -29,6 +29,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.headers.HeaderUtil; import org.jboss.resteasy.reactive.common.util.CaseInsensitiveMap; +import org.jboss.resteasy.reactive.common.util.MediaTypeHelper; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.spi.ServerHttpRequest; @@ -366,7 +367,7 @@ private boolean isText(String contentType) { if (contentType == null || contentType.isEmpty()) { // https://www.rfc-editor.org/rfc/rfc7578.html#section-4.4 says the default content-type if missing is text/plain return true; } - return MediaType.TEXT_PLAIN_TYPE.isCompatible(MediaType.valueOf(contentType)); + return MediaTypeHelper.isTextLike(MediaType.valueOf(contentType)); } public List getCreatedFiles() { From 9afdc862c526392f165e61826ab68a98746f0a59 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 16 Feb 2024 13:10:25 +0000 Subject: [PATCH 08/29] Add response text to the OIDC bootstrap log errors (cherry picked from commit c4433baac4cab145c6e655b37771a926a46c8f37) --- .../oidc/common/runtime/OidcCommonUtils.java | 8 ++++- .../oidc/runtime/OidcProviderClient.java | 29 +++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index ad353b7d1dc58..9e43d63452b73 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -447,7 +447,13 @@ public static Uni discoverMetadata(WebClient client, Map resp) { if (resp.statusCode() == 200) { return new JsonWebKeySet(resp.bodyAsString(StandardCharsets.UTF_8.name())); } else { - throw new OidcEndpointAccessException(resp.statusCode()); + throw responseException(metadata.getJsonWebKeySetUri(), resp); } } @@ -201,7 +200,7 @@ private UniOnItem> getHttpResponse(String uri, MultiMap for } private AuthorizationCodeTokens getAuthorizationCodeTokens(HttpResponse resp) { - JsonObject json = getJsonObject(resp); + JsonObject json = getJsonObject(metadata.getAuthorizationUri(), resp); final String idToken = json.getString(OidcConstants.ID_TOKEN_VALUE); final String accessToken = json.getString(OidcConstants.ACCESS_TOKEN_VALUE); final String refreshToken = json.getString(OidcConstants.REFRESH_TOKEN_VALUE); @@ -209,35 +208,41 @@ private AuthorizationCodeTokens getAuthorizationCodeTokens(HttpResponse } private UserInfo getUserInfo(HttpResponse resp) { - return new UserInfo(getString(resp)); + return new UserInfo(getString(metadata.getUserInfoUri(), resp)); } private TokenIntrospection getTokenIntrospection(HttpResponse resp) { - return new TokenIntrospection(getString(resp)); + return new TokenIntrospection(getString(metadata.getIntrospectionUri(), resp)); } - private static JsonObject getJsonObject(HttpResponse resp) { + private static JsonObject getJsonObject(String requestUri, HttpResponse resp) { if (resp.statusCode() == 200) { LOG.debugf("Request succeeded: %s", resp.bodyAsJsonObject()); return resp.bodyAsJsonObject(); } else { - throw responseException(resp); + throw responseException(requestUri, resp); } } - private static String getString(HttpResponse resp) { + private static String getString(String requestUri, HttpResponse resp) { if (resp.statusCode() == 200) { LOG.debugf("Request succeeded: %s", resp.bodyAsString()); return resp.bodyAsString(); } else { - throw responseException(resp); + throw responseException(requestUri, resp); } } - private static OIDCException responseException(HttpResponse resp) { + private static OIDCException responseException(String requestUri, HttpResponse resp) { String errorMessage = resp.bodyAsString(); - LOG.debugf("Request has failed: status: %d, error message: %s", resp.statusCode(), errorMessage); - throw new OIDCException(errorMessage); + + if (errorMessage != null && !errorMessage.isEmpty()) { + LOG.errorf("Request %s has failed: status: %d, error message: %s", requestUri, resp.statusCode(), errorMessage); + throw new OIDCException(errorMessage); + } else { + LOG.errorf("Request %s has failed: status: %d", requestUri, resp.statusCode()); + throw new OIDCException("Error status:" + resp.statusCode()); + } } @Override From 9806cf6d6fbe1d6a49216cb78831f1c0c7789999 Mon Sep 17 00:00:00 2001 From: Holly Cummins Date: Fri, 16 Feb 2024 13:01:03 +0000 Subject: [PATCH 09/29] Allow subclasses to override how much memory extension tests are allowed (cherry picked from commit 554bb02493b94bb2e2f11286f39ea93fb587c38e) --- .../io/quarkus/maven/it/RunAndCheckMojoTestBase.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java index 5ea0c98be1315..810d934b13852 100644 --- a/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java +++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java @@ -25,6 +25,7 @@ public class RunAndCheckMojoTestBase extends MojoTestBase { protected File testDir; protected DevModeClient devModeClient = new DevModeClient(getPort()); private static final int DEFAULT_PORT = 8080; + private static final int DEFAULT_MEMORY_IN_MB = 128; /** * Default to port 8080, but allow subtests to override. @@ -33,6 +34,14 @@ protected int getPort() { return DEFAULT_PORT; } + /** + * Default to quite constrained memory, but allow subclasses to override, for hungrier tests. + */ + + protected int getAllowedHeapInMb() { + return DEFAULT_MEMORY_IN_MB; + } + @AfterEach public void cleanup() { shutdownTheApp(); @@ -101,7 +110,7 @@ protected void run(boolean performCompile, LaunchMode mode, boolean skipAnalytic //running at once, if they add default to 75% of total mem we can easily run out //of physical memory as they will consume way more than what they need instead of //just running GC - args.add("-Djvm.args=-Xmx128m"); + args.add("-Djvm.args=-Xmx" + getAllowedHeapInMb() + "m"); running.execute(args, Map.of()); } From 8282af8c9a626f3608dfe2f2eb0c435c02ca712a Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 16 Feb 2024 16:24:04 +0100 Subject: [PATCH 10/29] Configure SISU bean filtering for the bootstrap Maven resolver (cherry picked from commit 1f231f0ebbaef987f292afc839b37a72dddfdf9e) --- .../resolver/maven/BootstrapMavenContext.java | 21 +++++---- .../maven/BootstrapMavenContextConfig.java | 43 ++++++++++++++++++- independent-projects/bootstrap/pom.xml | 2 +- .../extension-maven-plugin/pom.xml | 2 +- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java index e9f25f657a335..5150d282ca810 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContext.java @@ -79,8 +79,6 @@ import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.maven.dependency.ArtifactCoords; -import io.smallrye.beanbag.BeanSupplier; -import io.smallrye.beanbag.Scope; import io.smallrye.beanbag.maven.MavenFactory; public class BootstrapMavenContext { @@ -132,6 +130,8 @@ public class BootstrapMavenContext { private Boolean effectiveModelBuilder; private Boolean wsModuleParentHierarchy; private SettingsDecrypter settingsDecrypter; + private final List excludeSisuBeanPackages; + private final List includeSisuBeanPackages; public static BootstrapMavenContextConfig config() { return new BootstrapMavenContextConfig<>(); @@ -158,6 +158,8 @@ public BootstrapMavenContext(BootstrapMavenContextConfig config) this.remotePluginRepos = config.remotePluginRepos; this.remoteRepoManager = config.remoteRepoManager; this.cliOptions = config.cliOptions; + this.excludeSisuBeanPackages = config.getExcludeSisuBeanPackages(); + this.includeSisuBeanPackages = config.getIncludeSisuBeanPackages(); if (config.rootProjectDir == null) { final String topLevelBaseDirStr = PropertyUtils.getProperty(MAVEN_TOP_LEVEL_PROJECT_BASEDIR); if (topLevelBaseDirStr != null) { @@ -871,12 +873,15 @@ private void initRepoSystemAndManager() { protected MavenFactory configureMavenFactory() { return MavenFactory.create(RepositorySystem.class.getClassLoader(), builder -> { - builder.addBean(ModelBuilder.class).setSupplier(new BeanSupplier() { - @Override - public ModelBuilder get(Scope scope) { - return new MavenModelBuilder(BootstrapMavenContext.this); - } - }).setPriority(100).build(); + for (var pkg : includeSisuBeanPackages) { + builder.includePackage(pkg); + } + for (var pkg : excludeSisuBeanPackages) { + builder.excludePackage(pkg); + } + builder.addBean(ModelBuilder.class) + .setSupplier(scope -> new MavenModelBuilder(BootstrapMavenContext.this)) + .setPriority(100).build(); }); } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java index 8b645d5b0c2bc..0f44dcc37a8ec 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapMavenContextConfig.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -36,6 +37,45 @@ public class BootstrapMavenContextConfig modelProvider; + protected List excludeSisuBeanPackages; + protected List includeSisuBeanPackages; + + public T excludeSisuBeanPackage(String packageName) { + if (excludeSisuBeanPackages == null) { + excludeSisuBeanPackages = new ArrayList<>(); + } + excludeSisuBeanPackages.add(packageName); + return (T) this; + } + + protected List getExcludeSisuBeanPackages() { + if (excludeSisuBeanPackages == null) { + return List.of("org.apache.maven.shared.release", + "org.apache.maven.toolchain", + "org.apache.maven.lifecycle", + "org.apache.maven.execution", + "org.apache.maven.plugin"); + } + return excludeSisuBeanPackages; + } + + public T includeSisuBeanPackage(String packageName) { + if (includeSisuBeanPackages == null) { + includeSisuBeanPackages = new ArrayList<>(); + } + includeSisuBeanPackages.add(packageName); + return (T) this; + } + + protected List getIncludeSisuBeanPackages() { + if (includeSisuBeanPackages == null) { + return List.of("io.smallrye.beanbag", + "org.eclipse.aether", + "org.sonatype.plexus.components", + "org.apache.maven"); + } + return includeSisuBeanPackages; + } /** * Local repository location @@ -80,7 +120,6 @@ public T setCurrentProject(LocalProject currentProject) { * POM configuration will be picked up by the resolver and all the local projects * belonging to the workspace will be resolved at their original locations instead of * the actually artifacts installed in the repository. - * Note, that if {@link #workspace} is provided, this setting will be ignored. * * @param workspaceDiscovery enables or disables workspace discovery * @return this instance of the builder @@ -130,7 +169,7 @@ public T setRemoteRepositories(List remoteRepos) { /** * Remote plugin repositories that should be used by the resolver * - * @param repoPluginRepos remote plugin repositories + * @param remotePluginRepos remote plugin repositories * @return */ @SuppressWarnings("unchecked") diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 86a70e5f24be1..5a7dd66c305c2 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -74,7 +74,7 @@ 2.0 3.5.1 2.1.2 - 1.3.2 + 1.4.0 8.5 0.0.10 0.1.3 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 8a9a810916ef0..14054f1572df0 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -43,7 +43,7 @@ 3.2.5 3.10.2 2.16.1 - 1.3.2 + 1.4.0 5.10.2 From 7fd11a9e1042734e9e5feae09fbe56a89d6596d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sun, 18 Feb 2024 01:40:39 +0100 Subject: [PATCH 11/29] Fix Keycloak Admin Client Reactive Jackson reader provider priority (cherry picked from commit 8c0c413a2e646caecdba35ea03a2dbf54df74e61) --- .../reactive/runtime/ResteasyReactiveClientProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java index c39ffee71d45a..a8004dd19c049 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java @@ -28,7 +28,8 @@ public class ResteasyReactiveClientProvider implements ResteasyClientProvider { private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON); - private static final int PROVIDER_PRIORITY = Priorities.USER + 100; // ensures that it will be used first + private static final int WRITER_PROVIDER_PRIORITY = Priorities.USER + 100; // ensures that it will be used first + private static final int READER_PROVIDER_PRIORITY = Priorities.USER - 100; // ensures that it will be used first private final boolean tlsTrustAll; @@ -77,9 +78,9 @@ private ClientBuilderImpl registerJacksonProviders(ClientBuilderImpl clientBuild clientBuilder = clientBuilder .registerMessageBodyReader(new JacksonBasicMessageBodyReader(newObjectMapper), Object.class, HANDLED_MEDIA_TYPES, true, - PROVIDER_PRIORITY) + READER_PROVIDER_PRIORITY) .registerMessageBodyWriter(new ClientJacksonMessageBodyWriter(newObjectMapper), Object.class, - HANDLED_MEDIA_TYPES, true, PROVIDER_PRIORITY); + HANDLED_MEDIA_TYPES, true, WRITER_PROVIDER_PRIORITY); } InstanceHandle clientLogger = arcContainer.instance(ClientLogger.class); if (clientLogger.isAvailable()) { From 09d71181a097e32e1f712d678bab01b00b5adaac Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Sun, 18 Feb 2024 23:22:45 +0100 Subject: [PATCH 12/29] Fix copy/paste typo (cherry picked from commit 1ec895c562b92752ae375290b971d4aa75e39471) --- docs/src/main/asciidoc/hibernate-orm.adoc | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index e682cc45ceda6..3923a512e4327 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -1350,11 +1350,6 @@ and annotating the implementation with the appropriate qualifiers: @JsonFormat // <1> @PersistenceUnitExtension // <2> public class MyJsonFormatMapper implements FormatMapper { // <3> - @Override - public String inspect(String sql) { - // ... - return sql; - } @Override public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { // ... @@ -1382,11 +1377,6 @@ In case of a custom XML format mapper, a different CDI qualifier must be applied @XmlFormat // <1> @PersistenceUnitExtension // <2> public class MyJsonFormatMapper implements FormatMapper { // <3> - @Override - public String inspect(String sql) { - // ... - return sql; - } @Override public T fromString(CharSequence charSequence, JavaType javaType, WrapperOptions wrapperOptions) { // ... From f1cb0ef5bc3c75cb6a4cf34666d58c89b80136a1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 19 Feb 2024 10:02:37 +0200 Subject: [PATCH 13/29] Ensure that generated project GAV is always set Fixes: #38837 (cherry picked from commit 8178c7fd3dc1c5323cadcd308a5962f5e5ce45b7) --- .../io/quarkus/maven/CreateProjectMojo.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 3025394fba8f4..e663027c9ecd8 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -276,10 +276,10 @@ public void execute() throws MojoExecutionException { } askTheUserForMissingValues(); - if (projectArtifactId != DEFAULT_ARTIFACT_ID && !OK_ID.matcher(projectArtifactId).matches()) { + if (!DEFAULT_ARTIFACT_ID.equals(projectArtifactId) && !OK_ID.matcher(projectArtifactId).matches()) { throw new MojoExecutionException(String.format(BAD_IDENTIFIER, "artifactId", projectArtifactId)); } - if (projectGroupId != DEFAULT_GROUP_ID && !OK_ID.matcher(projectGroupId).matches()) { + if (!DEFAULT_GROUP_ID.equals(projectGroupId) && !OK_ID.matcher(projectGroupId).matches()) { throw new MojoExecutionException(String.format(BAD_IDENTIFIER, "groupId", projectGroupId)); } @@ -389,16 +389,7 @@ private void askTheUserForMissingValues() throws MojoExecutionException { // If the user has disabled the interactive mode or if the user has specified the artifactId, disable the // user interactions. if (!session.getRequest().isInteractiveMode() || shouldUseDefaults()) { - if (isBlank(projectArtifactId)) { - // we need to set it for the project directory - projectArtifactId = DEFAULT_ARTIFACT_ID; - } - if (isBlank(projectGroupId)) { - projectGroupId = DEFAULT_GROUP_ID; - } - if (isBlank(projectVersion)) { - projectVersion = DEFAULT_VERSION; - } + setProperDefaults(); return; } @@ -427,12 +418,27 @@ private void askTheUserForMissingValues() throws MojoExecutionException { input -> noCode = input.startsWith("n")); prompter.collectInput(); + } else { + setProperDefaults(); } } catch (IOException e) { throw new MojoExecutionException("Unable to get user input", e); } } + private void setProperDefaults() { + if (isBlank(projectArtifactId)) { + // we need to set it for the project directory + projectArtifactId = DEFAULT_ARTIFACT_ID; + } + if (isBlank(projectGroupId)) { + projectGroupId = DEFAULT_GROUP_ID; + } + if (isBlank(projectVersion)) { + projectVersion = DEFAULT_VERSION; + } + } + private boolean shouldUseDefaults() { // Must be called before user input return projectArtifactId != null; From 659e474197d4f1ece17acdda0001077d6128b440 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 19 Feb 2024 12:06:02 +0200 Subject: [PATCH 14/29] Make registration of OAuthBearerValidatorCallbackHandler conditional `org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallbackHandler` depends on the optional `org.jose4j.keys.resolvers.VerificationKeyResolver` so it should only be registered when the latter is present, similarly to `org.apache.kafka.common.security.oauthbearer.secured.OAuthBearerValidatorCallbackHandler` Closes https://github.com/quarkusio/quarkus/issues/38851 (cherry picked from commit d88e0bda8f67e8ab2fcd2894d92c8f1843f1a97e) --- .../io/quarkus/kafka/client/deployment/KafkaProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index 3a574b4424f85..18549b1f92eb4 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -415,6 +415,9 @@ public void withSasl(CombinedIndexBuildItem index, reflectiveClassCondition.produce(new ReflectiveClassConditionBuildItem( "org.apache.kafka.common.security.oauthbearer.secured.OAuthBearerValidatorCallbackHandler", "org.jose4j.keys.resolvers.VerificationKeyResolver")); + reflectiveClassCondition.produce(new ReflectiveClassConditionBuildItem( + "org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallbackHandler", + "org.jose4j.keys.resolvers.VerificationKeyResolver")); } private void registerJDKLoginModules(BuildProducer reflectiveClass) { From 404b337028ae78f1d1f079660433cda06206a8fb Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 19 Feb 2024 13:48:46 +0100 Subject: [PATCH 15/29] Testing: fix @MockitoConfig(convertScopes=true) with auto-producers The annotation transformer in `SingletonToApplicationScopedTestBuildChainCustomizerProducer` has to: - look for the `@Produces` annotation in ArC's `AnnotationStore`, not in Jandex; - run _after_ the annotation transformer in `AutoProducerMethodsProcessor`. This is enough to recognize an auto-producer (producer without `@Produces`). (cherry picked from commit 5aebe5a348d71aebde8e36336c787f2fc4bfee01) --- .../SuffixServiceSingletonProducer.java | 3 +-- .../quarkus/it/mockbean/UnusedServiceTest.java | 2 +- ...nScopedTestBuildChainCustomizerProducer.java | 17 ++++++++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixServiceSingletonProducer.java b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixServiceSingletonProducer.java index 7288856034311..6e79b292ca255 100644 --- a/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixServiceSingletonProducer.java +++ b/integration-tests/injectmock/src/main/java/io/quarkus/it/mockbean/SuffixServiceSingletonProducer.java @@ -1,11 +1,10 @@ package io.quarkus.it.mockbean; -import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; public class SuffixServiceSingletonProducer { - @Produces + //@Produces // intentionally commented out to test that auto-producers work with `@InjectMock` @Singleton public SuffixServiceSingleton dummyService() { return new SuffixServiceSingleton(); diff --git a/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/UnusedServiceTest.java b/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/UnusedServiceTest.java index 128967dc5a2fb..d3c3481429664 100644 --- a/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/UnusedServiceTest.java +++ b/integration-tests/injectmock/src/test/java/io/quarkus/it/mockbean/UnusedServiceTest.java @@ -21,7 +21,7 @@ public void testInjectedUnusedBeanIsNotRemoved() { } @Test - public void testNonInjectedUnusedBeanIsNotRemoved() { + public void testNonInjectedUnusedBeanIsRemoved() { Assertions.assertFalse(Arc.container().instance(OtherUnusedService.class).isAvailable()); } } diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SingletonToApplicationScopedTestBuildChainCustomizerProducer.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SingletonToApplicationScopedTestBuildChainCustomizerProducer.java index 9cfef464fa310..d9b0709c27fb8 100644 --- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SingletonToApplicationScopedTestBuildChainCustomizerProducer.java +++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/SingletonToApplicationScopedTestBuildChainCustomizerProducer.java @@ -59,7 +59,7 @@ public void execute(BuildContext context) { continue; } AnnotationValue allowScopeConversionValue = instance.value("convertScopes"); - if ((allowScopeConversionValue != null) && allowScopeConversionValue.asBoolean()) { + if (allowScopeConversionValue != null && allowScopeConversionValue.asBoolean()) { // we need to fetch the type of the bean, so we need to look at the type of the field mockTypes.add(instance.target().asField().type().name()); } @@ -84,7 +84,15 @@ public void execute(BuildContext context) { context.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { @Override public boolean appliesTo(AnnotationTarget.Kind kind) { - return (kind == AnnotationTarget.Kind.CLASS) || (kind == AnnotationTarget.Kind.METHOD); + return kind == AnnotationTarget.Kind.CLASS || kind == AnnotationTarget.Kind.METHOD; + } + + @Override + public int getPriority() { + // annotation transformer registered in `AutoProducerMethodsProcessor` has priority + // of `DEFAULT_PRIORITY - 1` and we need to run _after_ it, otherwise we wouldn't + // recognize an auto-producer (producer without `@Produces`) + return DEFAULT_PRIORITY - 10; } @Override @@ -100,9 +108,8 @@ public void transform(TransformationContext transformationContext) { } } else if (target.kind() == AnnotationTarget.Kind.METHOD) { // CDI producer case MethodInfo methodInfo = target.asMethod(); - if ((methodInfo.annotation(DotNames.PRODUCES) != null) - && (Annotations.contains(transformationContext.getAnnotations(), - DotNames.SINGLETON) + if (Annotations.contains(transformationContext.getAnnotations(), DotNames.PRODUCES) + && (Annotations.contains(transformationContext.getAnnotations(), DotNames.SINGLETON) || hasSingletonBeanDefiningAnnotation(transformationContext))) { DotName returnType = methodInfo.returnType().name(); if (mockTypes.contains(returnType)) { From 145404fe5aa4bc3e4d1f1e92f2ee8406e5d3078b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 13 Feb 2024 19:30:47 +0000 Subject: [PATCH 16/29] Fail early if OIDC client password grant OIDC UserInfo access is misconfigured (cherry picked from commit 4d591f02bf6ac8e23d14f4df3d15ed21b8e8a3de) --- ...tPasswordGrantSecretIsMissingTestCase.java | 48 +++++++++++++++++++ .../client/runtime/OidcClientRecorder.java | 15 ++++-- 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientPasswordGrantSecretIsMissingTestCase.java diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientPasswordGrantSecretIsMissingTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientPasswordGrantSecretIsMissingTestCase.java new file mode 100644 index 0000000000000..0e1d80f22152c --- /dev/null +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientPasswordGrantSecretIsMissingTestCase.java @@ -0,0 +1,48 @@ +package io.quarkus.oidc.client; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class OidcClientPasswordGrantSecretIsMissingTestCase { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset( + "quarkus.oidc-client.token-path=http://localhost:8180/oidc/tokens\n" + + "quarkus.oidc-client.client-id=quarkus\n" + + "quarkus.oidc-client.credentials.secret=secret\n" + + "quarkus.oidc-client.grant.type=password\n" + + "quarkus.oidc-client.grant-options.password.user=alice\n"), + "application.properties")) + .assertException(t -> { + Throwable e = t; + ConfigurationException te = null; + while (e != null) { + if (e instanceof ConfigurationException) { + te = (ConfigurationException) e; + break; + } + e = e.getCause(); + } + assertNotNull(te); + assertTrue( + te.getMessage() + .contains("Username and password must be set when a password grant is used"), + te.getMessage()); + }); + + @Test + public void test() { + Assertions.fail(); + } + +} diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java index cff9f35a930cc..f2d1a07153ba7 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientRecorder.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -170,10 +171,16 @@ public OidcClient apply(OidcConfigurationMetadata metadata, Throwable t) { // Without this block `password` will be listed first, before `username` // which is not a technical problem but might affect Wiremock tests or the endpoints // which expect a specific order. - tokenGrantParams.add(OidcConstants.PASSWORD_GRANT_USERNAME, - grantOptions.get(OidcConstants.PASSWORD_GRANT_USERNAME)); - tokenGrantParams.add(OidcConstants.PASSWORD_GRANT_PASSWORD, - grantOptions.get(OidcConstants.PASSWORD_GRANT_PASSWORD)); + final String userName = grantOptions.get(OidcConstants.PASSWORD_GRANT_USERNAME); + final String userPassword = grantOptions.get(OidcConstants.PASSWORD_GRANT_PASSWORD); + if (userName == null || userPassword == null) { + throw new ConfigurationException( + "Username and password must be set when a password grant is used", + Set.of("quarkus.oidc-client.grant.type", + "quarkus.oidc-client.grant-options")); + } + tokenGrantParams.add(OidcConstants.PASSWORD_GRANT_USERNAME, userName); + tokenGrantParams.add(OidcConstants.PASSWORD_GRANT_PASSWORD, userPassword); for (Map.Entry entry : grantOptions.entrySet()) { if (!OidcConstants.PASSWORD_GRANT_USERNAME.equals(entry.getKey()) && !OidcConstants.PASSWORD_GRANT_PASSWORD.equals(entry.getKey())) { From 080f72c6e1611d3c39ff1b8cc60913e3f2a32c0e Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Mon, 19 Feb 2024 14:24:18 +0100 Subject: [PATCH 17/29] Fix warning when launching dev mode specifying quarkus-maven-plugin GAV on the command line (cherry picked from commit c2f36d03fea5ec2384729c6d21fc59adf9854547) --- devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index fb8385f87be59..2781edf9f02f8 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -592,6 +592,7 @@ private String handleAutoCompile() throws MojoExecutionException { if (!e.getGoals().isEmpty()) { goalPrefix = getMojoDescriptor(p, e.getGoals().get(0)).getPluginDescriptor().getGoalPrefix(); pluginPrefixes.put(goalPrefix, p); + pluginPrefixes.put(p.getId(), p); } if (e.getPhase() != null) { phaseExecutions.computeIfAbsent(e.getPhase(), k -> new ArrayList<>()).add(new PluginExec(p, goalPrefix, e)); @@ -630,7 +631,7 @@ private String handleAutoCompile() throws MojoExecutionException { if (goal.endsWith(currentGoal)) { break; } - var colon = goal.indexOf(':'); + var colon = goal.lastIndexOf(':'); if (colon >= 0) { var plugin = pluginPrefixes.get(goal.substring(0, colon)); if (plugin == null) { From 7de30572eb49c10fab6e55222d4ef0d4cb0254b3 Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Fri, 16 Feb 2024 12:30:56 +0100 Subject: [PATCH 18/29] Propagate Vert.x context on all ExecutorService methods for VirtualThreadExecutor (cherry picked from commit 1ea93ccfd20089fc9e5781749fe9fb9184cfa984) --- .../ContextPreservingExecutorService.java | 101 ++++++++++++++---- .../VirtualThreadExecutorSupplierTest.java | 85 +++++++++++++++ .../quarkus/virtual/rr/FilteredResource.java | 23 +++- .../java/io/quarkus/virtual/rr/Filters.java | 2 +- 4 files changed, 186 insertions(+), 25 deletions(-) diff --git a/extensions/virtual-threads/runtime/src/main/java/io/quarkus/virtual/threads/ContextPreservingExecutorService.java b/extensions/virtual-threads/runtime/src/main/java/io/quarkus/virtual/threads/ContextPreservingExecutorService.java index 09499bb96f67f..50e8b5e0427ab 100644 --- a/extensions/virtual-threads/runtime/src/main/java/io/quarkus/virtual/threads/ContextPreservingExecutorService.java +++ b/extensions/virtual-threads/runtime/src/main/java/io/quarkus/virtual/threads/ContextPreservingExecutorService.java @@ -2,13 +2,17 @@ import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import io.vertx.core.Context; import io.vertx.core.Vertx; import io.vertx.core.impl.ContextInternal; @@ -22,26 +26,77 @@ class ContextPreservingExecutorService implements ExecutorService { this.delegate = delegate; } - public void execute(final Runnable command) { - var context = Vertx.currentContext(); - if (!(context instanceof ContextInternal)) { - delegate.execute(command); - } else { - ContextInternal contextInternal = (ContextInternal) context; - delegate.execute(new Runnable() { - @Override - public void run() { - final var previousContext = contextInternal.beginDispatch(); - try { - command.run(); - } finally { - contextInternal.endDispatch(previousContext); - } + private static final class ContextPreservingRunnable implements Runnable { + + private final Runnable task; + private final Context context; + + public ContextPreservingRunnable(Runnable task) { + this.task = task; + this.context = Vertx.currentContext(); + } + + @Override + public void run() { + if (context instanceof ContextInternal) { + ContextInternal contextInternal = (ContextInternal) context; + final var previousContext = contextInternal.beginDispatch(); + try { + task.run(); + } finally { + contextInternal.endDispatch(previousContext); + } + } else { + task.run(); + } + } + } + + private static final class ContextPreservingCallable implements Callable { + + private final Callable task; + private final Context context; + + public ContextPreservingCallable(Callable task) { + this.task = task; + this.context = Vertx.currentContext(); + } + + @Override + public T call() throws Exception { + if (context instanceof ContextInternal) { + ContextInternal contextInternal = (ContextInternal) context; + final var previousContext = contextInternal.beginDispatch(); + try { + return task.call(); + } finally { + contextInternal.endDispatch(previousContext); } - }); + } else { + return task.call(); + } } } + private static Runnable decorate(Runnable command) { + Objects.requireNonNull(command); + return new ContextPreservingRunnable(command); + } + + private static Callable decorate(Callable task) { + Objects.requireNonNull(task); + return new ContextPreservingCallable<>(task); + } + + private static Collection> decorateAll(Collection> tasks) { + Objects.requireNonNull(tasks); + return tasks.stream().map(ContextPreservingExecutorService::decorate).collect(Collectors.toList()); + } + + public void execute(final Runnable command) { + delegate.execute(decorate(command)); + } + public boolean isShutdown() { return delegate.isShutdown(); } @@ -56,39 +111,39 @@ public boolean awaitTermination(final long timeout, final TimeUnit unit) throws @Override public Future submit(Callable task) { - return delegate.submit(task); + return delegate.submit(decorate(task)); } @Override public Future submit(Runnable task, T result) { - return delegate.submit(task, result); + return submit(Executors.callable(task, result)); } @Override public Future submit(Runnable task) { - return delegate.submit(task); + return delegate.submit(decorate(task)); } @Override public List> invokeAll(Collection> tasks) throws InterruptedException { - return delegate.invokeAll(tasks); + return delegate.invokeAll(decorateAll(tasks)); } @Override public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { - return delegate.invokeAll(tasks, timeout, unit); + return delegate.invokeAll(decorateAll(tasks), timeout, unit); } @Override public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return delegate.invokeAny(tasks); + return delegate.invokeAny(decorateAll(tasks)); } @Override public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return delegate.invokeAny(tasks, timeout, unit); + return delegate.invokeAny(decorateAll(tasks), timeout, unit); } public void shutdown() { diff --git a/extensions/virtual-threads/runtime/src/test/java/io/quarkus/virtual/threads/VirtualThreadExecutorSupplierTest.java b/extensions/virtual-threads/runtime/src/test/java/io/quarkus/virtual/threads/VirtualThreadExecutorSupplierTest.java index 04cc1219d35a6..74b5a49c48fab 100644 --- a/extensions/virtual-threads/runtime/src/test/java/io/quarkus/virtual/threads/VirtualThreadExecutorSupplierTest.java +++ b/extensions/virtual-threads/runtime/src/test/java/io/quarkus/virtual/threads/VirtualThreadExecutorSupplierTest.java @@ -5,17 +5,34 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; +import io.vertx.core.Context; +import io.vertx.core.Vertx; class VirtualThreadExecutorSupplierTest { + @BeforeEach + void configRecorder() { + VirtualThreadsRecorder.config = new VirtualThreadsConfig(); + VirtualThreadsRecorder.config.enabled = true; + VirtualThreadsRecorder.config.namePrefix = Optional.empty(); + } + @Test @EnabledForJreRange(min = JRE.JAVA_20, disabledReason = "Virtual Threads are a preview feature starting from Java 20") void virtualThreadCustomScheduler() @@ -44,6 +61,74 @@ void execute() throws ClassNotFoundException, InvocationTargetException, NoSuchM assertSubscriber.awaitItem(Duration.ofSeconds(1)).assertCompleted(); } + @Test + @EnabledForJreRange(min = JRE.JAVA_20, disabledReason = "Virtual Threads are a preview feature starting from Java 20") + void executePropagatesVertxContext() throws ExecutionException, InterruptedException { + ExecutorService executorService = VirtualThreadsRecorder.getCurrent(); + Vertx vertx = Vertx.vertx(); + CompletableFuture future = new CompletableFuture<>(); + vertx.executeBlocking(() -> { + executorService.execute(() -> { + assertThatItRunsOnVirtualThread(); + future.complete(Vertx.currentContext()); + }); + return null; + }).toCompletionStage().toCompletableFuture().get(); + assertThat(future.get()).isNotNull(); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_20, disabledReason = "Virtual Threads are a preview feature starting from Java 20") + void executePropagatesVertxContextMutiny() { + ExecutorService executorService = VirtualThreadsRecorder.getCurrent(); + Vertx vertx = Vertx.vertx(); + var assertSubscriber = Uni.createFrom().voidItem() + .runSubscriptionOn(command -> vertx.executeBlocking(() -> { + command.run(); + return null; + })) + .emitOn(executorService) + .map(x -> { + assertThatItRunsOnVirtualThread(); + return Vertx.currentContext(); + }) + .subscribe().withSubscriber(UniAssertSubscriber.create()); + assertThat(assertSubscriber.awaitItem().assertCompleted().getItem()).isNotNull(); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_20, disabledReason = "Virtual Threads are a preview feature starting from Java 20") + void submitPropagatesVertxContext() throws ExecutionException, InterruptedException { + ExecutorService executorService = VirtualThreadsRecorder.getCurrent(); + Vertx vertx = Vertx.vertx(); + CompletableFuture future = new CompletableFuture<>(); + vertx.executeBlocking(() -> { + executorService.submit(() -> { + assertThatItRunsOnVirtualThread(); + future.complete(Vertx.currentContext()); + }); + return null; + }).toCompletionStage().toCompletableFuture().get(); + assertThat(future.get()).isNotNull(); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_20, disabledReason = "Virtual Threads are a preview feature starting from Java 20") + void invokeAllPropagatesVertxContext() throws ExecutionException, InterruptedException { + ExecutorService executorService = VirtualThreadsRecorder.getCurrent(); + Vertx vertx = Vertx.vertx(); + List> futures = vertx.executeBlocking(() -> { + return executorService.invokeAll(List.of((Callable) () -> { + assertThatItRunsOnVirtualThread(); + return Vertx.currentContext(); + }, (Callable) () -> { + assertThatItRunsOnVirtualThread(); + return Vertx.currentContext(); + })); + }).toCompletionStage().toCompletableFuture().get(); + assertThat(futures).allSatisfy(contextFuture -> assertThat(contextFuture.get()).isNotNull()); + } + public static void assertThatItRunsOnVirtualThread() { // We cannot depend on a Java 20. try { diff --git a/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/FilteredResource.java b/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/FilteredResource.java index e3794ae64eb42..783e622999b91 100644 --- a/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/FilteredResource.java +++ b/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/FilteredResource.java @@ -1,5 +1,9 @@ package io.quarkus.virtual.rr; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -7,7 +11,9 @@ import org.jboss.logmanager.MDC; +import io.quarkus.arc.Arc; import io.quarkus.test.vertx.VirtualThreadsAssertions; +import io.quarkus.virtual.threads.VirtualThreads; import io.smallrye.common.annotation.RunOnVirtualThread; import io.vertx.core.Vertx; @@ -17,14 +23,29 @@ public class FilteredResource { @Inject Counter counter; + @Inject + @VirtualThreads + ExecutorService vt; + @GET @RunOnVirtualThread - public Response filtered() { + public Response filtered() throws ExecutionException, InterruptedException { VirtualThreadsAssertions.assertEverything(); // Request scope assert counter.increment() == 2; + // Request scope propagated + assert vt.submit(() -> counter.increment()).get() == 3; + + // Request scope active + assert Arc.container().requestContext().isActive(); + assert vt.submit(() -> Arc.container().requestContext().isActive()).get(); + + CompletableFuture requestContextActive = new CompletableFuture<>(); + vt.execute(() -> requestContextActive.complete(Arc.container().requestContext().isActive())); + assert requestContextActive.get(); + // DC assert Vertx.currentContext().getLocal("filter").equals("test"); Vertx.currentContext().putLocal("test", "test test"); diff --git a/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/Filters.java b/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/Filters.java index e3878252d4927..8d75bd0007627 100644 --- a/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/Filters.java +++ b/integration-tests/virtual-threads/resteasy-reactive-virtual-threads/src/main/java/io/quarkus/virtual/rr/Filters.java @@ -27,7 +27,7 @@ public void getFilter(ContainerResponseContext responseContext) { if (responseContext.getHeaders().get("X-filter") != null) { VirtualThreadsAssertions.assertEverything(); // the request filter, the method, and here. - assert CDI.current().select(Counter.class).get().increment() == 3; + assert CDI.current().select(Counter.class).get().increment() == 4; assert Vertx.currentContext().getLocal("test").equals("test test"); assert MDC.get("mdc").equals("test test"); } From 34c1a63baf5401d0d578a23a1a4deb4b841ce65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Sat, 17 Feb 2024 13:19:08 +0100 Subject: [PATCH 19/29] Perform security checks eagerly in RR on inherited endpoints (cherry picked from commit 37834cb44a0bc5101cdf60922c8dd0301efac822) --- .../test/security/DenyAllJaxRsTest.java | 33 +++++++++++++++++++ .../security/UnsecuredParentResource.java | 8 +++++ .../test/security/UnsecuredResource.java | 12 +++++++ .../security/UnsecuredResourceInterface.java | 24 ++++++++++++++ .../security/EagerSecurityHandler.java | 2 +- .../processor/ServerEndpointIndexer.java | 1 + .../startup/RuntimeResourceDeployment.java | 2 +- .../server/model/ServerResourceMethod.java | 9 +++++ .../spi/ResteasyReactiveResourceInfo.java | 18 +++++++++- 9 files changed, 106 insertions(+), 3 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java index 76f23ddcc8056..7c394c783545e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java @@ -91,6 +91,39 @@ public void shouldDenyUnannotatedOnParentClass() { assertStatus(path, 403, 401); } + @Test + public void shouldAllowAnnotatedParentEndpoint() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/parent-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldAllowAnnotatedEndpointOnInterface() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/interface-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldDenyUnannotatedOverriddenOnInterfaceImplementor() { + // @RolesAllowed on interface, however implementor overridden the endpoint method with @Path @GET + String path = "/unsecured/interface-overridden-declared-on-implementor"; + assertStatus(path, 403, 401); + } + + @Test + public void shouldAllowAnnotatedOverriddenEndpointDeclaredOnInterface() { + // @RolesAllowed on interface and implementor didn't declare endpoint declaring annotations @GET + String path = "/unsecured/interface-overridden-declared-on-interface"; + assertStatus(path, 200, 401); + // check that response comes from the overridden method + given().auth().preemptive() + .basic("admin", "admin").get(path) + .then() + .body(Matchers.is("implementor-response")); + } + @Test public void shouldDenyUnannotatedOnInterface() { String path = "/unsecured/defaultSecurityInterface"; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java index 8250d5a9bf9a6..d92e24a57d685 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,11 @@ public String defaultSecurityParent() { return "defaultSecurityParent"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/parent-annotated") + public String parentAnnotated() { + return "parent-annotated"; + } + } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java index 82622c3e585de..0a7cf03414d81 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java @@ -57,4 +57,16 @@ public UnsecuredSubResource sub() { public UnsecuredSubResource permitAllSub() { return new UnsecuredSubResource(); } + + @Override + public String interfaceOverriddenDeclaredOnInterface() { + return "implementor-response"; + } + + @GET + @Path("/interface-overridden-declared-on-implementor") + @Override + public String interfaceOverriddenDeclaredOnImplementor() { + return "implementor-response"; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java index 7be652f15ef8d..d08cedd9f40fb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,27 @@ default String defaultSecurityInterface() { return "defaultSecurityInterface"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-annotated") + default String interfaceAnnotated() { + return "interface-annotated"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-interface") + default String interfaceOverriddenDeclaredOnInterface() { + // this interface is overridden without @GET and @Path + return "interface-overridden-declared-on-interface"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-implementor") + default String interfaceOverriddenDeclaredOnImplementor() { + // this interface is overridden with @GET and @Path + return "interface-overridden-declared-on-implementor"; + } + } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java index f94c369070cf0..0285e4a1a2ad3 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java @@ -187,7 +187,7 @@ private static Map createEventPropsWithRoutingCtx(ResteasyReacti } static MethodDescription lazyMethodToMethodDescription(ResteasyReactiveResourceInfo lazyMethod) { - return new MethodDescription(lazyMethod.getResourceClass().getName(), + return new MethodDescription(lazyMethod.getActualDeclaringClassName(), lazyMethod.getName(), MethodDescription.typesAsStrings(lazyMethod.getParameterTypes())); } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index 5044f7eb4b3ee..07fb3fba149d4 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -183,6 +183,7 @@ protected ServerResourceMethod createResourceMethod(MethodInfo methodInfo, Class } } serverResourceMethod.setHandlerChainCustomizers(methodCustomizers); + serverResourceMethod.setActualDeclaringClassName(methodInfo.declaringClass().name().toString()); return serverResourceMethod; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 455355d6a1521..e1bb21511e916 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -189,7 +189,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, ResteasyReactiveResourceInfo lazyMethod = new ResteasyReactiveResourceInfo(method.getName(), resourceClass, parameterDeclaredUnresolvedTypes, classAnnotationNames, method.getMethodAnnotationNames(), - !defaultBlocking && !method.isBlocking()); + !defaultBlocking && !method.isBlocking(), method.getActualDeclaringClassName()); RuntimeInterceptorDeployment.MethodInterceptorContext interceptorDeployment = runtimeInterceptorDeployment .forMethod(method, lazyMethod); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java index 9c8620e9da341..e56dd9e8836f3 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java @@ -18,6 +18,7 @@ public class ServerResourceMethod extends ResourceMethod { private List handlerChainCustomizers = new ArrayList<>(); private ParameterExtractor customerParameterExtractor; + private String actualDeclaringClassName; public ServerResourceMethod() { } @@ -70,4 +71,12 @@ public ServerResourceMethod setCustomerParameterExtractor(ParameterExtractor cus this.customerParameterExtractor = customerParameterExtractor; return this; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } + + public void setActualDeclaringClassName(String actualDeclaringClassName) { + this.actualDeclaringClassName = actualDeclaringClassName; + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java index 5e8b3377e4d77..014668cab4ebe 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java @@ -26,21 +26,33 @@ public class ResteasyReactiveResourceInfo implements ResourceInfo { * If it's non-blocking method within the runtime that won't always default to blocking */ public final boolean isNonBlocking; - + /** + * This class name will only differ from {@link this#declaringClass} name when the {@link this#method} was inherited. + */ + private final String actualDeclaringClassName; private volatile Annotation[] classAnnotations; private volatile Method method; private volatile Annotation[] annotations; private volatile Type returnType; private volatile String methodId; + @Deprecated public ResteasyReactiveResourceInfo(String name, Class declaringClass, Class[] parameterTypes, Set classAnnotationNames, Set methodAnnotationNames, boolean isNonBlocking) { + this(name, declaringClass, parameterTypes, classAnnotationNames, methodAnnotationNames, isNonBlocking, + declaringClass.getName()); + } + + public ResteasyReactiveResourceInfo(String name, Class declaringClass, Class[] parameterTypes, + Set classAnnotationNames, Set methodAnnotationNames, boolean isNonBlocking, + String actualDeclaringClassName) { this.name = name; this.declaringClass = declaringClass; this.parameterTypes = parameterTypes; this.classAnnotationNames = classAnnotationNames; this.methodAnnotationNames = methodAnnotationNames; this.isNonBlocking = isNonBlocking; + this.actualDeclaringClassName = actualDeclaringClassName; } public String getName() { @@ -119,4 +131,8 @@ public String getMethodId() { } return methodId; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } } From 7374ac818ef0ee5cd921dc21d365242c606f97dd Mon Sep 17 00:00:00 2001 From: Alex Katlein Date: Mon, 19 Feb 2024 18:29:11 +0100 Subject: [PATCH 20/29] Fixed a prior change which erroneously pulled in all transitive deployment modules - Now it's back to the previous behavior (pulling in deployment modules declared as dependencies of other deployment modules) - Gradle extension plugin now verifies that all required deployment modules are specified in dependencies - Added integration test to make sure such transitive dependencies are resolved correctly (especially regarding exclusions) - Clarified documentation about validation of extensions (cherry picked from commit 62483ed4e49ca601d0fb45b65f38cddb8635bcc6) --- .../gradle/QuarkusExtensionPlugin.java | 6 +- devtools/gradle/gradle-model/build.gradle.kts | 1 + .../ConditionalDependenciesEnabler.java | 6 - devtools/gradle/gradle/libs.versions.toml | 4 +- .../src/main/asciidoc/writing-extensions.adoc | 10 +- integration-tests/gradle/pom.xml | 34 +++ .../build.gradle | 32 ++ .../gradle.properties | 2 + .../settings.gradle | 17 ++ .../main/java/org/acme/GreetingResource.java | 16 + .../src/main/java/org/acme/MyEntity.java | 14 + .../resources/META-INF/resources/index.html | 285 ++++++++++++++++++ .../src/main/resources/application.properties | 5 + .../java/org/acme/GreetingResourceIT.java | 8 + .../java/org/acme/GreetingResourceTest.java | 21 ++ .../deployment/build.gradle | 2 + ...usionInExtensionDependencyDevModeTest.java | 27 ++ 17 files changed, 475 insertions(+), 15 deletions(-) create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/build.gradle create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/gradle.properties create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/settings.gradle create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/GreetingResource.java create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/MyEntity.java create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/META-INF/resources/index.html create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/application.properties create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/native-test/java/org/acme/GreetingResourceIT.java create mode 100644 integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/test/java/org/acme/GreetingResourceTest.java create mode 100644 integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MavenExclusionInExtensionDependencyDevModeTest.java diff --git a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/QuarkusExtensionPlugin.java b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/QuarkusExtensionPlugin.java index 10b44971e697c..6f8cdb1146038 100644 --- a/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/QuarkusExtensionPlugin.java +++ b/devtools/gradle/gradle-extension-plugin/src/main/java/io/quarkus/extension/gradle/QuarkusExtensionPlugin.java @@ -56,11 +56,13 @@ private void registerTasks(Project project, QuarkusExtensionConfiguration quarku Configuration runtimeModuleClasspath = project.getConfigurations() .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + TaskProvider validateExtensionTask = tasks.register(VALIDATE_EXTENSION_TASK_NAME, + ValidateExtensionTask.class, quarkusExt, runtimeModuleClasspath); + TaskProvider extensionDescriptorTask = tasks.register(EXTENSION_DESCRIPTOR_TASK_NAME, ExtensionDescriptorTask.class, quarkusExt, mainSourceSet, runtimeModuleClasspath); - TaskProvider validateExtensionTask = tasks.register(VALIDATE_EXTENSION_TASK_NAME, - ValidateExtensionTask.class, quarkusExt, runtimeModuleClasspath); + extensionDescriptorTask.configure(task -> task.dependsOn(validateExtensionTask)); project.getPlugins().withType( JavaPlugin.class, diff --git a/devtools/gradle/gradle-model/build.gradle.kts b/devtools/gradle/gradle-model/build.gradle.kts index be5055edd3d8c..da71708ccb97c 100644 --- a/devtools/gradle/gradle-model/build.gradle.kts +++ b/devtools/gradle/gradle-model/build.gradle.kts @@ -4,6 +4,7 @@ plugins { dependencies { compileOnly(libs.kotlin.gradle.plugin.api) + gradleApi() } group = "io.quarkus" diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index 4d00b1055fcdd..dd34c24d8d6b1 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -102,12 +102,6 @@ private void collectConditionalDependencies(Set runtimeArtifac queueConditionalDependency(extension, conditionalDep); } } - - // If the extension doesn't have any conditions we just enable it by default - if (extension.getDependencyConditions().isEmpty()) { - extension.setConditional(true); - enableConditionalDependency(extension.getExtensionId()); - } } } } diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index af0d775fdf467..5cb7cc25790ab 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -22,8 +22,8 @@ quarkus-project-core-extension-codestarts = { module = "io.quarkus:quarkus-proje kotlin-gradle-plugin-api = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" } smallrye-config-yaml = { module = "io.smallrye.config:smallrye-config-source-yaml", version.ref = "smallrye-config" } -jackson-databind = {module="com.fasterxml.jackson.core:jackson-databind"} -jackson-dataformat-yaml = {module="com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"} +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind" } +jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" } junit-bom = { module = "org.junit:junit-bom", version.ref = "junit5" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api" } diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 36f38ed605e3e..9ce12cb7c48e7 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -384,8 +384,12 @@ Your extension project should be setup as a multi-module project with two submod Your runtime artifact should depend on `io.quarkus:quarkus-core`, and possibly the runtime artifacts of other Quarkus modules if you want to use functionality provided by them. + Your deployment time module should depend on `io.quarkus:quarkus-core-deployment`, your runtime artifact, -and possibly the deployment artifacts of other Quarkus modules if you want to use functionality provided by them. +and the deployment artifacts of any other Quarkus extensions your own extension depends on. This is essential, otherwise any transitively +pulled in extensions will not provide their full functionality. + +NOTE: The Maven and Gradle plugins will validate this for you and alert you to any deployment artifacts you might have forgotten to add. [WARNING] ==== @@ -573,10 +577,6 @@ dependencies { } ---- -[WARNING] -==== -This plugin is still experimental, it does not validate the extension dependencies as the equivalent Maven plugin does. -==== [[build-step-processors]] === Build Step Processors diff --git a/integration-tests/gradle/pom.xml b/integration-tests/gradle/pom.xml index 7cd72302039e2..610218d8b61aa 100644 --- a/integration-tests/gradle/pom.xml +++ b/integration-tests/gradle/pom.xml @@ -104,6 +104,10 @@ io.quarkus quarkus-hibernate-orm-panache + + io.quarkus + quarkus-hibernate-reactive-panache + io.quarkus quarkus-hibernate-search-orm-elasticsearch @@ -136,6 +140,10 @@ io.quarkus quarkus-kubernetes-client + + io.quarkus + quarkus-reactive-pg-client + io.quarkus quarkus-resteasy @@ -242,6 +250,19 @@ + + io.quarkus + quarkus-hibernate-reactive-panache-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-hibernate-search-orm-elasticsearch-deployment @@ -307,6 +328,19 @@ + + io.quarkus + quarkus-reactive-pg-client-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-resteasy-deployment diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/build.gradle b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/build.gradle new file mode 100644 index 0000000000000..21deaf7f0d7ca --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java' + id 'io.quarkus' +} + +repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() +} + +dependencies { + implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'io.quarkus:quarkus-resteasy-reactive-jackson' + implementation 'io.quarkus:quarkus-hibernate-reactive-panache' + implementation 'io.quarkus:quarkus-reactive-pg-client' + implementation 'io.quarkus:quarkus-arc' + implementation 'io.quarkus:quarkus-resteasy-reactive' + testImplementation 'io.quarkus:quarkus-junit5' + testImplementation 'io.rest-assured:rest-assured' +} + +group 'org.acme' +version '1.0.0-SNAPSHOT' + +test { + systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" +} diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/gradle.properties b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/gradle.properties new file mode 100644 index 0000000000000..ec2b6ef199c2c --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/gradle.properties @@ -0,0 +1,2 @@ +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformGroupId=io.quarkus diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/settings.gradle b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/settings.gradle new file mode 100644 index 0000000000000..b2d511f9f6a1a --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/settings.gradle @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } +} + +rootProject.name='code-with-quarkus' diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/GreetingResource.java b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/GreetingResource.java new file mode 100644 index 0000000000000..6938062ec8ff7 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/GreetingResource.java @@ -0,0 +1,16 @@ +package org.acme; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "Hello from RESTEasy Reactive"; + } +} diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/MyEntity.java b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/MyEntity.java new file mode 100644 index 0000000000000..a18f8d226cf27 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/java/org/acme/MyEntity.java @@ -0,0 +1,14 @@ + +package org.acme; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.util.UUID; + +@Entity +public class MyEntity { + @Id + @GeneratedValue + private UUID id; +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/META-INF/resources/index.html b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..bd66dc4bb5121 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,285 @@ + + + + + code-with-quarkus - 1.0.0-SNAPSHOT + + + +
+
+
+ + + + + quarkus_logo_horizontal_rgb_1280px_reverse + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+

You just made a Quarkus application.

+

This page is served by Quarkus.

+ Visit the Dev UI +

This page: src/main/resources/META-INF/resources/index.html

+

App configuration: src/main/resources/application.properties

+

Static assets: src/main/resources/META-INF/resources/

+

Code: src/main/java

+

Generated starter code:

+
    +
  • + RESTEasy Reactive Easily start your Reactive RESTful Web Services +
    @Path: /hello +
    Related guide +
  • + +
+
+
+

Selected extensions

+
    +
  • RESTEasy Reactive Jackson
  • +
  • Hibernate Reactive with Panache
  • +
  • Reactive PostgreSQL client (guide)
  • +
+
Documentation
+

Practical step-by-step guides to help you achieve a specific goal. Use them to help get your work + done.

+
Set up your IDE
+

Everyone has a favorite IDE they like to use to code. Learn how to configure yours to maximize your + Quarkus productivity.

+
+
+
+ + diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/application.properties b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/application.properties new file mode 100644 index 0000000000000..abaaf7722ef5e --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/main/resources/application.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind = postgresql +quarkus.datasource.username = quarkus_test +quarkus.datasource.password = quarkus_test +quarkus.datasource.reactive.url = vertx-reactive:postgresql://localhost:5432/quarkus_test +#quarkus.datasource.jdbc=false \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/native-test/java/org/acme/GreetingResourceIT.java b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/native-test/java/org/acme/GreetingResourceIT.java new file mode 100644 index 0000000000000..cfa9d1b1aff2b --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/native-test/java/org/acme/GreetingResourceIT.java @@ -0,0 +1,8 @@ +package org.acme; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class GreetingResourceIT extends GreetingResourceTest { + // Execute the same tests but in packaged mode. +} diff --git a/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/test/java/org/acme/GreetingResourceTest.java b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/test/java/org/acme/GreetingResourceTest.java new file mode 100644 index 0000000000000..1e0da3846c90e --- /dev/null +++ b/integration-tests/gradle/src/main/resources/maven-exclusion-in-extension-dependency/src/test/java/org/acme/GreetingResourceTest.java @@ -0,0 +1,21 @@ +package org.acme; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class GreetingResourceTest { + @Test + void testHelloEndpoint() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello from RESTEasy Reactive")); + } + +} diff --git a/integration-tests/gradle/src/main/resources/multi-composite-build-extensions-project/extensions/another-example-extension/deployment/build.gradle b/integration-tests/gradle/src/main/resources/multi-composite-build-extensions-project/extensions/another-example-extension/deployment/build.gradle index 2114fbe38a983..4e2d4fa1829d5 100644 --- a/integration-tests/gradle/src/main/resources/multi-composite-build-extensions-project/extensions/another-example-extension/deployment/build.gradle +++ b/integration-tests/gradle/src/main/resources/multi-composite-build-extensions-project/extensions/another-example-extension/deployment/build.gradle @@ -13,6 +13,8 @@ dependencies { api project(':another-example-extension') // why: https://quarkus.io/guides/building-my-first-extension + implementation ('org.acme.extensions:example-extension-deployment') + implementation 'io.quarkus:quarkus-core-deployment' implementation 'io.quarkus:quarkus-arc-deployment' implementation ('org.acme.libs:libraryB') diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MavenExclusionInExtensionDependencyDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MavenExclusionInExtensionDependencyDevModeTest.java new file mode 100644 index 0000000000000..ef27b3d10c9b2 --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/MavenExclusionInExtensionDependencyDevModeTest.java @@ -0,0 +1,27 @@ +package io.quarkus.gradle.devmode; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This makes sure that exclusions in POM files of deployment modules are respected. + *

+ * One case where this is critical is when using quarkus-hibernate-reactive. Its deployment + * module takes care of pulling in quarkus-hibernate-orm-deployment where it excludes + * Agroal and Narayana. + *

+ * If that exclusion isn't taken into account by the Gradle plugin, it pulls in + * quarkus-hibernate-orm-deployment on its own, where those other modules aren't excluded, + * which leads to a failure at runtime because Agroal tries to initialize and looks + * for a JDBC driver which isn't typically available in reactive DB scenarios. + */ +public class MavenExclusionInExtensionDependencyDevModeTest extends QuarkusDevGradleTestBase { + @Override + protected String projectDirectoryName() { + return "maven-exclusion-in-extension-dependency"; + } + + @Override + protected void testDevMode() throws Exception { + assertThat(getHttpResponse("/hello")).contains("Hello"); + } +} From 5e1e0aacbf27a1892acbfeca561aa81b6178794d Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Mon, 19 Feb 2024 20:59:35 +0100 Subject: [PATCH 21/29] Add config flag to disable jacoco (cherry picked from commit 5236c49f8ea015d39fe64d0a728c52da0ca7f9b8) --- .../java/io/quarkus/jacoco/deployment/JacocoProcessor.java | 7 +++++++ .../main/java/io/quarkus/jacoco/runtime/JacocoConfig.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java index c03429eaee244..e8f403b855ea7 100644 --- a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java +++ b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java @@ -15,6 +15,7 @@ import org.jacoco.core.instr.Instrumenter; import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator; import org.jboss.jandex.ClassInfo; +import org.jboss.logging.Logger; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.workspace.SourceDir; @@ -36,6 +37,8 @@ public class JacocoProcessor { + private static final Logger log = Logger.getLogger(JacocoProcessor.class); + @BuildStep(onlyIf = IsTest.class) FeatureBuildItem feature() { return new FeatureBuildItem("jacoco"); @@ -53,6 +56,10 @@ void transformerBuildItem(BuildProducer transforme //no code coverage for continuous testing, it does not really make sense return; } + if (!config.enabled) { + log.debug("quarkus-jacoco is disabled via config"); + return; + } String dataFile = getFilePath(config.dataFile, outputTargetBuildItem.getOutputDirectory(), JacocoConfig.JACOCO_QUARKUS_EXEC); diff --git a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java index d9beb42898049..86aaf74c3f5fb 100644 --- a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java +++ b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/JacocoConfig.java @@ -16,6 +16,13 @@ public class JacocoConfig { public static final String TARGET_JACOCO_QUARKUS_EXEC = "target/" + JACOCO_QUARKUS_EXEC; public static final String TARGET_JACOCO_REPORT = "target/" + JACOCO_REPORT; + /** + * Whether or not the jacoco extension is enabled. Disabling it can come in handy when runnig tests in IDEs that do their + * own jacoco instrumentation, e.g. EclEmma in Eclipse. + */ + @ConfigItem(defaultValue = "true") + public boolean enabled; + /** * The jacoco data file. * The path can be relative (to the module) or absolute. From ca7c1bd37e57db17a8c4a0f9edbfae80bf0b5bf7 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 16 Feb 2024 19:08:37 +0000 Subject: [PATCH 22/29] Check the code flow access token after ID token (cherry picked from commit b8bdad1004716ab473f377f06fb01a58349fccc9) --- .../runtime/AbstractJsonObjectResponse.java | 2 +- .../oidc/runtime/OidcIdentityProvider.java | 86 ++++++++++--------- .../runtime/OidcTokenCredentialProducer.java | 38 +++++++- .../io/quarkus/oidc/runtime/OidcUtils.java | 5 ++ .../CodeFlowTokenIntrospectionResource.java | 6 +- .../keycloak/CodeFlowAuthorizationTest.java | 4 +- 6 files changed, 94 insertions(+), 47 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java index 9dc01cc51c156..416848f07fcf2 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractJsonObjectResponse.java @@ -58,7 +58,7 @@ public Object get(String name) { } public boolean contains(String propertyName) { - return json.containsKey(propertyName) && !json.isNull(propertyName); + return json != null && json.containsKey(propertyName) && !json.isNull(propertyName); } public Set getPropertyNames() { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 711baa25e5435..5ecd2c072296a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -46,7 +46,6 @@ public class OidcIdentityProvider implements IdentityProvider NULL_CODE_ACCESS_TOKEN_UNI = Uni.createFrom().nullItem(); - private static final String CODE_ACCESS_TOKEN_RESULT = "code_flow_access_token_result"; protected final DefaultTenantConfigResolver tenantResolver; private final BlockingTaskRunner uniVoidOidcContext; @@ -149,30 +148,7 @@ public Uni apply(UserInfo userInfo, Throwable t) { isIdToken(request), null); } - // Verify Code Flow access token first if it is available and has to be verified. - // It may be refreshed if it has or has nearly expired - Uni codeAccessTokenUni = verifyCodeFlowAccessTokenUni(requestData, request, - resolvedContext, - null); - return codeAccessTokenUni.onItemOrFailure().transformToUni( - new BiFunction>() { - @Override - public Uni apply(TokenVerificationResult codeAccessTokenResult, Throwable t) { - if (t != null) { - return Uni.createFrom().failure(t instanceof AuthenticationFailedException ? t - : new AuthenticationFailedException(t)); - } - if (codeAccessTokenResult != null) { - if (tokenAutoRefreshPrepared(codeAccessTokenResult, requestData, - resolvedContext.oidcConfig)) { - return Uni.createFrom().failure(new TokenAutoRefreshException(null)); - } - requestData.put(CODE_ACCESS_TOKEN_RESULT, codeAccessTokenResult); - } - return getUserInfoAndCreateIdentity(primaryTokenUni, requestData, request, resolvedContext); - } - }); - + return getUserInfoAndCreateIdentity(primaryTokenUni, requestData, request, resolvedContext); } } @@ -191,7 +167,7 @@ public Uni apply(TokenVerificationResult codeAccessToken, Thro } if (codeAccessToken != null) { - requestData.put(CODE_ACCESS_TOKEN_RESULT, codeAccessToken); + requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessToken); } Uni tokenUni = verifyTokenUni(requestData, resolvedContext, @@ -217,7 +193,8 @@ public Uni apply(TokenVerificationResult result, Throwable t) } private Uni getUserInfoAndCreateIdentity(Uni tokenUni, - Map requestData, TokenAuthenticationRequest request, + Map requestData, + TokenAuthenticationRequest request, TenantConfigContext resolvedContext) { return tokenUni.onItemOrFailure() @@ -227,21 +204,49 @@ public Uni apply(TokenVerificationResult result, Throwable t) if (t != null) { return Uni.createFrom().failure(new AuthenticationFailedException(t)); } - if (resolvedContext.oidcConfig.authentication.isUserInfoRequired().orElse(false)) { - return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure().transformToUni( - new BiFunction>() { - @Override - public Uni apply(UserInfo userInfo, Throwable t) { - if (t != null) { - return Uni.createFrom().failure(new AuthenticationFailedException(t)); + + Uni codeAccessTokenUni = verifyCodeFlowAccessTokenUni(requestData, request, + resolvedContext, + null); + return codeAccessTokenUni.onItemOrFailure().transformToUni( + new BiFunction>() { + @Override + public Uni apply(TokenVerificationResult codeAccessTokenResult, + Throwable t) { + if (t != null) { + return Uni.createFrom().failure(t instanceof AuthenticationFailedException ? t + : new AuthenticationFailedException(t)); + } + if (codeAccessTokenResult != null) { + if (tokenAutoRefreshPrepared(codeAccessTokenResult, requestData, + resolvedContext.oidcConfig)) { + return Uni.createFrom().failure(new TokenAutoRefreshException(null)); } + requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessTokenResult); + } + + if (resolvedContext.oidcConfig.authentication.isUserInfoRequired().orElse(false)) { + return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure() + .transformToUni( + new BiFunction>() { + @Override + public Uni apply(UserInfo userInfo, + Throwable t) { + if (t != null) { + return Uni.createFrom() + .failure(new AuthenticationFailedException(t)); + } + return createSecurityIdentityWithOidcServer(result, + requestData, request, + resolvedContext, userInfo); + } + }); + } else { return createSecurityIdentityWithOidcServer(result, requestData, request, - resolvedContext, userInfo); + resolvedContext, null); } - }); - } else { - return createSecurityIdentityWithOidcServer(result, requestData, request, resolvedContext, null); - } + } + }); } }); @@ -405,7 +410,8 @@ private static JsonObject getRolesJson(Map requestData, TenantCo rolesJson = new JsonObject(userInfo.getJsonObject().toString()); } else if (tokenCred instanceof IdTokenCredential && resolvedContext.oidcConfig.roles.source.get() == Source.accesstoken) { - rolesJson = ((TokenVerificationResult) requestData.get(CODE_ACCESS_TOKEN_RESULT)).localVerificationResult; + rolesJson = ((TokenVerificationResult) requestData + .get(OidcUtils.CODE_ACCESS_TOKEN_RESULT)).localVerificationResult; if (rolesJson == null) { // JSON token representation may be null not only if it is an opaque access token // but also if it is JWT and no JWK with a matching kid is available, asynchronous diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java index 2c5513e10aed8..fff629c7bffb5 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTokenCredentialProducer.java @@ -9,6 +9,7 @@ import org.jboss.logging.Logger; import io.quarkus.oidc.AccessTokenCredential; +import io.quarkus.oidc.IdToken; import io.quarkus.oidc.IdTokenCredential; import io.quarkus.oidc.RefreshToken; import io.quarkus.oidc.TokenIntrospection; @@ -78,13 +79,43 @@ UserInfo currentUserInfo() { } /** - * The producer method for the current UserInfo + * The producer method for the ID token TokenIntrospection only. * - * @return the user info + * @return the ID token introspection */ @Produces @RequestScoped - TokenIntrospection currentTokenIntrospection() { + @IdToken + TokenIntrospection idTokenIntrospection() { + return tokenIntrospectionFromIdentityAttribute(); + } + + /** + * The producer method for the current TokenIntrospection. + *

+ * This TokenIntrospection always represents the bearer access token introspection when the bearer access tokens + * are used. + *

+ * In case of the authorization code flow, it represents a code flow access token introspection + * if it has been enabled by setting the `quarkus.oidc.authentication.verify-access-token` property to `true` + * and an ID token introspection otherwise. Use the `@IdToken` qualifier if both ID and code flow access tokens + * must be introspected. + * + * @return the token introspection + */ + @Produces + @RequestScoped + TokenIntrospection tokenIntrospection() { + TokenVerificationResult codeFlowAccessTokenResult = (TokenVerificationResult) identity + .getAttribute(OidcUtils.CODE_ACCESS_TOKEN_RESULT); + if (codeFlowAccessTokenResult == null) { + return tokenIntrospectionFromIdentityAttribute(); + } else { + return codeFlowAccessTokenResult.introspectionResult; + } + } + + TokenIntrospection tokenIntrospectionFromIdentityAttribute() { TokenIntrospection introspection = (TokenIntrospection) identity.getAttribute(OidcUtils.INTROSPECTION_ATTRIBUTE); if (introspection == null) { LOG.trace("TokenIntrospection is null"); @@ -92,4 +123,5 @@ TokenIntrospection currentTokenIntrospection() { } return introspection; } + } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index a1d4c7f6aacf4..5c41edb561e44 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -81,6 +81,7 @@ public final class OidcUtils { public static final Integer MAX_COOKIE_VALUE_LENGTH = 4096; public static final String POST_LOGOUT_COOKIE_NAME = "q_post_logout"; static final String UNDERSCORE = "_"; + static final String CODE_ACCESS_TOKEN_RESULT = "code_flow_access_token_result"; static final Uni VOID_UNI = Uni.createFrom().voidItem(); static final BlockingTaskRunner deleteTokensRequestContext = new BlockingTaskRunner(); @@ -347,6 +348,10 @@ static QuarkusSecurityIdentity validateAndCreateIdentity(Map req setSecurityIdentityConfigMetadata(builder, resolvedContext); setBlockingApiAttribute(builder, vertxContext); setTenantIdAttribute(builder, config); + TokenVerificationResult codeFlowAccessTokenResult = (TokenVerificationResult) requestData.get(CODE_ACCESS_TOKEN_RESULT); + if (codeFlowAccessTokenResult != null) { + builder.addAttribute(CODE_ACCESS_TOKEN_RESULT, codeFlowAccessTokenResult); + } return builder.build(); } diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowTokenIntrospectionResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowTokenIntrospectionResource.java index 4bfd58e5de927..86825d36ddaac 100644 --- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowTokenIntrospectionResource.java +++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowTokenIntrospectionResource.java @@ -4,6 +4,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import io.quarkus.oidc.TokenIntrospection; import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; @@ -14,8 +15,11 @@ public class CodeFlowTokenIntrospectionResource { @Inject SecurityIdentity identity; + @Inject + TokenIntrospection tokenIntrospection; + @GET public String access() { - return identity.getPrincipal().getName(); + return identity.getPrincipal().getName() + ":" + tokenIntrospection.getUsername(); } } diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java index a21b2f14fc117..56e7bae3d7d58 100644 --- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java +++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java @@ -287,12 +287,12 @@ public void testCodeFlowTokenIntrospection() throws Exception { TextPage textPage = form.getInputByValue("login").click(); - assertEquals("alice", textPage.getContent()); + assertEquals("alice:alice", textPage.getContent()); // refresh Thread.sleep(3000); textPage = webClient.getPage("http://localhost:8081/code-flow-token-introspection"); - assertEquals("admin", textPage.getContent()); + assertEquals("admin:admin", textPage.getContent()); webClient.getCookieManager().clearCookies(); } From 45c86cc178a943937a71b0f34692ea4711d738cc Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Mon, 19 Feb 2024 14:53:32 +0100 Subject: [PATCH 23/29] Quartz - prevent memory leak when Job instance is a @Dependent bean (cherry picked from commit 6dcfaca7b62b6119ed454e8fafc58fe389cbefcc) --- .../quartz/test/DependentBeanJobTest.java | 126 ++++++++++++++++++ .../quarkus/quartz/runtime/CdiAwareJob.java | 36 +++++ .../quartz/runtime/QuartzSchedulerImpl.java | 4 +- 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/CdiAwareJob.java diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java new file mode 100644 index 0000000000000..cd6d828a1a1c9 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java @@ -0,0 +1,126 @@ +package io.quarkus.quartz.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; + +import io.quarkus.test.QuarkusUnitTest; + +public class DependentBeanJobTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Service.class, MyJob.class) + .addAsResource(new StringAsset("quarkus.quartz.start-mode=forced"), + "application.properties")); + + @Inject + Scheduler quartz; + + @Inject + Service service; + + @Test + public void testDependentBeanJobDestroyed() throws SchedulerException, InterruptedException { + assertEquals(0, MyJob.timesConstructed); + assertEquals(0, MyJob.timesDestroyed); + // prepare latch, schedule 10 one-off jobs, assert + CountDownLatch latch = service.initializeLatch(10); + for (int i = 0; i < 10; i++) { + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity("myTrigger" + i, "myGroup") + .startNow() + .build(); + JobDetail job = JobBuilder.newJob(MyJob.class) + .withIdentity("myJob" + i, "myGroup") + .build(); + quartz.scheduleJob(job, trigger); + } + assertTrue(latch.await(5, TimeUnit.SECONDS), "Latch count: " + latch.getCount()); + assertEquals(10, MyJob.timesConstructed); + assertEquals(10, MyJob.timesDestroyed); + + // now try the same with repeating job triggering three times + latch = service.initializeLatch(3); + JobDetail job = JobBuilder.newJob(MyJob.class) + .withIdentity("myRepeatingJob", "myGroup") + .build(); + Trigger trigger = TriggerBuilder.newTrigger() + .withIdentity("myRepeatingTrigger", "myGroup") + .startNow() + .withSchedule( + SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMilliseconds(333) + .withRepeatCount(3)) + .build(); + quartz.scheduleJob(job, trigger); + + assertTrue(latch.await(2, TimeUnit.SECONDS), "Latch count: " + latch.getCount()); + assertEquals(13, MyJob.timesConstructed); + assertEquals(13, MyJob.timesDestroyed); + } + + @ApplicationScoped + public static class Service { + + volatile CountDownLatch latch; + + public CountDownLatch initializeLatch(int latchCountdown) { + this.latch = new CountDownLatch(latchCountdown); + return latch; + } + + public void execute() { + latch.countDown(); + } + + } + + @Dependent + static class MyJob implements Job { + + public static volatile int timesConstructed = 0; + public static volatile int timesDestroyed = 0; + + @Inject + Service service; + + @PostConstruct + void postConstruct() { + timesConstructed++; + } + + @PreDestroy + void preDestroy() { + timesDestroyed++; + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + service.execute(); + } + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/CdiAwareJob.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/CdiAwareJob.java new file mode 100644 index 0000000000000..2618bb4379878 --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/CdiAwareJob.java @@ -0,0 +1,36 @@ +package io.quarkus.quartz.runtime; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Instance; + +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.Scheduler; +import org.quartz.spi.TriggerFiredBundle; + +/** + * An abstraction allowing proper destruction of Job instances in case they are dependent beans. + * According to {@link org.quartz.spi.JobFactory#newJob(TriggerFiredBundle, Scheduler)}, a new job instance is created for every + * trigger. + * We will therefore create a new dependent bean for every trigger and destroy it afterwards. + */ +class CdiAwareJob implements Job { + + private final Instance.Handle jobInstanceHandle; + + public CdiAwareJob(Instance.Handle jobInstanceHandle) { + this.jobInstanceHandle = jobInstanceHandle; + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + try { + jobInstanceHandle.get().execute(context); + } finally { + if (jobInstanceHandle.getBean().getScope().equals(Dependent.class)) { + jobInstanceHandle.destroy(); + } + } + } +} 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 60e76ea042e8e..e2b9638cd387a 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 @@ -1243,10 +1243,10 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler Scheduler) thr // Get the original class from an intercepted bean class jobClass = (Class) jobClass.getSuperclass(); } - Instance instance = jobs.select(jobClass); + Instance instance = jobs.select(jobClass); if (instance.isResolvable()) { // This is a job backed by a CDI bean - return jobWithSpanWrapper((Job) instance.get()); + return jobWithSpanWrapper(new CdiAwareJob(instance.getHandle())); } // Instantiate a plain job class return jobWithSpanWrapper(super.newJob(bundle, Scheduler)); From f1ab295e7ce4b405c6d33598303e799b2cdd6d48 Mon Sep 17 00:00:00 2001 From: Hendrik Schmitz Date: Mon, 19 Feb 2024 18:54:25 +0100 Subject: [PATCH 24/29] Update commons-compress version to mitigate CVE-2024-25710 (cherry picked from commit 0f6ffef6be9ad81509d921cd49ba3a05e161e7d4) --- bom/application/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5d64744142452..e05bd50c057cf 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -198,7 +198,7 @@ 2.1 4.7.5 1.1.0 - 1.25.0 + 1.26.0 1.11.0 2.10.1 1.1.2.Final diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 299c139b9012e..fae2bedfb564f 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -52,7 +52,7 @@ 2.16.1 4.0.1 5.10.2 - 1.25.0 + 1.26.0 3.5.3.Final 5.8.0 3.2.1 From bdac5c745117936594dfd1e10acb4312c1a55fa7 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 20 Feb 2024 14:30:45 +0000 Subject: [PATCH 25/29] Log resolved OIDC tenant id and how the bearer token is found (cherry picked from commit ff84d5d07e2e731f47863702505023baf29cfd70) --- .../oidc/runtime/BearerAuthenticationMechanism.java | 10 ++++++++++ .../oidc/runtime/OidcAuthenticationMechanism.java | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java index f6c22753ab98e..8869e9a9bdf90 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java @@ -2,6 +2,8 @@ import java.util.function.Function; +import org.jboss.logging.Logger; + import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.quarkus.oidc.AccessTokenCredential; @@ -15,14 +17,17 @@ import io.vertx.ext.web.RoutingContext; public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMechanism { + private static final Logger LOG = Logger.getLogger(BearerAuthenticationMechanism.class); public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) { + LOG.debug("Starting a bearer access token authentication"); String token = extractBearerToken(context, oidcTenantConfig); // if a bearer token is provided try to authenticate if (token != null) { return authenticate(identityProviderManager, context, new AccessTokenCredential(token)); } + LOG.debug("Bearer access token is not available"); return Uni.createFrom().nullItem(); } @@ -41,6 +46,7 @@ private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcC final HttpServerRequest request = context.request(); String header = oidcConfig.token.header.isPresent() ? oidcConfig.token.header.get() : HttpHeaders.AUTHORIZATION.toString(); + LOG.debugf("Looking for a token in the %s header", header); final String headerValue = request.headers().get(header); if (headerValue == null) { @@ -50,6 +56,10 @@ private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcC int idx = headerValue.indexOf(' '); final String scheme = idx > 0 ? headerValue.substring(0, idx) : null; + if (scheme != null) { + LOG.debugf("Authorization scheme: %s", scheme); + } + if (scheme == null && !header.equalsIgnoreCase(HttpHeaders.AUTHORIZATION.toString())) { return headerValue; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java index bd5c8ab18aeea..b17902078794c 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java @@ -6,6 +6,8 @@ import jakarta.enterprise.context.ApplicationScoped; +import org.jboss.logging.Logger; + import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.OidcTenantConfig.ApplicationType; @@ -23,6 +25,8 @@ @ApplicationScoped public class OidcAuthenticationMechanism implements HttpAuthenticationMechanism { + private static final Logger LOG = Logger.getLogger(OidcAuthenticationMechanism.class); + private static HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport( HttpCredentialTransport.Type.AUTHORIZATION_CODE, OidcConstants.CODE_FLOW_CODE); @@ -75,6 +79,7 @@ public OidcTenantConfig apply(OidcTenantConfig oidcTenantConfig) { if (oidcTenantConfig == null) { throw new OIDCException("Tenant configuration has not been resolved"); } + LOG.debugf("Resolved OIDC tenant id: %s", oidcTenantConfig.tenantId.orElse(OidcUtils.DEFAULT_TENANT_ID)); return oidcTenantConfig; }; }); From d4e9086321fea72b4aaf688a7a4cac85d29a7ea6 Mon Sep 17 00:00:00 2001 From: Ozan Gunalp Date: Wed, 7 Feb 2024 22:23:00 +0100 Subject: [PATCH 26/29] Disable messaging observation by default for backwards compatibility Fixes #38889 (cherry picked from commit 1921c452cd8a6a76ba8b546d3c779e1246b4ec27) --- docs/src/main/asciidoc/kafka.adoc | 25 ++++++++++++++++--- .../SmallRyeReactiveMessagingProcessor.java | 6 +++++ .../src/main/resources/application.properties | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 120a2977b7e52..35255f98a82a7 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -2048,14 +2048,31 @@ mp.messaging.incoming.data.tracing-enabled=false If the xref:telemetry-micrometer.adoc[Micrometer extension] is present, then Kafka producer and consumer clients metrics are exposed as Micrometer meters. -Per channel metrics are also exposed as Micrometer meters. -The number of messages produced or received per channel, acknowledgments and duration of processing are exposed. +=== Channel metrics -The messaging meters can be disabled: +Per channel metrics can also be gathered and exposed as Micrometer meters. +Following metrics can be gathered per channel, identified with the _channel_ tag: + +* `quarkus.messaging.message.count` : The number of messages produced or received +* `quarkus.messaging.message.acks` : The number of messages processed successfully +* `quarkus.messaging.message.failures` : The number of messages processed with failures +* `quarkus.messaging.message.duration` : The duration of the message processing. + +For backwards compatibility reasons channel metrics are not enabled by default and can be enabled with: + +[IMPORTANT] +==== +The https://smallrye.io/smallrye-reactive-messaging/latest/concepts/observability/[message observation] +depends on intercepting messages and therefore doesn't support channels consuming messages with +a custom message type such as `IncomingKafkaRecord`, `KafkaRecord`, `IncomingKafkaRecordBatch` or `KafkaRecordBatch`. + +The message interception, and observation, still work with channels consuming the generic `Message` type, +or custom payloads enabled by https://smallrye.io/smallrye-reactive-messaging/latest/concepts/converters/[converters]. +==== [source, properties] ---- -quarkus.micrometer.binder.messaging.enabled=false +smallrye.messaging.observation.enabled=true ---- diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index e4982848cd5a4..eada958ba8648 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -205,6 +205,12 @@ public void transform(AnnotationsTransformer.TransformationContext ctx) { }); } + @BuildStep + public void disableObservation(BuildProducer runtimeConfigProducer) { + runtimeConfigProducer.produce( + new RunTimeConfigurationDefaultBuildItem("smallrye.messaging.observation.enabled", "false")); + } + @BuildStep public void enableHealth(ReactiveMessagingBuildTimeConfig buildTimeConfig, BuildProducer producer) { diff --git a/integration-tests/reactive-messaging-kafka/src/main/resources/application.properties b/integration-tests/reactive-messaging-kafka/src/main/resources/application.properties index a4635331aff3c..d2945c59151da 100644 --- a/integration-tests/reactive-messaging-kafka/src/main/resources/application.properties +++ b/integration-tests/reactive-messaging-kafka/src/main/resources/application.properties @@ -46,3 +46,4 @@ mp.messaging.outgoing.request-reply.topic=request mp.messaging.outgoing.request-reply.reply.batch=true quarkus.micrometer.binder.messaging.enabled=true +smallrye.messaging.observation.enabled=true From 4d543383bdaeaf54a59c7b777df140e260f6c429 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 20 Feb 2024 15:07:34 +0100 Subject: [PATCH 27/29] SmallRye Health: terminate request context properly When the SmallRye Health route handler activates request context on its own, it also needs to terminate it. However, that termination needs to happen only after all health checks complete. That's what this commit does. (cherry picked from commit 650ec93a5990c1e1820c4cb49ba0e39b4ac1a28e) --- .../runtime/SmallRyeHealthHandlerBase.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java index e999375418769..c24dcfb60cc2b 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java @@ -28,18 +28,19 @@ abstract class SmallRyeHealthHandlerBase implements Handler { public void handle(RoutingContext ctx) { ManagedContext requestContext = Arc.container().requestContext(); if (requestContext.isActive()) { - doHandle(ctx); + doHandle(ctx, null); } else { requestContext.activate(); try { - doHandle(ctx); - } finally { + doHandle(ctx, requestContext); + } catch (Exception e) { requestContext.terminate(); + throw e; } } } - private void doHandle(RoutingContext ctx) { + private void doHandle(RoutingContext ctx, ManagedContext requestContext) { QuarkusHttpUser user = (QuarkusHttpUser) ctx.user(); if (user != null) { Arc.container().instance(CurrentIdentityAssociation.class).get().setIdentity(user.getSecurityIdentity()); @@ -48,6 +49,9 @@ private void doHandle(RoutingContext ctx) { Context context = Vertx.currentContext(); getHealth(reporter, ctx).emitOn(MutinyHelper.executor(context)) .subscribe().with(health -> { + if (requestContext != null) { + requestContext.terminate(); + } HttpServerResponse resp = ctx.response(); if (health.isDown()) { resp.setStatusCode(503); @@ -60,6 +64,10 @@ private void doHandle(RoutingContext ctx) { } catch (IOException e) { throw new UncheckedIOException(e); } + }, failure -> { + if (requestContext != null) { + requestContext.terminate(); + } }); } } From 943784b27ed68a07c1f8fa3b9c7bb927a12e2b35 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 20 Feb 2024 15:28:10 +0200 Subject: [PATCH 28/29] Ignore `ValidationSchema` that results in registering all models `ValidationSchema` is annotated with `@JsonDeserialize` which leads in its entire type hierarchy being registered for reflective access along with the corresponding methods. This essentially ends up registering all models as in kubernetes-client 6.9.0 ValidationSchema was augmented to implement `Editable`, which increases the reachable types in comparison to previous versions. Ignoring registrations for `ValidationSchema` aligns with what we already do for `KubeSchema`. Fixes https://github.com/quarkusio/quarkus/issues/38683 (cherry picked from commit 637ebeae5ecc85b33a0baf5d976b0e2a683d83f7) --- .../client/deployment/KubernetesClientProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java index 2ee0050374e00..aa0da573606ac 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java @@ -25,6 +25,7 @@ import io.fabric8.kubernetes.api.model.KubeSchema; import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.api.model.ValidationSchema; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.DefaultKubernetesClient; @@ -66,6 +67,7 @@ public class KubernetesClientProcessor { private static final DotName RESOURCE_EVENT_HANDLER = DotName .createSimple(io.fabric8.kubernetes.client.informers.ResourceEventHandler.class.getName()); private static final DotName KUBERNETES_RESOURCE = DotName.createSimple(KubernetesResource.class.getName()); + private static final DotName VALIDATION_SCHEMA = DotName.createSimple(ValidationSchema.class.getName()); private static final DotName KUBERNETES_RESOURCE_LIST = DotName .createSimple(KubernetesResourceList.class.getName()); private static final DotName KUBE_SCHEMA = DotName.createSimple(KubeSchema.class.getName()); @@ -190,6 +192,7 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui ignoredJsonDeserializationClasses.produce( new IgnoreJsonDeserializeClassBuildItem(KUBERNETES_RESOURCE_LIST)); ignoredJsonDeserializationClasses.produce(new IgnoreJsonDeserializeClassBuildItem(KUBERNETES_RESOURCE)); + ignoredJsonDeserializationClasses.produce(new IgnoreJsonDeserializeClassBuildItem(VALIDATION_SCHEMA)); final String[] deserializerClasses = combinedIndexBuildItem.getIndex() .getAllKnownSubclasses(DotName.createSimple("com.fasterxml.jackson.databind.JsonDeserializer")) From 4611ba630dfe6216aa6f1c05b9d2278299bdd4c6 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 20 Feb 2024 19:03:35 +0100 Subject: [PATCH 29/29] Attempt to fix flaky DependentBeanJobTest (cherry picked from commit a6f36e16229fa8672909f0cf7c8fc1880a4e42d2) --- .../quartz/test/DependentBeanJobTest.java | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java index cd6d828a1a1c9..c5c1db0c8f094 100644 --- a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/DependentBeanJobTest.java @@ -1,6 +1,5 @@ package io.quarkus.quartz.test; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.CountDownLatch; @@ -45,10 +44,10 @@ public class DependentBeanJobTest { @Test public void testDependentBeanJobDestroyed() throws SchedulerException, InterruptedException { - assertEquals(0, MyJob.timesConstructed); - assertEquals(0, MyJob.timesDestroyed); - // prepare latch, schedule 10 one-off jobs, assert - CountDownLatch latch = service.initializeLatch(10); + // prepare latches, schedule 10 one-off jobs, assert + CountDownLatch execLatch = service.initExecuteLatch(10); + CountDownLatch constructLatch = service.initConstructLatch(10); + CountDownLatch destroyedLatch = service.initDestroyedLatch(10); for (int i = 0; i < 10; i++) { Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger" + i, "myGroup") @@ -59,12 +58,14 @@ public void testDependentBeanJobDestroyed() throws SchedulerException, Interrupt .build(); quartz.scheduleJob(job, trigger); } - assertTrue(latch.await(5, TimeUnit.SECONDS), "Latch count: " + latch.getCount()); - assertEquals(10, MyJob.timesConstructed); - assertEquals(10, MyJob.timesDestroyed); + assertTrue(execLatch.await(2, TimeUnit.SECONDS), "Latch count: " + execLatch.getCount()); + assertTrue(constructLatch.await(2, TimeUnit.SECONDS), "Latch count: " + constructLatch.getCount()); + assertTrue(destroyedLatch.await(2, TimeUnit.SECONDS), "Latch count: " + destroyedLatch.getCount()); // now try the same with repeating job triggering three times - latch = service.initializeLatch(3); + execLatch = service.initExecuteLatch(3); + constructLatch = service.initConstructLatch(3); + destroyedLatch = service.initDestroyedLatch(3); JobDetail job = JobBuilder.newJob(MyJob.class) .withIdentity("myRepeatingJob", "myGroup") .build(); @@ -78,23 +79,43 @@ public void testDependentBeanJobDestroyed() throws SchedulerException, Interrupt .build(); quartz.scheduleJob(job, trigger); - assertTrue(latch.await(2, TimeUnit.SECONDS), "Latch count: " + latch.getCount()); - assertEquals(13, MyJob.timesConstructed); - assertEquals(13, MyJob.timesDestroyed); + assertTrue(execLatch.await(2, TimeUnit.SECONDS), "Latch count: " + execLatch.getCount()); + assertTrue(constructLatch.await(2, TimeUnit.SECONDS), "Latch count: " + constructLatch.getCount()); + assertTrue(destroyedLatch.await(2, TimeUnit.SECONDS), "Latch count: " + destroyedLatch.getCount()); } @ApplicationScoped public static class Service { - volatile CountDownLatch latch; + volatile CountDownLatch executeLatch; + volatile CountDownLatch constructedLatch; + volatile CountDownLatch destroyedLatch; - public CountDownLatch initializeLatch(int latchCountdown) { - this.latch = new CountDownLatch(latchCountdown); - return latch; + public CountDownLatch initExecuteLatch(int latchCountdown) { + this.executeLatch = new CountDownLatch(latchCountdown); + return executeLatch; + } + + public CountDownLatch initConstructLatch(int latchCountdown) { + this.constructedLatch = new CountDownLatch(latchCountdown); + return constructedLatch; + } + + public CountDownLatch initDestroyedLatch(int latchCountdown) { + this.destroyedLatch = new CountDownLatch(latchCountdown); + return destroyedLatch; } public void execute() { - latch.countDown(); + executeLatch.countDown(); + } + + public void constructedLatch() { + constructedLatch.countDown(); + } + + public void destroyedLatch() { + destroyedLatch.countDown(); } } @@ -102,20 +123,17 @@ public void execute() { @Dependent static class MyJob implements Job { - public static volatile int timesConstructed = 0; - public static volatile int timesDestroyed = 0; - @Inject Service service; @PostConstruct void postConstruct() { - timesConstructed++; + service.constructedLatch(); } @PreDestroy void preDestroy() { - timesDestroyed++; + service.destroyedLatch(); } @Override