From a30d82d0106274bcc26f8c568340a86ba67f9b6c Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Wed, 24 Jul 2024 17:33:42 -0400 Subject: [PATCH 01/35] Update from net.sourceforge.htmlunit to org.htmlunit (cherry picked from commit 752baa1bf2b0a049a1a692bf8c73529026550540) --- .../main/asciidoc/security-oidc-code-flow-authentication.adoc | 4 ++-- .../main/asciidoc/security-openid-connect-multitenancy.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc index fcccaf23740f6..97f340925e5d3 100644 --- a/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc @@ -1685,7 +1685,7 @@ Start by adding the following dependencies to your test project: .pom.xml ---- - net.sourceforge.htmlunit + org.htmlunit htmlunit @@ -1705,7 +1705,7 @@ Start by adding the following dependencies to your test project: [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -testImplementation("net.sourceforge.htmlunit:htmlunit") +testImplementation("org.htmlunit:htmlunit") testImplementation("io.quarkus:quarkus-junit5") ---- diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index d031ecdcd0071..bd551c0e14601 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -432,7 +432,7 @@ First, add the following dependencies: test - net.sourceforge.htmlunit + org.htmlunit htmlunit test @@ -443,7 +443,7 @@ First, add the following dependencies: ---- testImplementation("io.quarkus:quarkus-test-keycloak-server") testImplementation("io.rest-assured:rest-assured") -testImplementation("net.sourceforge.htmlunit:htmlunit") +testImplementation("org.htmlunit:htmlunit") ---- `quarkus-test-keycloak-server` provides a utility class `io.quarkus.test.keycloak.client.KeycloakTestClient` for acquiring the realm specific access tokens and which you can use with `RestAssured` for testing the `/{tenant}/bearer` endpoint expecting bearer access tokens. From 937991c46912a35247b50f70852b4c8f6ae81259 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 24 Jul 2024 11:52:36 +0300 Subject: [PATCH 02/35] Register sun.security.provider.NativePRNG# for reflection When instantiating a SecureRandom the constructor reflectively looks for the NativePRNG constructor and invokes it. Although the lookup succeeds without the explicit registration, it's better to explicitly request it. This also prevents getting a `MissingRegistrationError` when using `-H:+ThrowMissingRegistrationErrors` or `--exact-reachability-metadata`. Relates to https://github.com/quarkusio/quarkus/issues/41995 (cherry picked from commit 0e8f9cdc026c245958b03b5121902758e5040b10) --- .../deployment/SecureRandomProcessor.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/SecureRandomProcessor.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/SecureRandomProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/SecureRandomProcessor.java new file mode 100644 index 0000000000000..7eb9296a02446 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/SecureRandomProcessor.java @@ -0,0 +1,16 @@ +package io.quarkus.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; + +public class SecureRandomProcessor { + + @BuildStep + void registerReflectiveMethods(BuildProducer reflectiveMethods) { + // Called reflectively through java.security.SecureRandom.SecureRandom() + reflectiveMethods.produce(new ReflectiveMethodBuildItem("sun.security.provider.NativePRNG", "", + java.security.SecureRandomParameters.class)); + } + +} From 5ca3739534ea387aedfb3d8e6012d1fed381f58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sol=C3=B3rzano?= Date: Mon, 22 Jul 2024 02:26:07 +0200 Subject: [PATCH 03/35] Register fields for reflection in kubernetes-client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jorge Solórzano (cherry picked from commit 969351a1b00479a73a973b421a9c805633c43105) --- .../deployment/KubernetesClientProcessor.java | 65 ++++--------------- 1 file changed, 14 insertions(+), 51 deletions(-) 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 dc8370dfd862a..664d35fd6f90e 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 @@ -1,6 +1,5 @@ package io.quarkus.kubernetes.client.deployment; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -10,16 +9,12 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.Type; import org.jboss.logging.Logger; -import com.fasterxml.jackson.annotation.JsonFormat; - import io.fabric8.kubernetes.api.builder.VisitableBuilder; import io.fabric8.kubernetes.api.model.AnyType; import io.fabric8.kubernetes.api.model.IntOrString; @@ -75,9 +70,7 @@ public class KubernetesClientProcessor { private static final DotName KUBE_SCHEMA = DotName.createSimple(KubeSchema.class.getName()); private static final DotName VISITABLE_BUILDER = DotName.createSimple(VisitableBuilder.class.getName()); private static final DotName CUSTOM_RESOURCE = DotName.createSimple(CustomResource.class.getName()); - private static final String SERVICE_ACCOUNT = "ServiceAccount"; - private static final DotName JSON_FORMAT = DotName.createSimple(JsonFormat.class.getName()); private static final String[] EMPTY_STRINGS_ARRAY = new String[0]; @BuildStep @@ -159,30 +152,21 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui Collection visitableBuilderImpls = fullIndex.getAllKnownImplementors(VISITABLE_BUILDER); // default sizes determined experimentally - these are only set in order to prevent continuous expansion of the array list - List withoutFieldsRegistration = new ArrayList<>( - kubernetesResourceImpls.size() + kubernetesResourceListImpls.size()); - List withFieldsRegistration = new ArrayList<>(2); - List ignoreJsonDeserialization = new ArrayList<>( - kubernetesResourceImpls.size() + kubernetesResourceListImpls.size()); + int defSize = kubernetesResourceImpls.size() + kubernetesResourceListImpls.size() + visitableBuilderImpls.size(); + List withFieldsRegistration = new ArrayList<>(defSize); + List ignoreJsonDeserialization = new ArrayList<>(defSize); populateReflectionRegistrationLists(kubernetesResourceImpls, watchedClasses, ignoreJsonDeserialization, - withoutFieldsRegistration, withFieldsRegistration); populateReflectionRegistrationLists(kubernetesResourceListImpls, watchedClasses, ignoreJsonDeserialization, - withoutFieldsRegistration, withFieldsRegistration); populateReflectionRegistrationLists(visitableBuilderImpls, watchedClasses, ignoreJsonDeserialization, - withoutFieldsRegistration, withFieldsRegistration); if (!withFieldsRegistration.isEmpty()) { reflectiveClasses.produce(ReflectiveClassBuildItem - .builder(withFieldsRegistration.toArray(EMPTY_STRINGS_ARRAY)).weak(true).methods().fields() - .build()); - } - if (!withoutFieldsRegistration.isEmpty()) { - reflectiveClasses.produce(ReflectiveClassBuildItem - .builder(withoutFieldsRegistration.toArray(EMPTY_STRINGS_ARRAY)).weak(true).methods() + .builder(withFieldsRegistration.toArray(EMPTY_STRINGS_ARRAY)) + .weak().methods().fields() .build()); } @@ -233,9 +217,8 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui .sorted() .collect(Collectors.joining("\n")); log.debugv("Watched Classes:\n{0}", watchedClassNames); - List modelClasses = new ArrayList<>(withFieldsRegistration.size() + withoutFieldsRegistration.size()); + List modelClasses = new ArrayList<>(withFieldsRegistration.size()); modelClasses.addAll(withFieldsRegistration); - modelClasses.addAll(withoutFieldsRegistration); Collections.sort(modelClasses); log.debugv("Model Classes:\n{0}", String.join("\n", modelClasses)); } @@ -254,35 +237,15 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui private static void populateReflectionRegistrationLists(Collection kubernetesResourceImpls, Set watchedClasses, List ignoredJsonDeserializationClasses, - List withoutFieldsRegistration, List withFieldsRegistration) { - kubernetesResourceImpls - .stream() - .peek(c -> { - // we need to make sure that the Jackson extension does not try to fully register the model classes - // since we are going to register them weakly - ignoredJsonDeserializationClasses.add(c.name()); - }) - .filter(c -> !watchedClasses.contains(c.name())) - .map(c -> { - boolean registerFields = false; - List jsonFormatInstances = c.annotationsMap().get(JSON_FORMAT); - if (jsonFormatInstances != null) { - for (AnnotationInstance jsonFormatInstance : jsonFormatInstances) { - if (jsonFormatInstance.target().kind() == AnnotationTarget.Kind.FIELD) { - registerFields = true; - break; - } - } - } - return new AbstractMap.SimpleEntry<>(c.name(), registerFields); - }).forEach(e -> { - if (e.getValue()) { - withFieldsRegistration.add(e.getKey().toString()); - } else { - withoutFieldsRegistration.add(e.getKey().toString()); - } - }); + for (ClassInfo resource : kubernetesResourceImpls) { + // we need to make sure that the Jackson extension does not try to fully + // register the model classes since we are going to register them weakly + ignoredJsonDeserializationClasses.add(resource.name()); + if (!watchedClasses.contains(resource.name())) { + withFieldsRegistration.add(resource.name().toString()); + } + } } private void findWatchedClasses(final DotName implementedOrExtendedClass, From 10e384421da685e896352f160276da49cb2d3226 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Thu, 25 Jul 2024 16:58:41 +1000 Subject: [PATCH 04/35] Add support for CompletableFuture when using JsonRPC in Dev UI Signed-off-by: Phillip Kruger (cherry picked from commit 0b955496f8e16329c30f8e1569dc5c771de384c8) --- .../devui/runtime/comms/JsonRpcRouter.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java index bfcb43d40313a..e437eb8ccc71c 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/comms/JsonRpcRouter.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import jakarta.enterprise.event.Observes; @@ -233,8 +234,22 @@ private void route(JsonRpcRequest jsonRpcRequest, ServerWebSocket s) { } } else if (this.jsonRpcToDeploymentClassPathJava.contains(jsonRpcMethodName)) { // Route to extension (deployment) Object item = DevConsoleManager.invoke(jsonRpcMethodName, getArgsAsMap(jsonRpcRequest)); - codec.writeResponse(s, jsonRpcRequest.getId(), item, - MessageType.Response); + + // Support for Mutiny is diffcult because we are between the runtime and deployment classpath. + // Supporting something like CompletableFuture that is in the JDK works fine + if (item instanceof CompletionStage) { + CompletionStage future = (CompletionStage) item; + future.thenAccept(r -> { + codec.writeResponse(s, jsonRpcRequest.getId(), r, + MessageType.Response); + }).exceptionally(throwable -> { + codec.writeErrorResponse(s, jsonRpcRequest.getId(), jsonRpcMethodName, throwable); + return null; + }); + } else { + codec.writeResponse(s, jsonRpcRequest.getId(), item, + MessageType.Response); + } } else { // Method not found codec.writeMethodNotFoundResponse(s, jsonRpcRequest.getId(), jsonRpcMethodName); From 6cefd527474ace8e071c2e3f1f440ee1733e4045 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 25 Jul 2024 11:05:50 +0200 Subject: [PATCH 05/35] Scheduler: make sure an exception never slips through an invokers chain - related to #41240 (cherry picked from commit 0e075ee6d65602e119afbbe8a30ab12ee7ab3dda) --- .../test/ConcurrentExecutionSkipTest.java | 21 +++++++++++++++++++ .../common/runtime/DefaultInvoker.java | 8 ++++--- .../common/runtime/DelegateInvoker.java | 13 ++++++++++++ .../common/runtime/InstrumentedInvoker.java | 7 +------ .../SkipConcurrentExecutionInvoker.java | 2 +- .../common/runtime/SkipPredicateInvoker.java | 2 +- .../common/runtime/StatusEmitterInvoker.java | 2 +- .../test/ConcurrentExecutionSkipTest.java | 21 +++++++++++++++++++ 8 files changed, 64 insertions(+), 12 deletions(-) diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ConcurrentExecutionSkipTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ConcurrentExecutionSkipTest.java index 7e58c0c63a3d3..b7c25fa6435ad 100644 --- a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ConcurrentExecutionSkipTest.java +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/ConcurrentExecutionSkipTest.java @@ -2,6 +2,7 @@ import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.concurrent.CountDownLatch; @@ -13,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.scheduler.FailedExecution; import io.quarkus.scheduler.Scheduled; import io.quarkus.scheduler.SkippedExecution; import io.quarkus.scheduler.SuccessfulExecution; @@ -39,6 +41,10 @@ public void testExecution() { } else { fail("Jobs were not executed in 10 seconds!"); } + + assertTrue(Jobs.FAILED_LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.FAILURE_COUNTER.get() > 0); + } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); @@ -50,8 +56,11 @@ static class Jobs { static final CountDownLatch BLOCKING_LATCH = new CountDownLatch(1); static final AtomicInteger COUNTER = new AtomicInteger(0); + static final AtomicInteger FAILING_COUNTER = new AtomicInteger(0); static final AtomicInteger SUCCESS_COUNTER = new AtomicInteger(0); + static final AtomicInteger FAILURE_COUNTER = new AtomicInteger(0); static final CountDownLatch SKIPPED_LATCH = new CountDownLatch(1); + static final CountDownLatch FAILED_LATCH = new CountDownLatch(1); @Scheduled(every = "1s", concurrentExecution = SKIP) void nonconcurrent() throws InterruptedException { @@ -61,6 +70,14 @@ void nonconcurrent() throws InterruptedException { } } + @Scheduled(every = "1s", concurrentExecution = SKIP) + void failing() { + if (FAILING_COUNTER.incrementAndGet() > 2) { + FAILED_LATCH.countDown(); + } + throw new IllegalStateException(); + } + void onSkip(@Observes SkippedExecution event) { SKIPPED_LATCH.countDown(); } @@ -68,5 +85,9 @@ void onSkip(@Observes SkippedExecution event) { void onSuccess(@Observes SuccessfulExecution event) { SUCCESS_COUNTER.incrementAndGet(); } + + void onFailure(@Observes FailedExecution event) { + FAILURE_COUNTER.incrementAndGet(); + } } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DefaultInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DefaultInvoker.java index c587b0c056e32..cd9abbadbf656 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DefaultInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DefaultInvoker.java @@ -1,5 +1,6 @@ package io.quarkus.scheduler.common.runtime; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import io.quarkus.arc.Arc; @@ -24,10 +25,10 @@ public CompletionStage invoke(ScheduledExecution execution) throws Excepti return invokeBean(execution).whenComplete((v, t) -> { requestContext.destroy(state); }); - } catch (RuntimeException e) { - // Just terminate the context and rethrow the exception if something goes really wrong + } catch (Throwable e) { + // Terminate the context and return a failed stage if something goes really wrong requestContext.terminate(); - throw e; + return CompletableFuture.failedStage(e); } finally { // Always deactivate the context requestContext.deactivate(); @@ -35,6 +36,7 @@ public CompletionStage invoke(ScheduledExecution execution) throws Excepti } } + // This method is generated and should never throw an exception protected abstract CompletionStage invokeBean(ScheduledExecution execution); } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java index ff7571ab10352..a2245862d7fb0 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelegateInvoker.java @@ -1,5 +1,10 @@ package io.quarkus.scheduler.common.runtime; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import io.quarkus.scheduler.ScheduledExecution; + abstract class DelegateInvoker implements ScheduledInvoker { protected final ScheduledInvoker delegate; @@ -17,4 +22,12 @@ public boolean isBlocking() { public boolean isRunningOnVirtualThread() { return delegate.isRunningOnVirtualThread(); } + + protected CompletionStage invokeDelegate(ScheduledExecution execution) { + try { + return delegate.invoke(execution); + } catch (Throwable e) { + return CompletableFuture.failedStage(e); + } + } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/InstrumentedInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/InstrumentedInvoker.java index c0e330001f436..5cb2721bd6435 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/InstrumentedInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/InstrumentedInvoker.java @@ -1,6 +1,5 @@ package io.quarkus.scheduler.common.runtime; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import io.quarkus.scheduler.ScheduledExecution; @@ -26,11 +25,7 @@ public CompletionStage invoke(ScheduledExecution execution) throws Excepti @Override public CompletionStage executeJob() { - try { - return delegate.invoke(execution); - } catch (Exception e) { - return CompletableFuture.failedFuture(e); - } + return invokeDelegate(execution); } @Override diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipConcurrentExecutionInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipConcurrentExecutionInvoker.java index fc4a21906dbfb..6171099279cd5 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipConcurrentExecutionInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipConcurrentExecutionInvoker.java @@ -34,7 +34,7 @@ public SkipConcurrentExecutionInvoker(ScheduledInvoker delegate, Event invoke(ScheduledExecution execution) throws Exception { if (running.compareAndSet(false, true)) { - return delegate.invoke(execution).whenComplete((r, t) -> running.set(false)); + return invokeDelegate(execution).whenComplete((r, t) -> running.set(false)); } LOG.debugf("Skipped scheduled invoker execution: %s", delegate.getClass().getName()); SkippedExecution payload = new SkippedExecution(execution, diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipPredicateInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipPredicateInvoker.java index 7daf2c80234fc..90f7c7f9f2834 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipPredicateInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/SkipPredicateInvoker.java @@ -40,7 +40,7 @@ public CompletionStage invoke(ScheduledExecution execution) throws Excepti event.fireAsync(payload); return CompletableFuture.completedStage(null); } else { - return delegate.invoke(execution); + return invokeDelegate(execution); } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/StatusEmitterInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/StatusEmitterInvoker.java index 2a06be45171b5..20797e83405ee 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/StatusEmitterInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/StatusEmitterInvoker.java @@ -32,7 +32,7 @@ public StatusEmitterInvoker(ScheduledInvoker delegate, Event invoke(ScheduledExecution execution) throws Exception { - return delegate.invoke(execution).whenComplete((v, t) -> { + return invokeDelegate(execution).whenComplete((v, t) -> { if (t != null) { LOG.errorf(t, "Error occurred while executing task for trigger %s", execution.getTrigger()); Events.fire(failedEvent, new FailedExecution(execution, t)); diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConcurrentExecutionSkipTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConcurrentExecutionSkipTest.java index 27290798d8c4a..5c00b7181d4fc 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConcurrentExecutionSkipTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConcurrentExecutionSkipTest.java @@ -2,6 +2,7 @@ import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.concurrent.CountDownLatch; @@ -13,6 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.scheduler.FailedExecution; import io.quarkus.scheduler.Scheduled; import io.quarkus.scheduler.SkippedExecution; import io.quarkus.scheduler.SuccessfulExecution; @@ -39,6 +41,10 @@ public void testExecution() { } else { fail("Jobs were not executed in 10 seconds!"); } + + assertTrue(Jobs.FAILED_LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(Jobs.FAILURE_COUNTER.get() > 0); + } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); @@ -50,8 +56,11 @@ static class Jobs { static final CountDownLatch BLOCKING_LATCH = new CountDownLatch(1); static final AtomicInteger COUNTER = new AtomicInteger(0); + static final AtomicInteger FAILING_COUNTER = new AtomicInteger(0); static final AtomicInteger SUCCESS_COUNTER = new AtomicInteger(0); + static final AtomicInteger FAILURE_COUNTER = new AtomicInteger(0); static final CountDownLatch SKIPPED_LATCH = new CountDownLatch(1); + static final CountDownLatch FAILED_LATCH = new CountDownLatch(1); @Scheduled(every = "1s", concurrentExecution = SKIP) void nonconcurrent() throws InterruptedException { @@ -61,6 +70,14 @@ void nonconcurrent() throws InterruptedException { } } + @Scheduled(every = "1s", concurrentExecution = SKIP) + void failing() { + if (FAILING_COUNTER.incrementAndGet() > 2) { + FAILED_LATCH.countDown(); + } + throw new IllegalStateException(); + } + void onSkip(@Observes SkippedExecution event) { SKIPPED_LATCH.countDown(); } @@ -68,5 +85,9 @@ void onSkip(@Observes SkippedExecution event) { void onSuccess(@Observes SuccessfulExecution event) { SUCCESS_COUNTER.incrementAndGet(); } + + void onFailure(@Observes FailedExecution event) { + FAILURE_COUNTER.incrementAndGet(); + } } } From 70884671e227786da4e5bde18844382248d8e9ec Mon Sep 17 00:00:00 2001 From: "Wel, S.P.A. van der (Stef)" Date: Thu, 25 Jul 2024 12:48:27 +0200 Subject: [PATCH 06/35] Update RedisClientConfig.java (cherry picked from commit 4d44964be53f35d5df57b23a57d09cfe9412fe73) --- .../redis/runtime/client/config/RedisClientConfig.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java index 6b47bd51be284..962c368eb27d0 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/runtime/client/config/RedisClientConfig.java @@ -81,9 +81,9 @@ public interface RedisClientConfig { Optional password(); /** - * The maximum size of the connection pool. When working with cluster or sentinel. - *

- * This value should be at least the total number of cluster member (or number of sentinels + 1) + * The maximum size of the connection pool. + * When working with cluster or sentinel, this value should be at least the total number of cluster members (or + * number of sentinels + 1) */ @WithDefault("6") int maxPoolSize(); From e588b26ff648719bd756f830469e09302b2a75c1 Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Thu, 18 Jul 2024 12:07:42 +0200 Subject: [PATCH 07/35] liquibase resource files are automatically loaded for native image build (cherry picked from commit ae7d46a3a0285fb30c422b516ec3857d0b44bdb8) --- .../deployment/LiquibaseProcessor.java | 180 +++++++++++------- 1 file changed, 107 insertions(+), 73 deletions(-) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index 8b415d41106e4..f60a408fc931a 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -5,6 +5,8 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; @@ -19,7 +21,6 @@ import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; @@ -57,6 +58,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.liquibase.LiquibaseDataSource; @@ -65,6 +67,7 @@ import io.quarkus.liquibase.runtime.LiquibaseDataSourceBuildTimeConfig; import io.quarkus.liquibase.runtime.LiquibaseFactoryProducer; import io.quarkus.liquibase.runtime.LiquibaseRecorder; +import io.quarkus.paths.PathTree; import io.quarkus.runtime.util.StringUtil; import liquibase.change.Change; import liquibase.change.DatabaseChangeProperty; @@ -89,6 +92,13 @@ class LiquibaseProcessor { private static final Logger LOGGER = Logger.getLogger(LiquibaseProcessor.class); private static final String LIQUIBASE_BEAN_NAME_PREFIX = "liquibase_"; + private static final String LIQUIBASE_GROUP_ID = "org.liquibase"; + private static final String LIQUIBASE_ARTIFACT_ID = "liquibase-core"; + private static final String LIQUIBASE_PROPERTIES_PATH = ""; + private static final String LIQUIBASE_DB_CHANGELOG_XSD_PATH = "www.liquibase.org/xml/ns/dbchangelog"; + private static final String LIQUIBASE_SERVICE_PATH = "META-INF/services/"; + private static final Set RUNTIME_INITIALIZATION_SERVICES = Set.of( + liquibase.configuration.ConfigurationValueProvider.class.getName()); private static final DotName DATABASE_CHANGE_PROPERTY = DotName.createSimple(DatabaseChangeProperty.class.getName()); @@ -109,6 +119,7 @@ void nativeImageConfiguration( LiquibaseBuildTimeConfig liquibaseBuildConfig, List jdbcDataSourceBuildItems, CombinedIndexBuildItem combinedIndex, + CurateOutcomeBuildItem curateOutcome, Capabilities capabilities, BuildProducer reflective, BuildProducer resource, @@ -178,82 +189,26 @@ void nativeImageConfiguration( resource.produce( new NativeImageResourceBuildItem(getChangeLogs(dataSourceNames, liquibaseBuildConfig).toArray(new String[0]))); - Stream.of(liquibase.change.Change.class, - liquibase.changelog.ChangeLogHistoryService.class, - liquibase.changeset.ChangeSetService.class, - liquibase.database.Database.class, - liquibase.database.DatabaseConnection.class, - liquibase.datatype.LiquibaseDataType.class, - liquibase.diff.compare.DatabaseObjectComparator.class, - liquibase.diff.DiffGenerator.class, - liquibase.diff.output.changelog.ChangeGenerator.class, - liquibase.executor.Executor.class, - liquibase.license.LicenseService.class, - liquibase.lockservice.LockService.class, - liquibase.logging.LogService.class, - liquibase.parser.ChangeLogParser.class, - liquibase.parser.LiquibaseSqlParser.class, - liquibase.parser.NamespaceDetails.class, - liquibase.parser.SnapshotParser.class, - liquibase.precondition.Precondition.class, - liquibase.report.ShowSummaryGenerator.class, - liquibase.serializer.ChangeLogSerializer.class, - liquibase.serializer.SnapshotSerializer.class, - liquibase.servicelocator.ServiceLocator.class, - liquibase.snapshot.SnapshotGenerator.class, - liquibase.sqlgenerator.SqlGenerator.class, - liquibase.structure.DatabaseObject.class, - liquibase.logging.mdc.MdcManager.class) - .forEach(t -> consumeService(t, (serviceClass, implementations) -> { - services.produce( - new ServiceProviderBuildItem(serviceClass.getName(), implementations.toArray(new String[0]))); - reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(new String[0])) - .constructors().methods().build()); - })); - // Register Precondition services, and the implementation class for reflection while also registering fields for reflection - consumeService(liquibase.precondition.Precondition.class, (serviceClass, implementations) -> { - services.produce(new ServiceProviderBuildItem(serviceClass.getName(), implementations.toArray(new String[0]))); + consumeService(liquibase.precondition.Precondition.class.getName(), (serviceClassName, implementations) -> { + services.produce(new ServiceProviderBuildItem(serviceClassName, implementations.toArray(new String[0]))); reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(new String[0])) .constructors().methods().fields().build()); }); // CommandStep implementations are needed - consumeService(liquibase.command.CommandStep.class, (serviceClass, implementations) -> { + consumeService(liquibase.command.CommandStep.class.getName(), (serviceClassName, implementations) -> { var filteredImpls = implementations.stream() .filter(commandStepPredicate(capabilities)) .toArray(String[]::new); - services.produce(new ServiceProviderBuildItem(serviceClass.getName(), filteredImpls)); + services.produce(new ServiceProviderBuildItem(serviceClassName, filteredImpls)); reflective.produce(ReflectiveClassBuildItem.builder(filteredImpls).constructors().build()); for (String implementation : filteredImpls) { runtimeInitialized.produce(new RuntimeInitializedClassBuildItem(implementation)); } }); - // liquibase XSD - resource.produce(new NativeImageResourceBuildItem( - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.7.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.8.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.9.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.10.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.11.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.12.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.13.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.14.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.15.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.16.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.18.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.19.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.21.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.22.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.23.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.24.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.25.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd", - "www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd", - "liquibase.build.properties")); + resolveLiquibaseResources(curateOutcome, services, reflective, runtimeInitialized, resource); // liquibase resource bundles resourceBundle.produce(new NativeImageResourceBundleBuildItem("liquibase/i18n/liquibase-core")); @@ -267,17 +222,100 @@ private static Predicate commandStepPredicate(Capabilities capabilities) } } - private void consumeService(Class serviceClass, BiConsumer, Collection> consumer) { + private void consumeService(String serviceClassName, BiConsumer> consumer) { try { - String service = "META-INF/services/" + serviceClass.getName(); + String service = LIQUIBASE_SERVICE_PATH + serviceClassName; Set implementations = ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), service); - consumer.accept(serviceClass, implementations); + consumer.accept(serviceClassName, implementations); } catch (IOException ex) { throw new IllegalStateException(ex); } } + private void resolveLiquibaseResources( + CurateOutcomeBuildItem curateOutcome, + BuildProducer services, + BuildProducer reflective, + BuildProducer runtimeInitialized, + BuildProducer resource) { + + var dependencies = curateOutcome.getApplicationModel().getDependencies(); + var liquibaseDependency = dependencies.stream() + .filter(d -> LIQUIBASE_GROUP_ID.equals(d.getGroupId()) + && LIQUIBASE_ARTIFACT_ID.equals(d.getArtifactId())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Liquibase dependency not found")); + + var tree = liquibaseDependency.getContentTree(); + loadLiquibaseRootProperties(tree, resource); + loadLiquibaseXsdResources(tree, resource); + loadLiquibaseServiceProviderConfig(tree, services, reflective, runtimeInitialized); + } + + private List getResourceNames(PathTree pathTree, String basePath, String fileExtension, boolean stripPath) { + return pathTree.apply(basePath, visit -> { + try (var pathStream = Files.list(visit.getPath())) { + return pathStream + .map(p -> stripPath ? p.getFileName() : p.subpath(0, p.getNameCount())) + .map(Path::toString) + .filter(s -> s.endsWith(fileExtension)) + .toList(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + } + + private void loadLiquibaseRootProperties( + PathTree tree, + BuildProducer resource) { + + var rootProperties = getResourceNames( + tree, + LIQUIBASE_PROPERTIES_PATH, + ".properties", + true); + resource.produce(new NativeImageResourceBuildItem(rootProperties)); + } + + private void loadLiquibaseXsdResources( + PathTree tree, + BuildProducer resource) { + + var xsdResources = getResourceNames( + tree, + LIQUIBASE_DB_CHANGELOG_XSD_PATH, + ".xsd", + false); + resource.produce(new NativeImageResourceBuildItem(xsdResources)); + } + + private void loadLiquibaseServiceProviderConfig( + PathTree tree, + BuildProducer services, + BuildProducer reflective, + BuildProducer runtimeInitialized) { + + getResourceNames(tree, LIQUIBASE_SERVICE_PATH, "", true).stream() + .filter(not(RUNTIME_INITIALIZATION_SERVICES::contains)) + .forEach(t -> consumeService(t, (serviceClassName, implementations) -> { + services.produce(new ServiceProviderBuildItem( + serviceClassName, + implementations.toArray(new String[0]))); + reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(new String[0])) + .constructors().methods().build()); + })); + initializeLiquibaseServiceProviderAtRuntime(runtimeInitialized); + } + + private void initializeLiquibaseServiceProviderAtRuntime( + BuildProducer runtimeInitialized) { + + RUNTIME_INITIALIZATION_SERVICES + .forEach(className -> runtimeInitialized.produce(new RuntimeInitializedClassBuildItem(className))); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) void createBeans(LiquibaseRecorder recorder, @@ -479,20 +517,16 @@ private Set findAllChangeLogFiles(String file, ChangeLogParserFactory ch private Optional extractChangeFile(Change change, String changeSetFilePath) { String path = null; Boolean relative = null; - if (change instanceof LoadDataChange) { - LoadDataChange loadDataChange = (LoadDataChange) change; + if (change instanceof LoadDataChange loadDataChange) { path = loadDataChange.getFile(); relative = loadDataChange.isRelativeToChangelogFile(); - } else if (change instanceof SQLFileChange) { - SQLFileChange sqlFileChange = (SQLFileChange) change; + } else if (change instanceof SQLFileChange sqlFileChange) { path = sqlFileChange.getPath(); relative = sqlFileChange.isRelativeToChangelogFile(); - } else if (change instanceof CreateProcedureChange) { - CreateProcedureChange createProcedureChange = (CreateProcedureChange) change; + } else if (change instanceof CreateProcedureChange createProcedureChange) { path = createProcedureChange.getPath(); relative = createProcedureChange.isRelativeToChangelogFile(); - } else if (change instanceof CreateViewChange) { - CreateViewChange createViewChange = (CreateViewChange) change; + } else if (change instanceof CreateViewChange createViewChange) { path = createViewChange.getPath(); relative = createViewChange.getRelativeToChangelogFile(); } From 4e1946267e316fcaadc23d7273292f835a8194ca Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Fri, 19 Jul 2024 16:42:25 +0200 Subject: [PATCH 08/35] enable clean-at-start for liquibase integration test (cherry picked from commit 598b6199e19a3df7dfbf02cf5d09ba7eea07c954) --- .../liquibase/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/liquibase/src/main/resources/application.properties b/integration-tests/liquibase/src/main/resources/application.properties index 036f9cef122dc..89b5b764edf11 100644 --- a/integration-tests/liquibase/src/main/resources/application.properties +++ b/integration-tests/liquibase/src/main/resources/application.properties @@ -12,7 +12,7 @@ quarkus.datasource.second.jdbc.url=jdbc:h2:mem:second;INIT=RUNSCRIPT FROM 'src/m # Liquibase config properties quarkus.liquibase.change-log=db/changeLog.xml -quarkus.liquibase.clean-at-start=false +quarkus.liquibase.clean-at-start=true quarkus.liquibase.migrate-at-start=false quarkus.liquibase.database-change-log-lock-table-name=changelog_lock quarkus.liquibase.database-change-log-table-name=changelog From 3e400b8e1b2a8beab3220a34c17569d910717e49 Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Mon, 22 Jul 2024 10:16:17 +0200 Subject: [PATCH 09/35] exclude problematic services (cherry picked from commit 7ab7002e2dffc67003b01d925ccca635f62486ac) --- .../deployment/LiquibaseProcessor.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index f60a408fc931a..f103573d8430b 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -97,7 +97,8 @@ class LiquibaseProcessor { private static final String LIQUIBASE_PROPERTIES_PATH = ""; private static final String LIQUIBASE_DB_CHANGELOG_XSD_PATH = "www.liquibase.org/xml/ns/dbchangelog"; private static final String LIQUIBASE_SERVICE_PATH = "META-INF/services/"; - private static final Set RUNTIME_INITIALIZATION_SERVICES = Set.of( + // see: https://github.com/quarkusio/quarkus/pull/41928#issuecomment-2242353134 + private static final Set EXCLUDED_SERVICES = Set.of( liquibase.configuration.ConfigurationValueProvider.class.getName()); private static final DotName DATABASE_CHANGE_PROPERTY = DotName.createSimple(DatabaseChangeProperty.class.getName()); @@ -208,7 +209,7 @@ void nativeImageConfiguration( } }); - resolveLiquibaseResources(curateOutcome, services, reflective, runtimeInitialized, resource); + resolveLiquibaseResources(curateOutcome, services, reflective, resource); // liquibase resource bundles resourceBundle.produce(new NativeImageResourceBundleBuildItem("liquibase/i18n/liquibase-core")); @@ -237,7 +238,6 @@ private void resolveLiquibaseResources( CurateOutcomeBuildItem curateOutcome, BuildProducer services, BuildProducer reflective, - BuildProducer runtimeInitialized, BuildProducer resource) { var dependencies = curateOutcome.getApplicationModel().getDependencies(); @@ -250,7 +250,7 @@ private void resolveLiquibaseResources( var tree = liquibaseDependency.getContentTree(); loadLiquibaseRootProperties(tree, resource); loadLiquibaseXsdResources(tree, resource); - loadLiquibaseServiceProviderConfig(tree, services, reflective, runtimeInitialized); + loadLiquibaseServiceProviderConfig(tree, services, reflective); } private List getResourceNames(PathTree pathTree, String basePath, String fileExtension, boolean stripPath) { @@ -294,11 +294,10 @@ private void loadLiquibaseXsdResources( private void loadLiquibaseServiceProviderConfig( PathTree tree, BuildProducer services, - BuildProducer reflective, - BuildProducer runtimeInitialized) { + BuildProducer reflective) { getResourceNames(tree, LIQUIBASE_SERVICE_PATH, "", true).stream() - .filter(not(RUNTIME_INITIALIZATION_SERVICES::contains)) + .filter(not(EXCLUDED_SERVICES::contains)) .forEach(t -> consumeService(t, (serviceClassName, implementations) -> { services.produce(new ServiceProviderBuildItem( serviceClassName, @@ -306,14 +305,6 @@ private void loadLiquibaseServiceProviderConfig( reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(new String[0])) .constructors().methods().build()); })); - initializeLiquibaseServiceProviderAtRuntime(runtimeInitialized); - } - - private void initializeLiquibaseServiceProviderAtRuntime( - BuildProducer runtimeInitialized) { - - RUNTIME_INITIALIZATION_SERVICES - .forEach(className -> runtimeInitialized.produce(new RuntimeInitializedClassBuildItem(className))); } @BuildStep From 6dc25db2e1bf751fe6faf57a92a4f9b37dab6908 Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Wed, 24 Jul 2024 14:40:53 +0200 Subject: [PATCH 10/35] include ConfigurationValueProvider in native image; delete SubstituteEnvironmentValueProvider (cherry picked from commit 83706884f169570b43479a0856352d7dfaed630e) --- .../deployment/LiquibaseProcessor.java | 12 ++++------- .../SubstituteEnvironmentValueProvider.java | 20 ------------------- 2 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index f103573d8430b..4c8fc40218582 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -97,9 +97,6 @@ class LiquibaseProcessor { private static final String LIQUIBASE_PROPERTIES_PATH = ""; private static final String LIQUIBASE_DB_CHANGELOG_XSD_PATH = "www.liquibase.org/xml/ns/dbchangelog"; private static final String LIQUIBASE_SERVICE_PATH = "META-INF/services/"; - // see: https://github.com/quarkusio/quarkus/pull/41928#issuecomment-2242353134 - private static final Set EXCLUDED_SERVICES = Set.of( - liquibase.configuration.ConfigurationValueProvider.class.getName()); private static final DotName DATABASE_CHANGE_PROPERTY = DotName.createSimple(DatabaseChangeProperty.class.getName()); @@ -116,7 +113,7 @@ IndexDependencyBuildItem indexLiquibase() { @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) @Record(STATIC_INIT) void nativeImageConfiguration( - LiquibaseRecorder recorder, + LiquibaseRecorder ignored, LiquibaseBuildTimeConfig liquibaseBuildConfig, List jdbcDataSourceBuildItems, CombinedIndexBuildItem combinedIndex, @@ -296,13 +293,12 @@ private void loadLiquibaseServiceProviderConfig( BuildProducer services, BuildProducer reflective) { - getResourceNames(tree, LIQUIBASE_SERVICE_PATH, "", true).stream() - .filter(not(EXCLUDED_SERVICES::contains)) + getResourceNames(tree, LIQUIBASE_SERVICE_PATH, "", true) .forEach(t -> consumeService(t, (serviceClassName, implementations) -> { services.produce(new ServiceProviderBuildItem( serviceClassName, - implementations.toArray(new String[0]))); - reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(new String[0])) + implementations.toArray(String[]::new))); + reflective.produce(ReflectiveClassBuildItem.builder(implementations.toArray(String[]::new)) .constructors().methods().build()); })); } diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java deleted file mode 100644 index 2fae996137a87..0000000000000 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/graal/SubstituteEnvironmentValueProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.quarkus.liquibase.runtime.graal; - -import java.util.Map; - -import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -@TargetClass(className = "liquibase.configuration.core.EnvironmentValueProvider") -final class SubstituteEnvironmentValueProvider { - - @Delete - private Map environment; - - @Substitute - protected Map getMap() { - return System.getenv(); - } - -} \ No newline at end of file From 2fb4f3aaa2c2990928a06741c4dc7166c3489b3e Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Thu, 25 Jul 2024 10:42:23 +0200 Subject: [PATCH 11/35] remove recorder from nativeImageConfiguration (cherry picked from commit 0fb9735897364afc3944805cc234f90cf0765216) --- .../io/quarkus/liquibase/deployment/LiquibaseProcessor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java index 4c8fc40218582..0afcedac241e0 100644 --- a/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java +++ b/extensions/liquibase/deployment/src/main/java/io/quarkus/liquibase/deployment/LiquibaseProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.liquibase.deployment; -import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import static java.util.function.Predicate.not; import java.io.FileNotFoundException; @@ -111,9 +110,7 @@ IndexDependencyBuildItem indexLiquibase() { } @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) - @Record(STATIC_INIT) void nativeImageConfiguration( - LiquibaseRecorder ignored, LiquibaseBuildTimeConfig liquibaseBuildConfig, List jdbcDataSourceBuildItems, CombinedIndexBuildItem combinedIndex, From 49e969b8a581390b8b75f60e88b1194d3b6481af Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Thu, 25 Jul 2024 10:45:33 +0200 Subject: [PATCH 12/35] add liquibase IT to Github native-tests for Windows (cherry picked from commit a75588a8cb3c8fa1180bd7c9eba62127d1cc0a8d) --- .github/native-tests.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/native-tests.json b/.github/native-tests.json index 5cb3b5ada7a27..f7cdd076826dc 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -135,7 +135,7 @@ { "category": "Windows support", "timeout": 50, - "test-modules": "resteasy-jackson, qute", + "test-modules": "resteasy-jackson, qute, liquibase", "os-name": "windows-latest" }, { From cf5d38d8772cdbbfd099773077a3748f88b09063 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Mon, 15 Jul 2024 13:55:47 +0200 Subject: [PATCH 13/35] Gradle imageTask: look for builders among the dependencies of the runtime Quarkus application configuration instead of all the project configurations (cherry picked from commit 202eb36c55fe5b07bae8766673c7558d734c5d51) --- .../java/io/quarkus/gradle/tasks/ImageTask.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java index 089c63db4caea..e946fad553f3e 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java @@ -11,8 +11,13 @@ import org.gradle.api.tasks.TaskAction; +import io.quarkus.gradle.dependency.ApplicationDeploymentClasspathBuilder; +import io.quarkus.gradle.tooling.ToolingUtils; +import io.quarkus.runtime.LaunchMode; + public abstract class ImageTask extends QuarkusBuildTask { + private static final String DEPLOYMENT_SUFFIX = "-deployment"; static final String QUARKUS_PREFIX = "quarkus-"; static final String QUARKUS_CONTAINER_IMAGE_PREFIX = "quarkus-container-image-"; static final String QUARKUS_CONTAINER_IMAGE_BUILD = "quarkus.container-image.build"; @@ -53,10 +58,14 @@ List availableBuilders() { // This will only pickup direct dependencies and not transitives // This means that extensions like quarkus-container-image-openshift via quarkus-openshift are not picked up // So, let's relax our filters a bit so that we can pickup quarkus-openshift directly (relax the prefix requirement). - return getProject().getConfigurations().stream().flatMap(c -> c.getDependencies().stream()) + return getProject().getConfigurations() + .getByName(ToolingUtils.toDeploymentConfigurationName( + ApplicationDeploymentClasspathBuilder.getFinalRuntimeConfigName(LaunchMode.NORMAL))) + .getDependencies().stream() .map(d -> d.getName()) .filter(n -> n.startsWith(QUARKUS_CONTAINER_IMAGE_PREFIX) || n.startsWith(QUARKUS_PREFIX)) - .map(n -> n.replace(QUARKUS_CONTAINER_IMAGE_PREFIX, "").replace(QUARKUS_PREFIX, "")) + .map(n -> n.replace(QUARKUS_CONTAINER_IMAGE_PREFIX, "").replace(QUARKUS_PREFIX, "").replace(DEPLOYMENT_SUFFIX, + "")) .filter(BUILDERS::containsKey) .map(BUILDERS::get) .collect(Collectors.toList()); From 1cbce79f7568e20935c5447022bf2b3e79431a43 Mon Sep 17 00:00:00 2001 From: Jakub Jedlicka Date: Thu, 25 Jul 2024 17:33:18 +0200 Subject: [PATCH 14/35] Removed missed deprecated `@QuarkusTestResource` from docs (cherry picked from commit 6588ea3bf193b68c41faae61a454cac773775870) --- docs/src/main/asciidoc/mongodb.adoc | 4 ++-- docs/src/main/asciidoc/native-and-ssl.adoc | 2 +- .../src/main/java/io/quarkus/test/common/ResourceArg.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 94998df1eb073..89fef1dd72449 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -639,14 +639,14 @@ To set the desired port MongoDB will listen to when it is launched, the followin [source,java] ---- -@QuarkusTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = MongoTestResource.PORT, value = "27017")) +@WithTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = MongoTestResource.PORT, value = "27017")) ---- To set the desired MongoDB version that will be launched, the following code should be used: [source,java] ---- -@QuarkusTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = MongoTestResource.VERSION, value = "V5_0")) +@WithTestResource(value = MongoTestResource.class, initArgs = @ResourceArg(name = MongoTestResource.VERSION, value = "V5_0")) ---- The string value used can be any of one of the `de.flapdoodle.embed.mongo.distribution.Version` or `de.flapdoodle.embed.mongo.distribution.Version.Main` enums. diff --git a/docs/src/main/asciidoc/native-and-ssl.adoc b/docs/src/main/asciidoc/native-and-ssl.adoc index 86e14bf517349..4271614a28a94 100644 --- a/docs/src/main/asciidoc/native-and-ssl.adoc +++ b/docs/src/main/asciidoc/native-and-ssl.adoc @@ -45,7 +45,7 @@ which configures our REST client to connect to an SSL REST service. For the purposes of this guide, we also need to remove the configuration that starts the embedded WireMock server that stubs REST client responses so the tests actually propagate calls to the https://stage.code.quarkus.io/api. Update the test file `src/test/java/org/acme/rest/client/ExtensionsResourceTest.java` and remove the line: [source,java] ---- -@QuarkusTestResource(WireMockExtensions.class) +@WithTestResource(WireMockExtensions.class) ---- from the `ExtensionsResourceTest` class. diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/ResourceArg.java b/test-framework/common/src/main/java/io/quarkus/test/common/ResourceArg.java index e4c49b8cd05f6..fd4bdd7db6a91 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/ResourceArg.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/ResourceArg.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Uses to define arguments of for {@code QuarkusTestResource} + * Uses to define arguments for {@code WithTestResource} * * see {@link WithTestResource#initArgs()} */ From 6fbc0245835026df2098486a80bc0b68c4dd7fc1 Mon Sep 17 00:00:00 2001 From: "Gunther C. Wenda" Date: Fri, 26 Jul 2024 12:10:06 +0200 Subject: [PATCH 15/35] fix include_all in native image (cherry picked from commit bf6232befeae0dbf8be8063b48635693db4867bb) --- .../quarkus/liquibase/LiquibaseFactory.java | 35 +++++++---- .../runtime/NativeImageResourceAccessor.java | 63 +++++++++++++++++++ .../src/main/resources/db/xml/changeLog.xml | 4 +- .../LiquibaseFunctionalityNativeIT.java | 7 --- .../liquibase/LiquibaseFunctionalityPMT.java | 2 +- .../liquibase/LiquibaseFunctionalityTest.java | 10 +-- 6 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/NativeImageResourceAccessor.java diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java index 1616bdb3262af..6bc2900e1f288 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/LiquibaseFactory.java @@ -10,6 +10,8 @@ import io.agroal.api.AgroalDataSource; import io.quarkus.liquibase.runtime.LiquibaseConfig; +import io.quarkus.liquibase.runtime.NativeImageResourceAccessor; +import io.quarkus.runtime.ImageMode; import io.quarkus.runtime.ResettableSystemProperties; import io.quarkus.runtime.util.StringUtil; import liquibase.Contexts; @@ -38,33 +40,44 @@ public LiquibaseFactory(LiquibaseConfig config, DataSource datasource, String da } private ResourceAccessor resolveResourceAccessor() throws FileNotFoundException { + var rootAccessor = new CompositeResourceAccessor(); + return ImageMode.current().isNativeImage() + ? nativeImageResourceAccessor(rootAccessor) + : defaultResourceAccessor(rootAccessor); + } + + private ResourceAccessor defaultResourceAccessor(CompositeResourceAccessor rootAccessor) + throws FileNotFoundException { - CompositeResourceAccessor compositeResourceAccessor = new CompositeResourceAccessor(); - compositeResourceAccessor - .addResourceAccessor(new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader())); + rootAccessor.addResourceAccessor( + new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader())); if (!config.changeLog.startsWith("filesystem:") && config.searchPath.isEmpty()) { - return compositeResourceAccessor; + return rootAccessor; } if (config.searchPath.isEmpty()) { - compositeResourceAccessor.addResourceAccessor( + return rootAccessor.addResourceAccessor( new DirectoryResourceAccessor( - Paths.get(StringUtil.changePrefix(config.changeLog, "filesystem:", "")).getParent())); - return compositeResourceAccessor; + Paths.get(StringUtil + .changePrefix(config.changeLog, "filesystem:", "")) + .getParent())); } for (String searchPath : config.searchPath.get()) { - compositeResourceAccessor.addResourceAccessor(new DirectoryResourceAccessor(Paths.get(searchPath))); + rootAccessor.addResourceAccessor(new DirectoryResourceAccessor(Paths.get(searchPath))); } + return rootAccessor; + } - return compositeResourceAccessor; + private ResourceAccessor nativeImageResourceAccessor(CompositeResourceAccessor rootAccessor) { + return rootAccessor.addResourceAccessor(new NativeImageResourceAccessor()); } private String parseChangeLog(String changeLog) { - if (changeLog.startsWith("filesystem:") && config.searchPath.isEmpty()) { - return Paths.get(StringUtil.changePrefix(changeLog, "filesystem:", "")).getFileName().toString(); + return Paths.get(StringUtil.changePrefix(changeLog, "filesystem:", "")) + .getFileName().toString(); } if (changeLog.startsWith("filesystem:")) { diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/NativeImageResourceAccessor.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/NativeImageResourceAccessor.java new file mode 100644 index 0000000000000..5a9d1e0fa15bd --- /dev/null +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/NativeImageResourceAccessor.java @@ -0,0 +1,63 @@ +package io.quarkus.liquibase.runtime; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.jboss.logging.Logger; + +import liquibase.resource.AbstractPathResourceAccessor; +import liquibase.resource.PathResource; +import liquibase.resource.Resource; + +public class NativeImageResourceAccessor extends AbstractPathResourceAccessor { + private static final URI NATIVE_IMAGE_FILESYSTEM_URI = URI.create("resource:/"); + private static final Logger log = Logger.getLogger(NativeImageResourceAccessor.class); + + private final FileSystem fileSystem; + + public NativeImageResourceAccessor() { + FileSystem fs; + try { + fs = FileSystems.newFileSystem( + NATIVE_IMAGE_FILESYSTEM_URI, + Collections.singletonMap("create", "true")); + log.debug("Creating new filesystem for native image"); + } catch (FileSystemAlreadyExistsException ex) { + fs = FileSystems.getFileSystem(NATIVE_IMAGE_FILESYSTEM_URI); + log.debug("Native image file system already exists", ex); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + fileSystem = fs; + } + + @Override + protected Path getRootPath() { + return fileSystem.getPath("/"); + } + + @Override + protected Resource createResource(Path file, String pathToAdd) { + return new PathResource(pathToAdd, file); + } + + @Override + public void close() { + } + + @Override + public List describeLocations() { + return Collections.singletonList(fileSystem.toString()); + } + + @Override + public String toString() { + return getClass().getName() + " (" + getRootPath() + ") (" + fileSystem.toString() + ")"; + } +} diff --git a/integration-tests/liquibase/src/main/resources/db/xml/changeLog.xml b/integration-tests/liquibase/src/main/resources/db/xml/changeLog.xml index a0276c45aea2e..15f0c8ffa6301 100644 --- a/integration-tests/liquibase/src/main/resources/db/xml/changeLog.xml +++ b/integration-tests/liquibase/src/main/resources/db/xml/changeLog.xml @@ -10,7 +10,5 @@ - - + diff --git a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityNativeIT.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityNativeIT.java index f252e73b1d8d5..b23072fa20724 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityNativeIT.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityNativeIT.java @@ -4,11 +4,4 @@ @QuarkusIntegrationTest public class LiquibaseFunctionalityNativeIT extends LiquibaseFunctionalityTest { - - // see: https://github.com/quarkusio/quarkus/issues/16292 - // if this is ever resolved, make sure to remove errorIfMissingOrEmpty="false" from includeAll in changeLog.xml - @Override - protected boolean isIncludeAllExpectedToWork() { - return false; - } } diff --git a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java index 1bcb6cb604d16..5a55dba9f8c82 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityPMT.java @@ -28,7 +28,7 @@ public class LiquibaseFunctionalityPMT { @Test public void test() { - LiquibaseFunctionalityTest.doTestLiquibaseQuarkusFunctionality(true); + LiquibaseFunctionalityTest.doTestLiquibaseQuarkusFunctionality(); } @AfterEach diff --git a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java index 447537cdef628..4a2fa97094fcb 100644 --- a/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java +++ b/integration-tests/liquibase/src/test/java/io/quarkus/it/liquibase/LiquibaseFunctionalityTest.java @@ -15,7 +15,7 @@ public class LiquibaseFunctionalityTest { @Test @DisplayName("Migrates a schema correctly using integrated instance") public void testLiquibaseQuarkusFunctionality() { - doTestLiquibaseQuarkusFunctionality(isIncludeAllExpectedToWork()); + doTestLiquibaseQuarkusFunctionality(); } @Test @@ -25,21 +25,17 @@ public void testLiquibaseUsingDedicatedUsernameAndPassword() { "ADMIN")); } - static void doTestLiquibaseQuarkusFunctionality(boolean isIncludeAllExpectedToWork) { + static void doTestLiquibaseQuarkusFunctionality() { when() .get("/liquibase/update") .then() .body(is( "create-tables-1,test-1,create-view-inline,create-view-file-abs,create-view-file-rel," - + (isIncludeAllExpectedToWork ? "includeAll-1,includeAll-2," : "") + + ("includeAll-1,includeAll-2,") + "json-create-tables-1,json-test-1," + "sql-create-tables-1,sql-test-1," + "yaml-create-tables-1,yaml-test-1," + "00000000000000,00000000000001,00000000000002," + "1613578374533-1,1613578374533-2,1613578374533-3")); } - - protected boolean isIncludeAllExpectedToWork() { - return true; - } } From 6a6fa009074ab9366c2934ab67f7b146048f4c07 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 26 Jul 2024 13:24:36 +0200 Subject: [PATCH 16/35] Qute: fix parsing of string literals and lenient section parameters - fixes #41918 (cherry picked from commit d2ff3efd520bb0227db16a30d2cb36c969c70747) --- .../java/io/quarkus/qute/LiteralSupport.java | 10 +- .../src/main/java/io/quarkus/qute/Parser.java | 99 ++++++++++++++----- .../test/java/io/quarkus/qute/ParserTest.java | 24 +++++ 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java index e2bd413a21a50..1dd41b1ed927d 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java @@ -74,7 +74,15 @@ static Object getLiteralValue(String literal) { * false otherwise */ static boolean isStringLiteralSeparator(char character) { - return character == '"' || character == '\''; + return isStringLiteralSeparatorSingle(character) || isStringLiteralSeparatorDouble(character); + } + + static boolean isStringLiteralSeparatorSingle(char character) { + return character == '\''; + } + + static boolean isStringLiteralSeparatorDouble(char character) { + return character == '"'; } static boolean isStringLiteral(String value) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 5b23843db8a4d..ee14ccf58c764 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -23,6 +23,7 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.regex.Pattern; import org.jboss.logging.Logger; @@ -165,7 +166,7 @@ Template parse() { } else { String reason = null; ErrorCode code = null; - if (state == State.TAG_INSIDE_STRING_LITERAL) { + if (state == State.TAG_INSIDE_STRING_LITERAL_SINGLE || state == State.TAG_INSIDE_STRING_LITERAL_DOUBLE) { reason = "unterminated string literal"; code = ParserError.UNTERMINATED_STRING_LITERAL; } else if (state == State.TAG_INSIDE) { @@ -249,8 +250,11 @@ private void processCharacter(char character) { case TAG_INSIDE: tag(character); break; - case TAG_INSIDE_STRING_LITERAL: - tagStringLiteral(character); + case TAG_INSIDE_STRING_LITERAL_SINGLE: + tagStringLiteralSingle(character); + break; + case TAG_INSIDE_STRING_LITERAL_DOUBLE: + tagStringLiteralDouble(character); break; case COMMENT: comment(character); @@ -339,7 +343,8 @@ private boolean isCdataEnd(char character) { private void tag(char character) { if (LiteralSupport.isStringLiteralSeparator(character)) { - state = State.TAG_INSIDE_STRING_LITERAL; + state = LiteralSupport.isStringLiteralSeparatorSingle(character) ? State.TAG_INSIDE_STRING_LITERAL_SINGLE + : State.TAG_INSIDE_STRING_LITERAL_DOUBLE; buffer.append(character); } else if (character == END_DELIMITER) { flushTag(); @@ -348,8 +353,15 @@ private void tag(char character) { } } - private void tagStringLiteral(char character) { - if (LiteralSupport.isStringLiteralSeparator(character)) { + private void tagStringLiteralSingle(char character) { + if (LiteralSupport.isStringLiteralSeparatorSingle(character)) { + state = State.TAG_INSIDE; + } + buffer.append(character); + } + + private void tagStringLiteralDouble(char character) { + if (LiteralSupport.isStringLiteralSeparatorDouble(character)) { state = State.TAG_INSIDE; } buffer.append(character); @@ -819,7 +831,8 @@ static int getFirstDeterminingEqualsCharPosition(String part) { static Iterator splitSectionParams(String content, B block) { - boolean stringLiteral = false; + boolean stringLiteralSingle = false; + boolean stringLiteralDouble = false; short composite = 0; byte brackets = 0; boolean space = false; @@ -830,7 +843,10 @@ static Iterator splitSectionPa char c = content.charAt(i); if (c == ' ') { if (!space) { - if (!stringLiteral && composite == 0 && brackets == 0) { + if (!stringLiteralSingle + && !stringLiteralDouble + && composite == 0 + && brackets == 0) { if (buffer.length() > 0) { parts.add(buffer.toString()); buffer = new StringBuilder(); @@ -842,19 +858,30 @@ static Iterator splitSectionPa } } else { if (composite == 0 - && LiteralSupport.isStringLiteralSeparator(c)) { - stringLiteral = !stringLiteral; - } else if (!stringLiteral - && isCompositeStart(c) && (i == 0 || space || composite > 0 + && !stringLiteralDouble + && LiteralSupport.isStringLiteralSeparatorSingle(c)) { + stringLiteralSingle = !stringLiteralSingle; + } else if (composite == 0 + && !stringLiteralSingle + && LiteralSupport.isStringLiteralSeparatorDouble(c)) { + stringLiteralDouble = !stringLiteralDouble; + } else if (!stringLiteralSingle + && !stringLiteralDouble + && isCompositeStart(c) + && (i == 0 || space || composite > 0 || (buffer.length() > 0 && buffer.charAt(buffer.length() - 1) == '!'))) { composite++; - } else if (!stringLiteral - && isCompositeEnd(c) && composite > 0) { + } else if (!stringLiteralSingle + && !stringLiteralDouble + && isCompositeEnd(c) + && composite > 0) { composite--; - } else if (!stringLiteral + } else if (!stringLiteralSingle + && !stringLiteralDouble && Parser.isLeftBracket(c)) { brackets++; - } else if (!stringLiteral + } else if (!stringLiteralSingle + && !stringLiteralDouble && Parser.isRightBracket(c) && brackets > 0) { brackets--; } @@ -864,7 +891,7 @@ && isCompositeEnd(c) && composite > 0) { } if (buffer.length() > 0) { - if (stringLiteral || composite > 0) { + if (stringLiteralSingle || stringLiteralDouble || composite > 0) { throw block.error("unterminated string literal or composite parameter detected for [{content}]") .argument("content", content) .code(ParserError.UNTERMINATED_STRING_LITERAL_OR_COMPOSITE_PARAMETER) @@ -874,10 +901,16 @@ && isCompositeEnd(c) && composite > 0) { parts.add(buffer.toString()); } - // Try to find/replace "standalone" equals signs used as param names separators - // This allows for more lenient parsing of named section parameters, e.g. item.name = 'foo' instead of item.name='foo' + // Try to find/replace/merge: + // 1. "standalone" equals signs used as param names separators + // 2. parts that start/end with an equal sign followed/preceded by a valid Java identifier + // This allows for more lenient parsing of named section parameters + // e.g. `item = 'foo'` or `item= 'foo'` instead of `item='foo'` for (ListIterator it = parts.listIterator(); it.hasNext();) { - if (it.next().equals("=") && it.previousIndex() != 0 && it.hasNext()) { + String next = it.next(); + if (next.equals("=") + && it.previousIndex() != 0 + && it.hasNext()) { // move cursor back it.previous(); String merged = parts.get(it.previousIndex()) + it.next() + it.next(); @@ -889,12 +922,33 @@ && isCompositeEnd(c) && composite > 0) { it.remove(); it.previous(); it.remove(); + } else if (next.endsWith("=") + && it.hasNext() + && EQUAL_ENDS_PATTERN.matcher(next).matches()) { + String merged = next + it.next(); + // replace the element with the merged value + it.set(merged); + // move cursor back and remove the element that ended with equals + it.previous(); + it.previous(); + it.remove(); + } else if (next.startsWith("=") + && it.hasPrevious() + && EQUAL_STARTS_PATTERN.matcher(next).matches()) { + String merged = next + it.previous(); + // replace the element with the merged value + it.set(merged); + // move cursor back and remove the element that started with equals + it.next(); + it.remove(); } } - return parts.iterator(); } + static final Pattern EQUAL_ENDS_PATTERN = Pattern.compile(".*[a-zA-Z0-9_$]=$"); + static final Pattern EQUAL_STARTS_PATTERN = Pattern.compile("^=[a-zA-Z0-9_$].*"); + static boolean isCompositeStart(char character) { return character == START_COMPOSITE_PARAM; } @@ -931,7 +985,8 @@ enum State { TEXT, TAG_INSIDE, - TAG_INSIDE_STRING_LITERAL, + TAG_INSIDE_STRING_LITERAL_SINGLE, + TAG_INSIDE_STRING_LITERAL_DOUBLE, TAG_CANDIDATE, COMMENT, ESCAPE, diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java index 9d6b663f675e0..c7375dc68763c 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java @@ -452,6 +452,30 @@ public void testMandatorySectionParas() { "Parser error: mandatory section parameters not declared for {#include /}: [template]", 1); } + @Test + public void testSectionParameterWithNestedSingleQuotationMark() { + Engine engine = Engine.builder().addDefaults().build(); + assertSectionParams(engine, "{#let id=\"'Foo'\"}", Map.of("id", "\"'Foo'\"")); + assertSectionParams(engine, "{#let id=\"'Foo \"}", Map.of("id", "\"'Foo \"")); + assertSectionParams(engine, "{#let id=\"'Foo ' \"}", Map.of("id", "\"'Foo ' \"")); + assertSectionParams(engine, "{#let id=\"'Foo ' \" bar='baz'}", Map.of("id", "\"'Foo ' \"", "bar", "'baz'")); + assertSectionParams(engine, "{#let my=bad id=(\"'Foo ' \" + 1) bar='baz'}", + Map.of("my", "bad", "id", "(\"'Foo ' \" + 1)", "bar", "'baz'")); + assertSectionParams(engine, "{#let id = 'Foo'}", Map.of("id", "'Foo'")); + assertSectionParams(engine, "{#let id= 'Foo'}", Map.of("id", "'Foo'")); + assertSectionParams(engine, "{#let my = (bad or not) id=1}", Map.of("my", "(bad or not)", "id", "1")); + assertSectionParams(engine, "{#let my= (bad or not) id=1}", Map.of("my", "(bad or not)", "id", "1")); + + } + + private void assertSectionParams(Engine engine, String content, Map expectedParams) { + Template template = engine.parse(content); + SectionNode node = template.findNodes(n -> n.isSection() && n.asSection().name.equals("let")).iterator().next() + .asSection(); + Map params = node.getBlocks().get(0).parameters; + assertEquals(expectedParams, params); + } + public static class Foo { public List getItems() { From 0ff01dc86ed0bfc022af4bbb61d2b1f8e7a876ac Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sun, 28 Jul 2024 10:36:56 +0200 Subject: [PATCH 17/35] toplevel class can't have static if pasting the code into toplevel class you end up with error so removing it from the client endpoint (cherry picked from commit 3fcf02ecaaf68128a0fb101d751a21c7af5f1d2e) --- docs/src/main/asciidoc/websockets-next-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/websockets-next-reference.adoc b/docs/src/main/asciidoc/websockets-next-reference.adoc index 3ccf664f55127..ac9012f75da3c 100644 --- a/docs/src/main/asciidoc/websockets-next-reference.adoc +++ b/docs/src/main/asciidoc/websockets-next-reference.adoc @@ -819,7 +819,7 @@ Let’s consider the following client endpoint: [source, java] ---- @WebSocketClient(path = "/endpoint/{name}") -public static class ClientEndpoint { +public class ClientEndpoint { @OnTextMessage void onMessage(@PathParam String name, String message, WebSocketClientConnection connection) { From 52e57f8ec25d50909d7b5e855d841be306efbc9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:25:52 +0000 Subject: [PATCH 18/35] Bump com.gradle:quarkus-build-caching-extension from 1.6 to 1.7 Bumps [com.gradle:quarkus-build-caching-extension](https://github.com/gradle/develocity-build-config-samples) from 1.6 to 1.7. - [Release notes](https://github.com/gradle/develocity-build-config-samples/releases) - [Commits](https://github.com/gradle/develocity-build-config-samples/compare/v1.6...v1.7) --- updated-dependencies: - dependency-name: com.gradle:quarkus-build-caching-extension dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] (cherry picked from commit d2658ab8d18c5c2ad8cbd884cc00b8cc39dbed8c) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 600f3a0177662..07f3499ebe3e4 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -12,7 +12,7 @@ com.gradle quarkus-build-caching-extension - 1.6 + 1.7 io.quarkus.develocity From a639b0dc797f557a3bfba1620703f231a75d8c5f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 29 Jul 2024 10:38:16 +0300 Subject: [PATCH 19/35] Treat Kotlin's Unit as void for the Quarkus REST scoring system Fixes: #42159 (cherry picked from commit 104de690a1bacfd83f121a08bcd28748ef43a0f1) --- .../core/startup/RuntimeResourceDeployment.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 041796659ddf3..abe1546f86896 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 @@ -565,8 +565,16 @@ private static void smartInitParameterConverter(int i, ParameterConverter quarku } private static boolean isNotVoid(Class rawEffectiveReturnType) { - return rawEffectiveReturnType != Void.class - && rawEffectiveReturnType != void.class; + if (rawEffectiveReturnType == Void.class) { + return false; + } + if (rawEffectiveReturnType == void.class) { + return false; + } + if ("kotlin.Unit".equals(rawEffectiveReturnType.getName())) { + return false; + } + return true; } private void addResponseHandler(ServerResourceMethod method, List handlers) { From c39698273d91ce8bcc8fec0907ac7f4b9fa202b7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 27 Jul 2024 17:11:27 +0100 Subject: [PATCH 20/35] Avoid a possible NPE during application stop (cherry picked from commit 0e74800c318779844bff1a51dc028af89744facc) --- .../io/quarkus/runtime/ApplicationLifecycleManager.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java index 48c18f868b7b2..d16e5ff7e94af 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java @@ -429,13 +429,15 @@ public void run() { } finally { stateLock.unlock(); } - if (currentApplication.isStarted()) { + //take a reliable reference before changing the application state: + final Application app = currentApplication; + if (app.isStarted()) { // On CLI apps, SIGINT won't call io.quarkus.runtime.Application#stop(), // making the awaitShutdown() below block the application termination process // It should be a noop if called twice anyway - currentApplication.stop(); + app.stop(); } - currentApplication.awaitShutdown(); + app.awaitShutdown(); currentApplication = null; System.out.flush(); System.err.flush(); From 24883bfdf1e76a5d36fdc046746eeb466447bea9 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 30 Jul 2024 10:00:35 +0300 Subject: [PATCH 21/35] Add a note about JpaSpecificationExecutor not being supported Relates to: #4040 (cherry picked from commit b4674b4158859cc9de5c9f77577e2ee90966b759) --- docs/src/main/asciidoc/spring-data-jpa.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/spring-data-jpa.adoc b/docs/src/main/asciidoc/spring-data-jpa.adoc index cb41a8bc350d0..52c20b03c83e4 100644 --- a/docs/src/main/asciidoc/spring-data-jpa.adoc +++ b/docs/src/main/asciidoc/spring-data-jpa.adoc @@ -599,6 +599,7 @@ An extensive list of examples can be seen in the https://github.com/quarkusio/qu * Methods of the `org.springframework.data.repository.query.QueryByExampleExecutor` interface - if any of these are invoked, a Runtime exception will be thrown. * QueryDSL support. No attempt will be made to generate implementations of the QueryDSL related repositories. +* Using `org.springframework.data.jpa.repository.JpaSpecificationExecutor` * Customizing the base repository for all repository interfaces in the code base. ** In Spring Data JPA this is done by registering a class that extends `org.springframework.data.jpa.repository.support.SimpleJpaRepository` however in Quarkus this class is not used at all (since all the necessary plumbing is done at build time). Similar support might be added to Quarkus in the future. From c244fdeca80273438172ee848b55fa9d93d0789a Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 30 Jul 2024 13:20:18 +0200 Subject: [PATCH 22/35] Qute: support synthetic named CDI beans injected in templates - this requires an additional late initialization of the QuteContext bean (cherry picked from commit 31848b81b124e066ca666d84ece6c57b52e8460b) --- .../qute/deployment/QuteProcessor.java | 34 ++++++++++------ .../inject/InjectNamespaceResolverTest.java | 23 ++++++++++- .../io/quarkus/qute/runtime/QuteRecorder.java | 40 +++++++++++++++++-- 3 files changed, 80 insertions(+), 17 deletions(-) diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index f40f1eeb724aa..193fd8f588f78 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -61,15 +61,17 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; -import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem; import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem; import io.quarkus.arc.deployment.QualifierRegistrarBuildItem; +import io.quarkus.arc.deployment.SynthesisFinishedBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.arc.processor.QualifierRegistrar; @@ -951,7 +953,7 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis, BuildProducer incorrectExpressions, BuildProducer implicitClasses, BuildProducer expressionMatches, - BeanDiscoveryFinishedBuildItem beanDiscovery, + SynthesisFinishedBuildItem synthesisFinished, List checkedTemplates, List templateData, QuteConfig config, @@ -970,10 +972,7 @@ public String apply(String id) { return findTemplatePath(templatesAnalysis, id); } }; - // IMPLEMENTATION NOTE: - // We do not support injection of synthetic beans with names - // Dependency on the ValidationPhaseBuildItem would result in a cycle in the build chain - Map namedBeans = beanDiscovery.beanStream().withName() + Map namedBeans = synthesisFinished.beanStream().withName() .collect(toMap(BeanInfo::getName, Function.identity())); // Map implicit class -> set of used members Map> implicitClassToMembersUsed = new HashMap<>(); @@ -2447,9 +2446,7 @@ public boolean test(TypeCheck check) { @BuildStep @Record(value = STATIC_INIT) void initialize(BuildProducer syntheticBeans, QuteRecorder recorder, - List generatedValueResolvers, List templatePaths, - Optional templateVariants, - List templateInitializers, + List templatePaths, Optional templateVariants, TemplateRootsBuildItem templateRoots) { List templates = new ArrayList<>(); @@ -2475,14 +2472,25 @@ void initialize(BuildProducer syntheticBeans, QuteRecord } syntheticBeans.produce(SyntheticBeanBuildItem.configure(QuteContext.class) - .supplier(recorder.createContext(generatedValueResolvers.stream() - .map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), templates, - tags, variants, templateInitializers.stream() - .map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList()), + .scope(BuiltinScope.SINGLETON.getInfo()) + .supplier(recorder.createContext(templates, + tags, variants, templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()), templateContents)) .done()); } + @BuildStep + @Record(value = STATIC_INIT) + void initializeGeneratedClasses(BeanContainerBuildItem beanContainer, QuteRecorder recorder, + List generatedValueResolvers, + List templateInitializers) { + // The generated classes must be initialized after the template expressions are validated in order to break the cycle in the build chain + recorder.initializeGeneratedClasses(generatedValueResolvers.stream() + .map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), + templateInitializers.stream() + .map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList())); + } + @BuildStep QualifierRegistrarBuildItem turnLocationIntoQualifier() { diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectNamespaceResolverTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectNamespaceResolverTest.java index e3bca7a111002..6ba4437a63ec8 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectNamespaceResolverTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectNamespaceResolverTest.java @@ -14,10 +14,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; import io.quarkus.qute.Qute; import io.quarkus.qute.Template; import io.quarkus.qute.deployment.Hello; import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Identifier; public class InjectNamespaceResolverTest { @@ -28,7 +32,21 @@ public class InjectNamespaceResolverTest { .addAsResource( new StringAsset( "{inject:hello.ping} != {inject:simple.ping} and {cdi:hello.ping} != {cdi:simple.ping}"), - "templates/foo.html")); + "templates/foo.html")) + .addBuildChainCustomizer(bcb -> { + bcb.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(SyntheticBeanBuildItem.configure(String.class) + .addQualifier().annotation(Identifier.class).addValue("value", "synthetic").done() + .name("synthetic") + .creator(mc -> { + mc.returnValue(mc.load("Yes!")); + }) + .done()); + } + }).produces(SyntheticBeanBuildItem.class).build(); + }); @Inject Template foo; @@ -45,6 +63,9 @@ public void testInjection() { assertEquals("pong::<br>", Qute.fmt("{cdi:hello.ping}::{newLine}").contentType("text/html").data("newLine", "
").render()); assertEquals(2, SimpleBean.DESTROYS.longValue()); + + // Test a synthetic named bean injected in a template + assertEquals("YES!", Qute.fmt("{cdi:synthetic.toUpperCase}").render()); } @Named("simple") diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java index 2e53fe166580d..60797098731d9 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java @@ -5,20 +5,23 @@ import java.util.Set; import java.util.function.Supplier; +import io.quarkus.arc.Arc; import io.quarkus.runtime.annotations.Recorder; @Recorder public class QuteRecorder { - public Supplier createContext(List resolverClasses, - List templatePaths, List tags, Map> variants, - List templateGlobalProviderClasses, Set templateRoots, Map templateContents) { + public Supplier createContext(List templatePaths, List tags, Map> variants, + Set templateRoots, Map templateContents) { return new Supplier() { @Override public Object get() { return new QuteContext() { + volatile List resolverClasses; + volatile List templateGlobalProviderClasses; + @Override public List getTemplatePaths() { return templatePaths; @@ -31,6 +34,9 @@ public List getTags() { @Override public List getResolverClasses() { + if (resolverClasses == null) { + throw generatedClassesNotInitialized(); + } return resolverClasses; } @@ -41,6 +47,9 @@ public Map> getVariants() { @Override public List getTemplateGlobalProviderClasses() { + if (templateGlobalProviderClasses == null) { + throw generatedClassesNotInitialized(); + } return templateGlobalProviderClasses; } @@ -53,11 +62,27 @@ public Set getTemplateRoots() { public Map getTemplateContents() { return templateContents; } + + @Override + public void setGeneratedClasses(List resolverClasses, List templateGlobalProviderClasses) { + this.resolverClasses = resolverClasses; + this.templateGlobalProviderClasses = templateGlobalProviderClasses; + } + + private IllegalStateException generatedClassesNotInitialized() { + return new IllegalStateException("Generated classes not initialized yet!"); + } + }; } }; } + public void initializeGeneratedClasses(List resolverClasses, List templateGlobalProviderClasses) { + QuteContext context = Arc.container().instance(QuteContext.class).get(); + context.setGeneratedClasses(resolverClasses, templateGlobalProviderClasses); + } + public interface QuteContext { List getResolverClasses(); @@ -74,6 +99,15 @@ public interface QuteContext { Map getTemplateContents(); + /** + * The generated classes must be initialized after the template expressions are validated (later during the STATIC_INIT + * bootstrap phase) in order to break the cycle in the build chain. + * + * @param resolverClasses + * @param templateGlobalProviderClasses + */ + void setGeneratedClasses(List resolverClasses, List templateGlobalProviderClasses); + } } From 30f543fa42eea9325bfc73412c2c7c92b7009f8b Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 30 Jul 2024 14:54:30 +0200 Subject: [PATCH 23/35] Quartz - Do not recreate job instances for re-fires (cherry picked from commit f2ea2b032b660ff10b1ac823dae13ac858bd7943) --- .../quartz/test/DependentBeanJobTest.java | 16 ++++++++-------- .../io/quarkus/quartz/runtime/CdiAwareJob.java | 6 +++++- 2 files changed, 13 insertions(+), 9 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 9a2943c1ab78a..410018b410a2b 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 @@ -86,10 +86,10 @@ public void testDependentBeanJobDestroyed() throws SchedulerException, Interrupt @Test public void testDependentBeanJobWithRefire() throws SchedulerException, InterruptedException { - // 5 one-off jobs should trigger construction/execution/destruction 10 times in total - CountDownLatch execLatch = service.initExecuteLatch(10); - CountDownLatch constructLatch = service.initConstructLatch(10); - CountDownLatch destroyedLatch = service.initDestroyedLatch(10); + // 5 one-off jobs should trigger construction/execution/destruction 5 times in total + CountDownLatch execLatch = service.initExecuteLatch(5); + CountDownLatch constructLatch = service.initConstructLatch(5); + CountDownLatch destroyedLatch = service.initDestroyedLatch(5); for (int i = 0; i < 5; i++) { Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger" + i, "myRefiringGroup") @@ -104,10 +104,10 @@ public void testDependentBeanJobWithRefire() throws SchedulerException, Interrup assertTrue(constructLatch.await(2, TimeUnit.SECONDS), "Latch count: " + constructLatch.getCount()); assertTrue(destroyedLatch.await(2, TimeUnit.SECONDS), "Latch count: " + destroyedLatch.getCount()); - // repeating job triggering three times; we expect six beans to exist for that due to refires - execLatch = service.initExecuteLatch(6); - constructLatch = service.initConstructLatch(6); - destroyedLatch = service.initDestroyedLatch(6); + // repeating job triggering three times; re-fires should NOT recreate the bean instance + execLatch = service.initExecuteLatch(3); + constructLatch = service.initConstructLatch(3); + destroyedLatch = service.initDestroyedLatch(3); JobDetail job = JobBuilder.newJob(RefiringJob.class) .withIdentity("myRepeatingJob", "myRefiringGroup") .build(); 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 index 4e02136078ef9..fde25d16681d7 100644 --- 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 @@ -28,10 +28,14 @@ public CdiAwareJob(Instance jobInstance) { @Override public void execute(JobExecutionContext context) throws JobExecutionException { Instance.Handle handle = jobInstance.getHandle(); + boolean refire = false; try { handle.get().execute(context); + } catch (JobExecutionException e) { + refire = e.refireImmediately(); + throw e; } finally { - if (handle.getBean().getScope().equals(Dependent.class)) { + if (refire != true && handle.getBean().getScope().equals(Dependent.class)) { handle.destroy(); } } From a84a574e4715ba7289d59488c839f39c0b244a77 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 30 Jul 2024 15:21:58 +0200 Subject: [PATCH 24/35] Quartz - Use the same dep. bean instance for both, execution and interruption of CdiAwareJob (cherry picked from commit 2498f5ca417255a11f857da06b3792458eeb57e1) --- .../programmatic/InterruptableJobTest.java | 11 +++++++++- .../quarkus/quartz/runtime/CdiAwareJob.java | 20 +++++++------------ .../quartz/runtime/QuartzSchedulerImpl.java | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/InterruptableJobTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/InterruptableJobTest.java index f7226ab5d5d4b..8381694a248cc 100644 --- a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/InterruptableJobTest.java +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/programmatic/InterruptableJobTest.java @@ -6,6 +6,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -40,6 +41,7 @@ public class InterruptableJobTest { static final CountDownLatch INTERRUPT_LATCH = new CountDownLatch(1); static final CountDownLatch EXECUTE_LATCH = new CountDownLatch(1); + static Integer initCounter = 0; static final CountDownLatch NON_INTERRUPTABLE_EXECUTE_LATCH = new CountDownLatch(1); static final CountDownLatch NON_INTERRUPTABLE_HOLD_LATCH = new CountDownLatch(1); @@ -66,7 +68,9 @@ public void testInterruptableJob() throws InterruptedException { throw new RuntimeException(e); } - assertTrue(INTERRUPT_LATCH.await(5, TimeUnit.SECONDS)); + assertTrue(INTERRUPT_LATCH.await(3, TimeUnit.SECONDS)); + // asserts that a single dep. scoped bean instance was used for both, execute() and interrupt() methods + assertTrue(initCounter == 1); } @Test @@ -102,6 +106,11 @@ public void testNonInterruptableJob() throws InterruptedException { @ApplicationScoped static class MyJob implements InterruptableJob { + @PostConstruct + public void postConstruct() { + initCounter++; + } + @Override public void execute(JobExecutionContext context) { EXECUTE_LATCH.countDown(); 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 index fde25d16681d7..8c3e42c1becd0 100644 --- 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 @@ -19,18 +19,19 @@ */ class CdiAwareJob implements InterruptableJob { - private final Instance jobInstance; + private final Instance.Handle handle; + private final Job beanInstance; - public CdiAwareJob(Instance jobInstance) { - this.jobInstance = jobInstance; + public CdiAwareJob(Instance.Handle handle) { + this.handle = handle; + this.beanInstance = handle.get(); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { - Instance.Handle handle = jobInstance.getHandle(); boolean refire = false; try { - handle.get().execute(context); + beanInstance.execute(context); } catch (JobExecutionException e) { refire = e.refireImmediately(); throw e; @@ -43,16 +44,9 @@ public void execute(JobExecutionContext context) throws JobExecutionException { @Override public void interrupt() throws UnableToInterruptJobException { - Instance.Handle handle = jobInstance.getHandle(); // delegate if possible; throw an exception in other cases if (InterruptableJob.class.isAssignableFrom(handle.getBean().getBeanClass())) { - try { - ((InterruptableJob) handle.get()).interrupt(); - } finally { - if (handle.getBean().getScope().equals(Dependent.class)) { - handle.destroy(); - } - } + ((InterruptableJob) beanInstance).interrupt(); } else { throw new UnableToInterruptJobException("Job " + handle.getBean().getBeanClass() + " can not be interrupted, since it does not implement " + InterruptableJob.class.getName()); 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 7e08b2a596de5..572192f58a976 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 @@ -1246,7 +1246,7 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler Scheduler) thr Instance instance = jobs.select(jobClass); if (instance.isResolvable()) { // This is a job backed by a CDI bean - return jobWithSpanWrapper(new CdiAwareJob(instance)); + return jobWithSpanWrapper(new CdiAwareJob(instance.getHandle())); } // Instantiate a plain job class return jobWithSpanWrapper(super.newJob(bundle, Scheduler)); From 99fad1c0976a95ee79d869849d27214a5b9aa1b6 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 29 Jul 2024 12:32:10 +0200 Subject: [PATCH 25/35] Upgrade to Jandex 3.2.1 (cherry picked from commit 64c808643a1e0f5a508fd1cd60fd671dd1715fd7) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/junit5-virtual-threads/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 9df65f525be75..424ba2c229961 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -20,7 +20,7 @@ 1.0.19 5.0.0 3.0.2 - 3.2.0 + 3.2.1 1.3.2 1 1.1.6 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 053deff0e84c4..45dbcdcdb4f0a 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -28,7 +28,7 @@ ${scala-maven-plugin.version} - 3.2.0 + 3.2.1 1.0.0 2.5.13 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 8c5d506d6e1a6..72af26b14889d 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -45,7 +45,7 @@ 2.0.1 1.8.0 - 3.2.0 + 3.2.1 3.6.0.Final 2.6.2 1.6.Final diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index f0b1e0ae8c6d5..9cc9c12dd2001 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -34,7 +34,7 @@ 1.3.2 1 UTF-8 - 3.2.0 + 3.2.1 1.37 diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index 875e37d03ee88..9ca7257288417 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -41,7 +41,7 @@ 3.13.0 3.2.1 3.2.5 - 3.2.0 + 3.2.1 2.24.1 1.11.0 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 9e1353a137609..675387eb89086 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -40,7 +40,7 @@ UTF-8 5.10.3 3.26.3 - 3.2.0 + 3.2.1 1.8.0 3.6.0.Final 2.6.2 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 5d8b59b862610..2c11c35670b92 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -45,7 +45,7 @@ UTF-8 4.1.0 - 3.2.0 + 3.2.1 1.14.11 5.10.3 3.9.8 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 6fd63c9de1735..cad9357939434 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -57,7 +57,7 @@ 5.12.0 ${project.version} 37 - 3.2.0 + 3.2.1 2.0.2 4.2.1 0.0.7 From 07380a7bdfde0e998d3805357756b5ba7b740c3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:39:59 +0000 Subject: [PATCH 26/35] Bump org.eclipse.parsson:parsson from 1.1.6 to 1.1.7 Bumps [org.eclipse.parsson:parsson](https://github.com/eclipse-ee4j/parsson) from 1.1.6 to 1.1.7. - [Release notes](https://github.com/eclipse-ee4j/parsson/releases) - [Commits](https://github.com/eclipse-ee4j/parsson/compare/1.1.6...1.1.7) --- updated-dependencies: - dependency-name: org.eclipse.parsson:parsson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 15b62d8d348ed116a86657f2b67a76675d0080ea) --- bom/application/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 424ba2c229961..ff59efe0487b1 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -23,7 +23,7 @@ 3.2.1 1.3.2 1 - 1.1.6 + 1.1.7 2.1.5.Final 3.1.3.Final 6.2.9.Final diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 2c11c35670b92..6beec95db70b0 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -41,7 +41,7 @@ 2.1.3 3.1.0 4.0.2 - 1.1.6 + 1.1.7 UTF-8 4.1.0 From 94b73087e47cca515e11c4154b18a12d13584a0d Mon Sep 17 00:00:00 2001 From: normalek Date: Wed, 31 Jul 2024 12:14:37 +0400 Subject: [PATCH 27/35] Update kafka-schema-registry-json-schema.adoc Fixed typo "yeay" -> "year" (cherry picked from commit 36fd79a8b9980e9a2abf227f1673f90875f204cb) --- docs/src/main/asciidoc/kafka-schema-registry-json-schema.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/kafka-schema-registry-json-schema.adoc b/docs/src/main/asciidoc/kafka-schema-registry-json-schema.adoc index e65e0cf8e3baa..e5e5fb99a7539 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-json-schema.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-json-schema.adoc @@ -76,7 +76,7 @@ Create a file called `src/main/resources/json-schema.json` with the schema for o "type": "string", "description": "The movie's title." }, - "yeay": { + "year": { "type": "integer", "description": "The movie's year." } From e1f18e28cc422947a48818e2a208dfbf4e719a30 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 31 Jul 2024 14:44:11 +0300 Subject: [PATCH 28/35] Fix Optional result type handling in Spring Data JPA Fixes: #42239 (cherry picked from commit eab22c321465bead34358e18c38d6e8558502193) --- .../data/deployment/generate/AbstractMethodsAdder.java | 4 ++-- .../spring/data/deployment/BookListCrudRepository.java | 4 ++++ .../spring/data/deployment/BookListCrudRepositoryTest.java | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java index 6033edfd7d663..1ffedab3d4003 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java @@ -124,7 +124,7 @@ protected void generateFindQueryResultHandling(MethodCreator methodCreator, Resu if (customResultType == null) { ResultHandle casted = tryBlock.checkCast(singleResult, entityClassInfo.name().toString()); ResultHandle optional = tryBlock.invokeStaticMethod( - MethodDescriptor.ofMethod(Optional.class, "of", Optional.class, Object.class), + MethodDescriptor.ofMethod(Optional.class, "ofNullable", Optional.class, Object.class), casted); tryBlock.returnValue(optional); } else { @@ -134,7 +134,7 @@ protected void generateFindQueryResultHandling(MethodCreator methodCreator, Resu originalResultType), singleResult); ResultHandle optional = tryBlock.invokeStaticMethod( - MethodDescriptor.ofMethod(Optional.class, "of", Optional.class, Object.class), + MethodDescriptor.ofMethod(Optional.class, "ofNullable", Optional.class, Object.class), customResult); tryBlock.returnValue(optional); } diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java index e429d08949499..b6def3e0addc7 100644 --- a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepository.java @@ -1,6 +1,10 @@ package io.quarkus.spring.data.deployment; +import java.util.Optional; + import org.springframework.data.repository.ListCrudRepository; public interface BookListCrudRepository extends ListCrudRepository { + + Optional findFirstByNameOrderByBid(String name); } diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java index 5b1a6a39cff29..f18b5cb2976d7 100644 --- a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BookListCrudRepositoryTest.java @@ -69,6 +69,12 @@ public void shouldSaveBooks() { "Harry Potter and the Prisoner of Azkaban", "Harry Potter and the Globet of Fire"); } + @Test + @Transactional + public void optionalWithNonExisting() { + assertThat(repo.findFirstByNameOrderByBid("foobar")).isEmpty(); + } + private Book populateBook(Integer id, String title) { Book book = new Book(); book.setBid(id); From ea409609b8d6b2cf80ef5f068b288bd87eee24f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:22:17 +0000 Subject: [PATCH 29/35] Bump io.quarkus.develocity:quarkus-project-develocity-extension Bumps [io.quarkus.develocity:quarkus-project-develocity-extension](https://github.com/quarkusio/quarkus-project-develocity-extension) from 1.1.3 to 1.1.4. - [Commits](https://github.com/quarkusio/quarkus-project-develocity-extension/compare/1.1.3...1.1.4) --- updated-dependencies: - dependency-name: io.quarkus.develocity:quarkus-project-develocity-extension dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 23d53ef38a3293d86281b6fb5cac3f085841328a) --- .mvn/extensions.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 07f3499ebe3e4..6ed8fa7d562cc 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -17,6 +17,6 @@ io.quarkus.develocity quarkus-project-develocity-extension - 1.1.3 + 1.1.4 From 640f5b262cebabfbbd68efc64e6647f6246fcc97 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sun, 28 Jul 2024 17:03:37 +0200 Subject: [PATCH 30/35] Manage opentelemetry-semconv-incubating (cherry picked from commit f4b0c5e5556ee08b8a436271d0193e117a59bbe2) --- bom/application/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index ff59efe0487b1..8f6efd1dbe668 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -6442,6 +6442,11 @@ + + io.opentelemetry.semconv + opentelemetry-semconv-incubating + ${opentelemetry-semconv.version} + From 06a11d415f245b5c749bb46cc53040ac7fd80d74 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sun, 28 Jul 2024 17:05:07 +0200 Subject: [PATCH 31/35] Remove superfluous opentelemetry-semconv exclusions on which semconv does not depend at all (cherry picked from commit 65a47aafad7c9fb2f24f1a03db42b14e3d4b1b10) --- bom/application/pom.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 8f6efd1dbe668..f477ead69e8fa 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -6431,16 +6431,6 @@ io.opentelemetry.semconv opentelemetry-semconv ${opentelemetry-semconv.version} - - - io.opentelemetry - opentelemetry-bom - - - io.opentelemetry - opentelemetry-api - - io.opentelemetry.semconv From 3e33252dd47021fc78fb675520b94dc59f53d2ee Mon Sep 17 00:00:00 2001 From: Danilo Piazzalunga Date: Sat, 27 Jul 2024 15:46:47 +0200 Subject: [PATCH 32/35] Rename suggested extensions in contextualizers' hints (cherry picked from commit fefafd3ab61cb59d4d754c999fa8c1a758da589b) --- .../reactive/deployment/QuarkusClientEndpointIndexer.java | 2 +- .../JsonMissingMessageBodyReaderErrorMessageContextualizer.java | 2 +- .../XmlMissingMessageBodyReaderErrorMessageContextualizer.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java index ba76b9388c4e9..be5dce445bdb7 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/QuarkusClientEndpointIndexer.java @@ -69,7 +69,7 @@ protected void handleAdditionalMethodProcessing(ResourceMethod method, ClassInfo protected void logMissingJsonWarning(MethodInfo info) { LOGGER.warnf("Quarkus detected the use of JSON in REST Client method '" + info.declaringClass().name() + "#" + info.name() - + "' but no JSON extension has been added. Consider adding 'quarkus-rest-client-reactive-jackson' (recommended) or 'quarkus-rest-client-reactive-jsonb'."); + + "' but no JSON extension has been added. Consider adding 'quarkus-rest-client-jackson' (recommended) or 'quarkus-rest-client-jsonb'."); } @Override diff --git a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/JsonMissingMessageBodyReaderErrorMessageContextualizer.java b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/JsonMissingMessageBodyReaderErrorMessageContextualizer.java index f5ffb518b63be..50211277ac8e2 100644 --- a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/JsonMissingMessageBodyReaderErrorMessageContextualizer.java +++ b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/JsonMissingMessageBodyReaderErrorMessageContextualizer.java @@ -9,7 +9,7 @@ public class JsonMissingMessageBodyReaderErrorMessageContextualizer implements @Override public String provideContextMessage(Input input) { if ((input.mediaType() != null) && input.mediaType().isCompatible(MediaType.APPLICATION_JSON_TYPE)) { - return "Consider adding one the 'quarkus-rest-client-reactive-jackson' or 'quarkus-rest-client-reactive-jsonb' extensions"; + return "Consider adding one the 'quarkus-rest-client-jackson' or 'quarkus-rest-client-jsonb' extensions"; } return null; } diff --git a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/XmlMissingMessageBodyReaderErrorMessageContextualizer.java b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/XmlMissingMessageBodyReaderErrorMessageContextualizer.java index 96927126ca442..bfb4b068f2f32 100644 --- a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/XmlMissingMessageBodyReaderErrorMessageContextualizer.java +++ b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/spi/XmlMissingMessageBodyReaderErrorMessageContextualizer.java @@ -9,7 +9,7 @@ public class XmlMissingMessageBodyReaderErrorMessageContextualizer implements @Override public String provideContextMessage(Input input) { if ((input.mediaType() != null) && input.mediaType().isCompatible(MediaType.APPLICATION_XML_TYPE)) { - return "Consider adding the 'quarkus-rest-client-reactive-jaxb' extension"; + return "Consider adding the 'quarkus-rest-client-jaxb' extension"; } return null; } From d491e7e55dc1e9d99a1ee58a3007b7ee406475af Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 1 Aug 2024 14:31:31 +0300 Subject: [PATCH 33/35] Add a note about the REST Client's dev mode proxy (cherry picked from commit 64830e707c07657f958b3c750c070d637e45257e) --- docs/src/main/asciidoc/rest-client.adoc | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index cafc648611853..09d9af02496f5 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -1657,6 +1657,34 @@ quarkus.rest-client.my-client.url=... NOTE: MicroProfile REST Client specification does not allow setting proxy credentials. In order to specify proxy user and proxy password programmatically, you need to cast your `RestClientBuilder` to `RestClientBuilderImpl`. +=== Local proxy for dev mode + +When using the REST Client in dev mode, Quarkus has the ability to stand up a pass-through proxy which can be used as a target for Wireshark (or similar tools) +in order to capture all the traffic originating from the REST Client (this really makes sense when the REST Client is used against HTTPS services) + +To enable this feature, all that needs to be done is set the `enable-local-proxy` configuration option for the configKey corresponding to the client for which proxying is desired. +For example: + +[source,properties] +---- +quarkus.rest-client.my-client.enable-local-proxy=true +---- + +When a REST Client does not use a config key (for example when it is created programmatically via `QuarkusRestClientBuilder`) then the class name can be used instead. +For example: + +[source,properties] +---- +quarkus.rest-client."org.acme.SomeClient".enable-local-proxy=true +---- + +The port the proxy is listening can be found in startup logs. An example entry is: + +[source] +---- +Started HTTP proxy server on http://localhost:38227 for REST Client 'org.acme.SomeClient' +---- + == Package and run the application Run the application with: From aae897415364f6be91ea05b24d09bc9cd27d21de Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Wed, 31 Jul 2024 20:41:56 +0200 Subject: [PATCH 34/35] Fix `QuarkusMainLauncher` not returning exit code `QuarkusMainLauncher` always returns `0`, because `currentApplication` is set to `null` in `io.quarkus.runtime.ApplicationLifecycleManager#run(io.quarkus.runtime.Application, java.lang.Class, java.util.function.BiConsumer, java.lang.String...)`. This change introduces a new callback to `ApplicationLifecycleManager` used by `StartupActionImpl` to know whether the application was actually started or not. (cherry picked from commit 8e7c255a708505f1f881f8f0f4ae8cebfd0fec62) --- .../runner/bootstrap/StartupActionImpl.java | 51 +++++++++---------- .../runtime/ApplicationLifecycleManager.java | 49 +++++++++++++----- .../quarkus/it/picocli/ExitCodeCommand.java | 17 +++++++ .../io/quarkus/it/picocli/TopTestCommand.java | 1 + .../io/quarkus/it/picocli/PicocliTest.java | 10 ++++ .../quarkus/it/picocli/ExitCodeCommand.java | 17 +++++++ .../io/quarkus/it/picocli/TestExitCode.java | 21 ++++++++ 7 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java create mode 100644 integration-tests/picocli/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java create mode 100644 integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestExitCode.java diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java index 4589a5d5407c7..e03976589005e 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -210,24 +211,20 @@ public int runMainClassBlocking(String... args) throws Exception { try { AtomicInteger result = new AtomicInteger(); Class lifecycleManager = Class.forName(ApplicationLifecycleManager.class.getName(), true, runtimeClassLoader); - Method getCurrentApplication = lifecycleManager.getDeclaredMethod("getCurrentApplication"); - Object oldApplication = getCurrentApplication.invoke(null); - lifecycleManager.getDeclaredMethod("setDefaultExitCodeHandler", Consumer.class).invoke(null, - new Consumer() { - @Override - public void accept(Integer integer) { - result.set(integer); - } - }); - // force init here - Class appClass = Class.forName(className, true, runtimeClassLoader); - Method start = appClass.getMethod("main", String[].class); - start.invoke(null, (Object) (args == null ? new String[0] : args)); + AtomicBoolean alreadyStarted = new AtomicBoolean(); + Method setDefaultExitCodeHandler = lifecycleManager.getDeclaredMethod("setDefaultExitCodeHandler", Consumer.class); + Method setAlreadyStartedCallback = lifecycleManager.getDeclaredMethod("setAlreadyStartedCallback", Consumer.class); - CountDownLatch latch = new CountDownLatch(1); - new Thread(new Runnable() { - @Override - public void run() { + try { + setDefaultExitCodeHandler.invoke(null, (Consumer) result::set); + setAlreadyStartedCallback.invoke(null, (Consumer) alreadyStarted::set); + // force init here + Class appClass = Class.forName(className, true, runtimeClassLoader); + Method start = appClass.getMethod("main", String[].class); + start.invoke(null, (Object) (args == null ? new String[0] : args)); + + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> { try { Class q = Class.forName(Quarkus.class.getName(), true, runtimeClassLoader); q.getMethod("blockingExit").invoke(null); @@ -236,17 +233,19 @@ public void run() { } finally { latch.countDown(); } + }).start(); + latch.await(); + + if (alreadyStarted.get()) { + //quarkus was not actually started by the main method + //just return + return 0; } - }).start(); - latch.await(); - - Object newApplication = getCurrentApplication.invoke(null); - if (oldApplication == newApplication) { - //quarkus was not actually started by the main method - //just return - return 0; + return result.get(); + } finally { + setDefaultExitCodeHandler.invoke(null, (Consumer) null); + setAlreadyStartedCallback.invoke(null, (Consumer) null); } - return result.get(); } finally { for (var closeTask : runtimeCloseTasks) { try { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java index d16e5ff7e94af..bc57782191e2e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationLifecycleManager.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -51,7 +50,7 @@ public class ApplicationLifecycleManager { // used by ShutdownEvent to propagate the information about shutdown reason public static volatile ShutdownEvent.ShutdownReason shutdownReason = ShutdownEvent.ShutdownReason.STANDARD; - private static volatile BiConsumer defaultExitCodeHandler = new BiConsumer() { + private static final BiConsumer MAIN_EXIT_CODE_HANDLER = new BiConsumer<>() { @Override public void accept(Integer integer, Throwable cause) { Logger logger = Logger.getLogger(Application.class); @@ -62,6 +61,12 @@ public void accept(Integer integer, Throwable cause) { System.exit(integer); } }; + private static final Consumer NOOP_ALREADY_STARTED_CALLBACK = new Consumer<>() { + @Override + public void accept(Boolean t) { + } + }; + private static volatile BiConsumer defaultExitCodeHandler = MAIN_EXIT_CODE_HANDLER; private ApplicationLifecycleManager() { @@ -77,8 +82,9 @@ private ApplicationLifecycleManager() { private static int exitCode = -1; private static volatile boolean shutdownRequested; - private static Application currentApplication; + private static volatile Application currentApplication; private static boolean vmShuttingDown; + private static Consumer alreadyStartedCallback = NOOP_ALREADY_STARTED_CALLBACK; private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"); private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac"); @@ -89,17 +95,19 @@ public static void run(Application application, String... args) { public static void run(Application application, Class quarkusApplication, BiConsumer exitCodeHandler, String... args) { + boolean alreadyStarted; stateLock.lock(); - //in tests, we might pass this method an already started application - //in this case we don't shut it down at the end - boolean alreadyStarted = application.isStarted(); - if (shutdownHookThread == null) { - registerHooks(exitCodeHandler == null ? defaultExitCodeHandler : exitCodeHandler); - } - if (currentApplication != null && !shutdownRequested) { - throw new IllegalStateException("Quarkus already running"); - } try { + //in tests, we might pass this method an already started application + //in this case we don't shut it down at the end + alreadyStarted = application.isStarted(); + alreadyStartedCallback.accept(alreadyStarted); + if (shutdownHookThread == null) { + registerHooks(exitCodeHandler == null ? defaultExitCodeHandler : exitCodeHandler); + } + if (currentApplication != null && !shutdownRequested) { + throw new IllegalStateException("Quarkus already running"); + } exitCode = -1; shutdownRequested = false; currentApplication = application; @@ -209,6 +217,7 @@ public static void run(Application application, Class defaultExitCodeHandler) { - Objects.requireNonNull(defaultExitCodeHandler); + if (defaultExitCodeHandler == null) { + defaultExitCodeHandler = MAIN_EXIT_CODE_HANDLER; + } ApplicationLifecycleManager.defaultExitCodeHandler = defaultExitCodeHandler; } @@ -365,8 +376,18 @@ public static void setDefaultExitCodeHandler(BiConsumer defa * * @param defaultExitCodeHandler the new default exit handler */ + // Used by StartupActionImpl via reflection public static void setDefaultExitCodeHandler(Consumer defaultExitCodeHandler) { - setDefaultExitCodeHandler((exitCode, cause) -> defaultExitCodeHandler.accept(exitCode)); + BiConsumer biConsumer = defaultExitCodeHandler == null ? null + : (exitCode, cause) -> defaultExitCodeHandler.accept(exitCode); + setDefaultExitCodeHandler(biConsumer); + } + + @SuppressWarnings("unused") + // Used by StartupActionImpl via reflection + public static void setAlreadyStartedCallback(Consumer alreadyStartedCallback) { + ApplicationLifecycleManager.alreadyStartedCallback = alreadyStartedCallback != null ? alreadyStartedCallback + : NOOP_ALREADY_STARTED_CALLBACK; } /** diff --git a/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java b/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java new file mode 100644 index 0000000000000..3601951ed6ee7 --- /dev/null +++ b/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java @@ -0,0 +1,17 @@ +package io.quarkus.it.picocli; + +import java.util.concurrent.Callable; + +import picocli.CommandLine; + +@CommandLine.Command(name = "exitcode", versionProvider = DynamicVersionProvider.class) +public class ExitCodeCommand implements Callable { + + @CommandLine.Option(names = "--code") + int exitCode; + + @Override + public Integer call() { + return exitCode; + } +} diff --git a/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/TopTestCommand.java b/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/TopTestCommand.java index 66e98420e31bb..d51e5c4069841 100644 --- a/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/TopTestCommand.java +++ b/integration-tests/picocli-native/src/main/java/io/quarkus/it/picocli/TopTestCommand.java @@ -5,6 +5,7 @@ @TopCommand @CommandLine.Command(name = "test", mixinStandardHelpOptions = true, commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "%nOptions:%n", subcommands = { + ExitCodeCommand.class, CommandUsedAsParent.class, CompletionReflectionCommand.class, DefaultValueProviderCommand.class, diff --git a/integration-tests/picocli-native/src/test/java/io/quarkus/it/picocli/PicocliTest.java b/integration-tests/picocli-native/src/test/java/io/quarkus/it/picocli/PicocliTest.java index 2022710db4fa2..75542f961f53d 100644 --- a/integration-tests/picocli-native/src/test/java/io/quarkus/it/picocli/PicocliTest.java +++ b/integration-tests/picocli-native/src/test/java/io/quarkus/it/picocli/PicocliTest.java @@ -21,6 +21,16 @@ public class PicocliTest { private String value; + @Test + public void testExitCode(QuarkusMainLauncher launcher) { + LaunchResult result = launcher.launch("exitcode", "--code", Integer.toString(42)); + assertThat(result.exitCode()).isEqualTo(42); + result = launcher.launch("exitcode", "--code", Integer.toString(0)); + assertThat(result.exitCode()).isEqualTo(0); + result = launcher.launch("exitcode", "--code", Integer.toString(2)); + assertThat(result.exitCode()).isEqualTo(2); + } + @Test @Launch({ "test-command", "-f", "test.txt", "-f", "test2.txt", "-f", "test3.txt", "-s", "ERROR", "-h", "SOCKS=5.5.5.5", "-p", "privateValue", "pos1", "pos2" }) diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java new file mode 100644 index 0000000000000..4678f030a937e --- /dev/null +++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/ExitCodeCommand.java @@ -0,0 +1,17 @@ +package io.quarkus.it.picocli; + +import java.util.concurrent.Callable; + +import picocli.CommandLine; + +@CommandLine.Command(name = "exitcode") +public class ExitCodeCommand implements Callable { + + @CommandLine.Option(names = "--code") + int exitCode; + + @Override + public Integer call() { + return exitCode; + } +} diff --git a/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestExitCode.java b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestExitCode.java new file mode 100644 index 0000000000000..7e5b98e7de182 --- /dev/null +++ b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestExitCode.java @@ -0,0 +1,21 @@ +package io.quarkus.it.picocli; + +import static io.quarkus.it.picocli.TestUtils.createConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class TestExitCode { + + @RegisterExtension + static final QuarkusProdModeTest config = createConfig("hello-app", ExitCodeCommand.class) + .setCommandLineParameters("--code", "42"); + + @Test + public void simpleTest() { + assertThat(config.getExitCode()).isEqualTo(42); + } +} From 38099d156a01ecf4e58188355f6965d558189d53 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Thu, 1 Aug 2024 10:54:41 +0200 Subject: [PATCH 35/35] Bump smallrye-config from 3.9.0 to 3.9.1 Fixes #42240 (cherry picked from commit 54a4d3e822cb60d5c882b6cc4e013180d7772f92) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index f477ead69e8fa..7cf2c464062dc 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -51,7 +51,7 @@ 2.0 3.1.1 2.5.0 - 3.9.0 + 3.9.1 4.1.0 4.0.0 3.10.0