From 57ab518f881eac2c4942bfc3fb41f3114dbda143 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 6 Nov 2024 09:56:13 +0200 Subject: [PATCH 01/24] Remove use of Json in OIDC Relates to: #42748 (cherry picked from commit b7e58d6eca6e3a0d0dcdba59d4cf3c87d8a54988) --- .../runtime/banned-signatures.txt | 2 ++ .../oidc-client-registration/runtime/pom.xml | 14 ++++++++++++++ .../oidc/client/registration/ClientMetadata.java | 13 +++++++------ .../runtime/OidcClientRegistrationImpl.java | 9 +++++---- .../registration/runtime/RegisteredClientImpl.java | 5 +++-- .../oidc-common/runtime/banned-signatures.txt | 2 ++ extensions/oidc-common/runtime/pom.xml | 14 ++++++++++++++ .../oidc/common/runtime/AbstractJsonObject.java | 9 +++++---- extensions/oidc/runtime/banned-signatures.txt | 2 ++ extensions/oidc/runtime/pom.xml | 14 ++++++++++++++ .../providers/AzureAccessTokenCustomizer.java | 5 +++-- 11 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 extensions/oidc-client-registration/runtime/banned-signatures.txt create mode 100644 extensions/oidc-common/runtime/banned-signatures.txt create mode 100644 extensions/oidc/runtime/banned-signatures.txt diff --git a/extensions/oidc-client-registration/runtime/banned-signatures.txt b/extensions/oidc-client-registration/runtime/banned-signatures.txt new file mode 100644 index 0000000000000..be3e7aa782379 --- /dev/null +++ b/extensions/oidc-client-registration/runtime/banned-signatures.txt @@ -0,0 +1,2 @@ +@defaultMessage Don't jakarta.json.Json as it is ridiculously slow (see https://github.com/quarkusio/quarkus/issues/42748) +jakarta.json.Json diff --git a/extensions/oidc-client-registration/runtime/pom.xml b/extensions/oidc-client-registration/runtime/pom.xml index 5a7b0038d55bb..94a4d7a812aa2 100644 --- a/extensions/oidc-client-registration/runtime/pom.xml +++ b/extensions/oidc-client-registration/runtime/pom.xml @@ -64,6 +64,20 @@ + + de.thetaphi + forbiddenapis + + + verify-forbidden-apis + + + ./banned-signatures.txt + + + + + diff --git a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/ClientMetadata.java b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/ClientMetadata.java index 832460f2c0b1b..e1e24a19f0041 100644 --- a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/ClientMetadata.java +++ b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/ClientMetadata.java @@ -1,9 +1,10 @@ package io.quarkus.oidc.client.registration; +import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider; + import java.util.List; import java.util.Map; -import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; @@ -66,11 +67,11 @@ public static class Builder { boolean built = false; Builder() { - builder = Json.createObjectBuilder(); + builder = jsonProvider().createObjectBuilder(); } Builder(JsonObject json) { - builder = Json.createObjectBuilder(json); + builder = jsonProvider().createObjectBuilder(json); } public Builder clientName(String clientName) { @@ -86,7 +87,7 @@ public Builder redirectUri(String redirectUri) { throw new IllegalStateException(); } builder.add(OidcConstants.CLIENT_METADATA_REDIRECT_URIS, - Json.createArrayBuilder().add(redirectUri).build()); + jsonProvider().createArrayBuilder().add(redirectUri).build()); return this; } @@ -95,7 +96,7 @@ public Builder postLogoutUri(String postLogoutUri) { throw new IllegalStateException(); } builder.add(OidcConstants.CLIENT_METADATA_POST_LOGOUT_URIS, - Json.createArrayBuilder().add(postLogoutUri).build()); + jsonProvider().createArrayBuilder().add(postLogoutUri).build()); return this; } @@ -103,7 +104,7 @@ public Builder extraProps(Map extraProps) { if (built) { throw new IllegalStateException(); } - builder.addAll(Json.createObjectBuilder(extraProps)); + builder.addAll(jsonProvider().createObjectBuilder(extraProps)); return this; } diff --git a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java index be4e72d3e87f3..15048803b4fb6 100644 --- a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java +++ b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/OidcClientRegistrationImpl.java @@ -1,5 +1,7 @@ package io.quarkus.oidc.client.registration.runtime; +import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider; + import java.io.IOException; import java.net.ConnectException; import java.util.List; @@ -7,7 +9,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import jakarta.json.Json; import jakarta.json.JsonObjectBuilder; import org.jboss.logging.Logger; @@ -250,17 +251,17 @@ static class ClientRegistrationHelper { } static ClientMetadata createMetadata(Metadata metadata) { - JsonObjectBuilder json = Json.createObjectBuilder(); + JsonObjectBuilder json = jsonProvider().createObjectBuilder(); if (metadata.clientName.isPresent()) { json.add(OidcConstants.CLIENT_METADATA_CLIENT_NAME, metadata.clientName.get()); } if (metadata.redirectUri.isPresent()) { json.add(OidcConstants.CLIENT_METADATA_REDIRECT_URIS, - Json.createArrayBuilder().add(metadata.redirectUri.get())); + jsonProvider().createArrayBuilder().add(metadata.redirectUri.get())); } if (metadata.postLogoutUri.isPresent()) { json.add(OidcConstants.POST_LOGOUT_REDIRECT_URI, - Json.createArrayBuilder().add(metadata.postLogoutUri.get())); + jsonProvider().createArrayBuilder().add(metadata.postLogoutUri.get())); } for (Map.Entry entry : metadata.extraProps.entrySet()) { json.add(entry.getKey(), entry.getValue()); diff --git a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java index 0288735390dc7..9c25b8c12cce3 100644 --- a/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java +++ b/extensions/oidc-client-registration/runtime/src/main/java/io/quarkus/oidc/client/registration/runtime/RegisteredClientImpl.java @@ -1,12 +1,13 @@ package io.quarkus.oidc.client.registration.runtime; +import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider; + import java.io.IOException; import java.net.ConnectException; import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.json.Json; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonValue; @@ -94,7 +95,7 @@ public Uni update(ClientMetadata newMetadata) { throw new OidcClientRegistrationException("Client secret can not be modified"); } - JsonObjectBuilder builder = Json.createObjectBuilder(); + JsonObjectBuilder builder = jsonProvider().createObjectBuilder(); JsonObject newJsonObject = newMetadata.getJsonObject(); JsonObject currentJsonObject = registeredMetadata.getJsonObject(); diff --git a/extensions/oidc-common/runtime/banned-signatures.txt b/extensions/oidc-common/runtime/banned-signatures.txt new file mode 100644 index 0000000000000..be3e7aa782379 --- /dev/null +++ b/extensions/oidc-common/runtime/banned-signatures.txt @@ -0,0 +1,2 @@ +@defaultMessage Don't jakarta.json.Json as it is ridiculously slow (see https://github.com/quarkusio/quarkus/issues/42748) +jakarta.json.Json diff --git a/extensions/oidc-common/runtime/pom.xml b/extensions/oidc-common/runtime/pom.xml index 4a8e750382909..3600d112cbda2 100644 --- a/extensions/oidc-common/runtime/pom.xml +++ b/extensions/oidc-common/runtime/pom.xml @@ -78,6 +78,20 @@ + + de.thetaphi + forbiddenapis + + + verify-forbidden-apis + + + ./banned-signatures.txt + + + + + diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/AbstractJsonObject.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/AbstractJsonObject.java index 9959b2292f6ad..186f14253993d 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/AbstractJsonObject.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/AbstractJsonObject.java @@ -1,5 +1,7 @@ package io.quarkus.oidc.common.runtime; +import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider; + import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; @@ -7,7 +9,6 @@ import java.util.Map; import java.util.Set; -import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonNumber; import jakarta.json.JsonObject; @@ -20,7 +21,7 @@ public abstract class AbstractJsonObject { private JsonObject json; protected AbstractJsonObject() { - json = Json.createObjectBuilder().build(); + json = jsonProvider().createObjectBuilder().build(); } protected AbstractJsonObject(String jsonString) { @@ -54,7 +55,7 @@ public JsonObject getObject(String name) { } public JsonObject getJsonObject() { - return Json.createObjectBuilder(json).build(); + return jsonProvider().createObjectBuilder(json).build(); } public Object get(String name) { @@ -91,7 +92,7 @@ protected List getListOfStrings(String prop) { } public static JsonObject toJsonObject(String json) { - try (JsonReader jsonReader = Json.createReader(new StringReader(json))) { + try (JsonReader jsonReader = jsonProvider().createReader(new StringReader(json))) { return jsonReader.readObject(); } } diff --git a/extensions/oidc/runtime/banned-signatures.txt b/extensions/oidc/runtime/banned-signatures.txt new file mode 100644 index 0000000000000..be3e7aa782379 --- /dev/null +++ b/extensions/oidc/runtime/banned-signatures.txt @@ -0,0 +1,2 @@ +@defaultMessage Don't jakarta.json.Json as it is ridiculously slow (see https://github.com/quarkusio/quarkus/issues/42748) +jakarta.json.Json diff --git a/extensions/oidc/runtime/pom.xml b/extensions/oidc/runtime/pom.xml index 25bb4a266db35..70a6279628b80 100644 --- a/extensions/oidc/runtime/pom.xml +++ b/extensions/oidc/runtime/pom.xml @@ -88,6 +88,20 @@ + + de.thetaphi + forbiddenapis + + + verify-forbidden-apis + + + ./banned-signatures.txt + + + + + diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/AzureAccessTokenCustomizer.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/AzureAccessTokenCustomizer.java index edad83046e7be..0aa8b8e15ea50 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/AzureAccessTokenCustomizer.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/providers/AzureAccessTokenCustomizer.java @@ -1,11 +1,12 @@ package io.quarkus.oidc.runtime.providers; +import static io.quarkus.jsonp.JsonProviderHolder.jsonProvider; + import java.nio.charset.StandardCharsets; import java.util.Base64; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Named; -import jakarta.json.Json; import jakarta.json.JsonObject; import io.quarkus.oidc.OIDCException; @@ -24,7 +25,7 @@ public JsonObject customizeHeaders(JsonObject headers) { if (nonce != null) { byte[] nonceSha256 = OidcUtils.getSha256Digest(nonce.getBytes(StandardCharsets.UTF_8)); byte[] newNonceBytes = Base64.getUrlEncoder().withoutPadding().encode(nonceSha256); - return Json.createObjectBuilder(headers) + return jsonProvider().createObjectBuilder(headers) .add(OidcConstants.NONCE, new String(newNonceBytes, StandardCharsets.UTF_8)).build(); } return null; From 210f7632570a82800fc2b8ec04df320896f04b58 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 6 Nov 2024 09:52:34 +0100 Subject: [PATCH 02/24] Scheduler: fix OffLoadingInvoker - ScheduledInvoker#invoke() should never return null (cherry picked from commit 81c9a0f9543c52d9f5aacc479ac570c521581b04) --- .../runtime/DelayedExecutionInvoker.java | 7 +------ .../common/runtime/DelegateInvoker.java | 14 ++++++++++++++ .../common/runtime/OffloadingInvoker.java | 18 ++++++------------ .../common/runtime/ScheduledInvoker.java | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelayedExecutionInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelayedExecutionInvoker.java index 6e343ac35ab5e..4faa121b25794 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelayedExecutionInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/DelayedExecutionInvoker.java @@ -53,12 +53,7 @@ public CompletionStage invoke(ScheduledExecution execution) throws Excepti executor.schedule(new Runnable() { @Override public void run() { - try { - delegate.invoke(execution); - ret.complete(null); - } catch (Exception e) { - ret.completeExceptionally(e); - } + invokeComplete(ret, execution); } }, delay, TimeUnit.MILLISECONDS); return ret; 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 a2245862d7fb0..5b25e3bebd99b 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 @@ -2,6 +2,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; import io.quarkus.scheduler.ScheduledExecution; @@ -30,4 +31,17 @@ protected CompletionStage invokeDelegate(ScheduledExecution execution) { return CompletableFuture.failedStage(e); } } + + protected void invokeComplete(CompletableFuture ret, ScheduledExecution execution) { + invokeDelegate(execution).whenComplete(new BiConsumer<>() { + @Override + public void accept(Void r, Throwable t) { + if (t != null) { + ret.completeExceptionally(t); + } else { + ret.complete(null); + } + } + }); + } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/OffloadingInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/OffloadingInvoker.java index 3026c382fd282..23b8605aa6e5a 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/OffloadingInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/OffloadingInvoker.java @@ -1,6 +1,7 @@ package io.quarkus.scheduler.common.runtime; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import io.quarkus.scheduler.ScheduledExecution; @@ -28,6 +29,7 @@ public OffloadingInvoker(ScheduledInvoker delegate, Vertx vertx) { @Override public CompletionStage invoke(ScheduledExecution execution) throws Exception { + CompletableFuture ret = new CompletableFuture<>(); Context context = VertxContext.getOrCreateDuplicatedContext(vertx); VertxContextSafetyToggle.setContextSafe(context, true); if (delegate.isBlocking()) { @@ -40,7 +42,7 @@ public void handle(Void event) { VirtualThreadsRecorder.getCurrent().execute(new Runnable() { @Override public void run() { - doInvoke(execution); + invokeComplete(ret, execution); } }); } @@ -49,7 +51,7 @@ public void run() { context.executeBlocking(new Callable() { @Override public Void call() { - doInvoke(execution); + invokeComplete(ret, execution); return null; } }, false); @@ -58,19 +60,11 @@ public Void call() { context.runOnContext(new Handler() { @Override public void handle(Void event) { - doInvoke(execution); + invokeComplete(ret, execution); } }); } - return null; - } - - void doInvoke(ScheduledExecution execution) { - try { - delegate.invoke(execution); - } catch (Throwable t) { - // already logged by the StatusEmitterInvoker - } + return ret; } } diff --git a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java index a7f1f6a80e702..b57f91648ddfe 100644 --- a/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java +++ b/extensions/scheduler/common/src/main/java/io/quarkus/scheduler/common/runtime/ScheduledInvoker.java @@ -11,7 +11,7 @@ public interface ScheduledInvoker { /** * @param execution - * @return the result + * @return the result, never {@code null} * @throws Exception */ CompletionStage invoke(ScheduledExecution execution) throws Exception; From 37576b27754643e867bcf19235e4ca3c70d58916 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 6 Nov 2024 08:40:42 +0200 Subject: [PATCH 03/24] Allow for using RuntimeDelegate SPI in native mode Fixes: #44301 (cherry picked from commit 4af3754ebec8efa14dfb038ba710cabe1d8180e1) --- .../common/deployment/ResteasyReactiveCommonProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/resteasy-reactive/rest-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/rest-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 9d154c483eb96..c1fa069d6e2cc 100644 --- a/extensions/resteasy-reactive/rest-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/rest-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -27,7 +27,6 @@ import org.jboss.jandex.Indexer; import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.common.jaxrs.RuntimeDelegateImpl; import org.jboss.resteasy.reactive.common.model.InterceptorContainer; import org.jboss.resteasy.reactive.common.model.PreMatchInterceptorContainer; import org.jboss.resteasy.reactive.common.model.ResourceInterceptor; @@ -340,8 +339,7 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, @BuildStep void registerRuntimeDelegateImpl(BuildProducer serviceProviders) { - serviceProviders.produce(new ServiceProviderBuildItem(RuntimeDelegate.class.getName(), - RuntimeDelegateImpl.class.getName())); + serviceProviders.produce(ServiceProviderBuildItem.allProvidersFromClassPath(RuntimeDelegate.class.getName())); } /* From 98f98926186bd4467fcca96831f8b79ac603bca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 6 Nov 2024 09:08:06 +0100 Subject: [PATCH 04/24] Do not try to list synthetic injection points in "inactive bean" errors Attempting to list them results in an error message such as: ``` 04:07:27,906 INFO [app] Caused by: io.quarkus.runtime.configuration.ConfigurationException: Unable to find datasource '' for persistence unit 'default-reactive': Bean is not active: SYNTHETIC bean [class=io.vertx.pgclient.PgPool, id=WVL9cdM2vfa8AHSEpmajClhheoQ] 04:07:27,906 INFO [app] Reason: Datasource '' was deactivated automatically because its URL is not set. To activate the datasource, set configuration property 'quarkus.datasource.reactive.url'. Refer to https://quarkus.io/guides/datasource for guidance. 04:07:27,906 INFO [app] To avoid this exception while keeping the bean inactive: 04:07:27,906 INFO [app] - Configure all extensions consuming this bean as inactive as well, if they allow it, e.g. 'quarkus.someextension.active=false' 04:07:27,906 INFO [app] - Make sure that custom code only accesses this bean if it is active 04:07:27,907 INFO [app] - Inject the bean with 'Instance' instead of 'io.vertx.pgclient.PgPool' 04:07:27,907 INFO [app] This bean is injected into: 04:07:27,907 INFO [app] - 04:07:27,907 INFO [app] at io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil.unableToFindDataSource(PersistenceUnitUtil.java:115) ``` See the empty list item after "This bean is injected into"? (cherry picked from commit f4596591e4d7ab6b9a159cf3746c4254e1feacab) --- .../src/main/java/io/quarkus/arc/processor/BeanGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 6d839c1277323..1a1355759037d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -1100,7 +1100,7 @@ private void implementCreateForSyntheticBean(ClassCreator beanCreator, BeanInfo List matchingIPs = new ArrayList<>(); for (InjectionPointInfo injectionPoint : bean.getDeployment().getInjectionPoints()) { - if (bean.equals(injectionPoint.getResolvedBean())) { + if (!injectionPoint.isSynthetic() && bean.equals(injectionPoint.getResolvedBean())) { matchingIPs.add(injectionPoint); } } From c6376e373f858e48e960a34f2941acc092a215ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 6 Nov 2024 18:04:23 +0100 Subject: [PATCH 05/24] Small improvements to the Google Cloud Function guides (cherry picked from commit fba9f6cdf6d59922a1b82697f17bc292db6a020b) --- docs/src/main/asciidoc/funqy-gcp-functions.adoc | 15 ++++++++++----- docs/src/main/asciidoc/gcp-functions-http.adoc | 4 ++-- docs/src/main/asciidoc/gcp-functions.adoc | 15 ++++++++++----- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/src/main/asciidoc/funqy-gcp-functions.adoc b/docs/src/main/asciidoc/funqy-gcp-functions.adoc index 02109969b2499..85982963ee058 100644 --- a/docs/src/main/asciidoc/funqy-gcp-functions.adoc +++ b/docs/src/main/asciidoc/funqy-gcp-functions.adoc @@ -147,7 +147,7 @@ You will have a single JAR inside the `target/deployment` repository that contai Then you will be able to use `gcloud` to deploy your function to Google Cloud. The `gcloud` command will be different depending on which event triggers your function. -NOTE: We will use the Java 17 runtime but you can switch to the Java 21 runtime by using `--runtime=java21` instead of `--runtime=java17` on the deploy commands. +NOTE: We will use the Java 21 runtime, but you can switch to the Java 17 runtime by using `--runtime=java17` instead of `--runtime=java21` on the deploy commands. [WARNING] ==== @@ -168,7 +168,7 @@ Use this command to deploy to Google Cloud Functions: ---- gcloud functions deploy quarkus-example-funky-pubsub \ --entry-point=io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction \ - --runtime=java17 --trigger-resource hello_topic --trigger-event google.pubsub.topic.publish \ + --runtime=java21 --trigger-resource hello_topic --trigger-event google.pubsub.topic.publish \ --source=target/deployment ---- @@ -205,7 +205,7 @@ Then, use this command to deploy to Google Cloud Functions: ---- gcloud functions deploy quarkus-example-funky-storage \ --entry-point=io.quarkus.funqy.gcp.functions.FunqyBackgroundFunction \ - --runtime=java17 --trigger-resource quarkus-hello --trigger-event google.storage.object.finalize \ + --runtime=java21 --trigger-resource quarkus-hello --trigger-event google.storage.object.finalize \ --source=target/deployment ---- @@ -246,7 +246,7 @@ Then, use this command to deploy to Google Cloud Functions: ---- gcloud functions deploy quarkus-example-cloud-event --gen2 \ --entry-point=io.quarkus.funqy.gcp.functions.FunqyCloudEventsFunction \ - --runtime=java17 --trigger-bucket=example-cloud-event --source=target/deployment + --runtime=java21 --trigger-bucket=example-cloud-event --source=target/deployment ---- [IMPORTANT] @@ -371,7 +371,7 @@ This will call your cloud events function with an event on the `"MY_FILE.txt` fi Quarkus provides built-in support for testing your Funqy Google Cloud functions via the `quarkus-test-google-cloud-functions` dependency. -To use it, you must add the following test dependency in your `pom.xml`. +To use it, you must add the following test dependencies in your `pom.xml`. [source,xml] ---- @@ -380,6 +380,11 @@ To use it, you must add the following test dependency in your `pom.xml`. quarkus-test-google-cloud-functions test + + io.rest-assured + rest-assured + test + ---- This extension provides a `@WithFunction` annotation that can be used to annotate `@QuarkusTest` test cases to start a Cloud Function invoker before you test cases and stop it at the end. diff --git a/docs/src/main/asciidoc/gcp-functions-http.adoc b/docs/src/main/asciidoc/gcp-functions-http.adoc index 39d72b68a2073..0d0040952902c 100644 --- a/docs/src/main/asciidoc/gcp-functions-http.adoc +++ b/docs/src/main/asciidoc/gcp-functions-http.adoc @@ -162,13 +162,13 @@ The result of the previous command is a single JAR file inside the `target/deplo Then you will be able to use `gcloud` to deploy your function to Google Cloud. -NOTE: We will use the Java 17 runtime but you can switch to the Java 21 runtime by using `--runtime=java21` instead of `--runtime=java17` on the deploy commands. +NOTE: We will use the Java 21 runtime, but you can switch to the Java 17 runtime by using `--runtime=java17` instead of `--runtime=java21` on the deploy commands. [source,bash] ---- gcloud functions deploy quarkus-example-http \ --entry-point=io.quarkus.gcp.functions.http.QuarkusHttpFunction \ - --runtime=java17 --trigger-http --allow-unauthenticated --source=target/deployment + --runtime=java21 --trigger-http --allow-unauthenticated --source=target/deployment ---- [IMPORTANT] diff --git a/docs/src/main/asciidoc/gcp-functions.adoc b/docs/src/main/asciidoc/gcp-functions.adoc index 319c73b226e9d..4c3a53bb2ddc0 100644 --- a/docs/src/main/asciidoc/gcp-functions.adoc +++ b/docs/src/main/asciidoc/gcp-functions.adoc @@ -242,7 +242,7 @@ The result of the previous command is a single JAR file inside the `target/deplo Then you will be able to use `gcloud` to deploy your function to Google Cloud. The `gcloud` command will be different depending on which event triggers your function. -NOTE: We will use the Java 17 runtime but you can switch to the Java 21 runtime by using `--runtime=java21` instead of `--runtime=java17` on the deploy commands. +NOTE: We will use the Java 21 runtime but you can switch to the Java 17 runtime by using `--runtime=java17` instead of `--runtime=java21` on the deploy commands. [WARNING] ==== @@ -262,7 +262,7 @@ This is an example command to deploy your `HttpFunction` to Google Cloud: ---- gcloud functions deploy quarkus-example-http \ --entry-point=io.quarkus.gcp.functions.QuarkusHttpFunction \ - --runtime=java17 --trigger-http --allow-unauthenticated --source=target/deployment + --runtime=java21 --trigger-http --allow-unauthenticated --source=target/deployment ---- [IMPORTANT] @@ -289,7 +289,7 @@ it needs to use `--trigger-event google.storage.object.finalize` and the `--trig gcloud functions deploy quarkus-example-storage \ --entry-point=io.quarkus.gcp.functions.QuarkusBackgroundFunction \ --trigger-resource quarkus-hello --trigger-event google.storage.object.finalize \ - --runtime=java17 --source=target/deployment + --runtime=java21 --source=target/deployment ---- [IMPORTANT] @@ -315,7 +315,7 @@ it needs to use `--trigger-event google.pubsub.topic.publish` and the `--trigger ---- gcloud functions deploy quarkus-example-pubsub \ --entry-point=io.quarkus.gcp.functions.QuarkusBackgroundFunction \ - --runtime=java17 --trigger-resource hello_topic --trigger-event google.pubsub.topic.publish --source=target/deployment + --runtime=java21 --trigger-resource hello_topic --trigger-event google.pubsub.topic.publish --source=target/deployment ---- [IMPORTANT] @@ -341,7 +341,7 @@ it needs to use `--trigger-bucket` parameter with the name of a previously creat ---- gcloud functions deploy quarkus-example-cloud-event --gen2 \ --entry-point=io.quarkus.gcp.functions.QuarkusCloudEventsFunction \ - --runtime=java17 --trigger-bucket=example-cloud-event --source=target/deployment + --runtime=java21 --trigger-bucket=example-cloud-event --source=target/deployment ---- [IMPORTANT] @@ -485,6 +485,11 @@ To use it, you must add the following test dependency in your `pom.xml`. quarkus-test-google-cloud-functions test + + io.rest-assured + rest-assured + test + ---- This extension provides a `@WithFunction` annotation that can be used to annotate `@QuarkusTest` test cases to start a Cloud Function invoker before you test cases and stop it at the end. From 71364844765f0a04eec4922b522a54f9401d266e Mon Sep 17 00:00:00 2001 From: Frantisek Havel <42615282+fhavel@users.noreply.github.com> Date: Wed, 6 Nov 2024 08:37:06 +0100 Subject: [PATCH 06/24] Update opentelemetry-tracing.adoc * edited comment in docker-compose yaml, as Quarkus support HTTP export now * Fixing typo (cherry picked from commit 1e68752fbb1f700c389a7e44c8bea6d3c56d409c) --- docs/src/main/asciidoc/opentelemetry-tracing.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/opentelemetry-tracing.adoc b/docs/src/main/asciidoc/opentelemetry-tracing.adoc index ef62e35cbb403..3b0196ca44697 100644 --- a/docs/src/main/asciidoc/opentelemetry-tracing.adoc +++ b/docs/src/main/asciidoc/opentelemetry-tracing.adoc @@ -121,7 +121,7 @@ We have 2 options: * Take a look at: xref:observability-devservices-lgtm.adoc[Getting Started with Grafana-OTel-LGTM]. -This features a Quarkus Dev service including a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data. +This features a Quarkus Dev service including a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides an OTel collector to receive the data. === Jaeger to see traces option @@ -148,7 +148,7 @@ services: - "16686:16686" # Jaeger UI - "14268:14268" # Receive legacy OpenTracing traces, optional - "4317:4317" # OTLP gRPC receiver - - "4318:4318" # OTLP HTTP receiver, not yet used by Quarkus, optional + - "4318:4318" # OTLP HTTP receiver - "14250:14250" # Receive from external otel-collector, optional environment: - COLLECTOR_OTLP_ENABLED=true From 1f38df1bb50c4ee21bcbb369d2fb95961b1d4f1e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 5 Nov 2024 17:44:46 +0100 Subject: [PATCH 07/24] Do not run the Gradle tests when using -DquicklyDocs (cherry picked from commit e1fda94882e7f53bfeadd657c3b72773f4ef39b6) --- devtools/gradle/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index 398af0ca03beb..96c5786642531 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -192,6 +192,17 @@ assemble + + quickly-docs-build + + + quicklyDocs + + + + assemble + + quick-build-ci From 64de7962253b1dc5fc0cfca6cbddc97d7eb179fa Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 5 Nov 2024 17:45:03 +0100 Subject: [PATCH 08/24] Add some useful workflow tips to CONTRIBUTING.md (cherry picked from commit a728ac3d017c105a19e83fc9311b874fb61d6a3f) --- CONTRIBUTING.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74183fd8497d3..b0924e3f2997a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -400,6 +400,53 @@ Thus, it is recommended to use the following approach: Due to Quarkus being a large repository, having to rebuild the entire project every time a change is made isn't very productive. The following Maven tips can vastly speed up development when working on a specific extension. +#### Using mvnd + +[mvnd](https://github.com/apache/maven-mvnd) is a daemon for Maven providing faster builds. +It parallelizes your builds by default and makes sure the output is consistent even for a parallelized build. + +You can https://github.com/apache/maven-mvnd?tab=readme-ov-file#how-to-install-mvnd[install mvnd] with SDKMAN!, Homebrew... + +mvnd is a good companion for your Quarkus builds. + +Make sure you install the latest mvnd 1.0.x which embeds Maven 3.x as Quarkus does not support Maven 4 yet. +Once it is installed, you can use `mvnd` in your Maven command lines instead of the typical `mvn` or `./mvnw`. + +If anything goes wrong, you can stop the daemon and start fresh with `mvnd --stop`. + +#### Using aliases + +While building with `-Dquickly` or `-DquicklyDocs` is practical when contributing your first patches, +if you contribute to Quarkus often, it is recommended to have your own aliases - for instance to make sure your build is parallelized. + +Here are a couple of useful aliases that are good starting points - and that you will need to adapt to your environment: + +- `build-fast`: build the Quarkus artifacts and install them +- `build-docs`: run from the root of the project, build the documentation +- `format`: format the source code following our coding conventions +- `qss`: run the Quarkus CLI from a snapshot (make sure you build the artifacts first) + +- If using mvnd + +```sh +alias build-fast="mvnd -e -DskipDocs -DskipTests -DskipITs -Dinvoker.skip -DskipExtensionValidation -Dskip.gradle.tests -Dtruststore.skip clean install" +alias build-docs="mvnd -e -DskipTests -DskipITs -Dinvoker.skip -DskipExtensionValidation -Dskip.gradle.tests -Dtruststore.skip -Dno-test-modules -Dasciidoctor.fail-if=DEBUG clean install" +alias format="mvnd process-sources -Denforcer.skip -Dprotoc.skip" +alias qss="java -jar ${HOME}/git/quarkus/devtools/cli/target/quarkus-cli-999-SNAPSHOT-runner.jar" +``` + +- If using plain Maven + +```sh +alias build-fast="mvn -T0.8C -e -DskipDocs -DskipTests -DskipITs -Dinvoker.skip -DskipExtensionValidation -Dskip.gradle.tests -Dtruststore.skip clean install" +alias build-docs="mvn -T0.8C -e -DskipTests -DskipITs -Dinvoker.skip -DskipExtensionValidation -Dskip.gradle.tests -Dtruststore.skip -Dno-test-modules -Dasciidoctor.fail-if=DEBUG clean install" +alias format="mvn -T0.8C process-sources -Denforcer.skip -Dprotoc.skip" +alias qss="java -jar ${HOME}/git/quarkus/devtools/cli/target/quarkus-cli-999-SNAPSHOT-runner.jar" +``` + +Using `./mvnw` is often not practical in this case as you might want to call these aliases from a nested directory. +[gum](https://andresalmiray.com/gum-the-gradle-maven-wrapper/) might be useful in this case. + #### Building all modules of an extension Let's say you want to make changes to the `Jackson` extension. This extension contains the `deployment`, `runtime` From 1027099115e160fa0df70f2c5722df6cf6f48470 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Tue, 5 Nov 2024 14:12:28 +0100 Subject: [PATCH 09/24] Make sure the result from QCL.getElementsWithResource(name) does not include duplicates (cherry picked from commit 70e2f17917359f2fa9d3bbc25aaa2c98019a057b) --- .../bootstrap/app/CuratedApplication.java | 14 ++-- .../classloading/QuarkusClassLoader.java | 77 ++++++++++++++++--- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java index 852e3c9147f89..2a9a7f0462fb5 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java @@ -356,9 +356,7 @@ public QuarkusClassLoader createDeploymentClassLoader() { if (configuredClassLoading.isRemovedArtifact(dependency.getKey())) { continue; } - if (dependency.isRuntimeCp() && dependency.isJar() && - (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) || - configuredClassLoading.isReloadableArtifact(dependency.getKey()))) { + if (isReloadableRuntimeDependency(dependency)) { processCpElement(dependency, element -> addCpElement(builder, dependency, element)); } } @@ -368,6 +366,12 @@ public QuarkusClassLoader createDeploymentClassLoader() { return builder.build(); } + private boolean isReloadableRuntimeDependency(ResolvedDependency dependency) { + return dependency.isRuntimeCp() && dependency.isJar() && + (dependency.isReloadable() && appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey()) || + configuredClassLoading.isReloadableArtifact(dependency.getKey())); + } + public String getClassLoaderNameSuffix() { return quarkusBootstrap.getBaseName() != null ? " for " + quarkusBootstrap.getBaseName() : ""; } @@ -405,9 +409,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map addCpElement(builder, dependency, element)); } } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java index 9368790bf7509..7f8ff90de6705 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java @@ -625,22 +625,77 @@ public List getElementsWithResource(String name) { public List getElementsWithResource(String name, boolean localOnly) { ensureOpen(name); - boolean parentFirst = parentFirst(name, getClassPathResourceIndex()); + final boolean parentFirst = parentFirst(name, getClassPathResourceIndex()); - List ret = new ArrayList<>(); + List result = List.of(); - if (parentFirst && !localOnly && parent instanceof QuarkusClassLoader) { - ret.addAll(((QuarkusClassLoader) parent).getElementsWithResource(name)); + if (parentFirst && !localOnly && parent instanceof QuarkusClassLoader parentQcl) { + result = parentQcl.getElementsWithResource(name); } - List classPathElements = getClassPathResourceIndex().getClassPathElements(name); - ret.addAll(classPathElements); + result = joinAndDedupe(result, getClassPathResourceIndex().getClassPathElements(name)); - if (!parentFirst && !localOnly && parent instanceof QuarkusClassLoader) { - ret.addAll(((QuarkusClassLoader) parent).getElementsWithResource(name)); + if (!parentFirst && !localOnly && parent instanceof QuarkusClassLoader parentQcl) { + result = joinAndDedupe(result, parentQcl.getElementsWithResource(name)); } - return ret; + return result; + } + + /** + * Returns a list containing elements from two lists eliminating duplicates. Elements from the first list + * will appear in the result before elements from the second list. + *

+ * The current implementation assumes that none of the lists contains duplicates on their own but some elements + * may be present in both lists. + * + * @param list1 first list + * @param list2 second list + * @return resulting list + */ + private static List joinAndDedupe(List list1, List list2) { + // it appears, in the vast majority of cases at least one of the lists will be empty + if (list1.isEmpty()) { + return list2; + } + if (list2.isEmpty()) { + return list1; + } + final List result = new ArrayList<>(list1.size() + list2.size()); + // it looks like in most cases at this point list1 (representing elements from the parent cl) will contain only one element + if (list1.size() == 1) { + final T firstCpe = list1.get(0); + result.add(firstCpe); + for (var cpe : list2) { + if (cpe != firstCpe) { + result.add(cpe); + } + } + return result; + } + result.addAll(list1); + for (var cpe : list2) { + if (!containsReference(list1, cpe)) { + result.add(cpe); + } + } + return result; + } + + /** + * Checks whether a list contains an element that references the other argument. + * + * @param list list of elements + * @param e element to look for + * @return true if the list contains an element referencing {@code e}, otherwise - false + */ + private static boolean containsReference(List list, T e) { + for (int i = list.size() - 1; i >= 0; --i) { + if (e == list.get(i)) { + return true; + } + } + return false; } public Set getReloadableClassNames() { @@ -902,6 +957,10 @@ public QuarkusClassLoader build() { return new QuarkusClassLoader(this); } + @Override + public String toString() { + return "QuarkusClassLoader.Builder:" + name + "@" + Integer.toHexString(hashCode()); + } } public ClassLoader parent() { From 59658df30360cb32f57a5761effbc77942af80d0 Mon Sep 17 00:00:00 2001 From: Inaki Villar Date: Sun, 27 Oct 2024 19:44:00 -0700 Subject: [PATCH 10/24] avoid duplication descriptors in PlatformImportsImpl (cherry picked from commit a3ff2d32a67a5d05eb6f9215b0ad442537a86275) --- .../bootstrap/model/PlatformImportsImpl.java | 39 +++++++++++-------- .../bootstrap/model/PlatformImportsTest.java | 8 ++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java index 1b747cb158390..b332eb3e72bff 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java @@ -81,8 +81,10 @@ public void addPlatformDescriptor(String groupId, String artifactId, String clas artifactId.substring(0, artifactId.length() - BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX.length()), version); - platformImports.computeIfAbsent(bomCoords, c -> new PlatformImport()).descriptorFound = true; - platformBoms.add(bomCoords); + platformImports.computeIfAbsent(bomCoords, c -> { + platformBoms.add(bomCoords); + return new PlatformImport(); + }).descriptorFound = true; } public void addPlatformProperties(String groupId, String artifactId, String classifier, String type, String version, @@ -92,21 +94,24 @@ public void addPlatformProperties(String groupId, String artifactId, String clas artifactId.length() - BootstrapConstants.PLATFORM_PROPERTIES_ARTIFACT_ID_SUFFIX.length()), version); platformImports.computeIfAbsent(bomCoords, c -> new PlatformImport()); - importedPlatformBoms.computeIfAbsent(groupId, g -> new ArrayList<>()).add(bomCoords); - - final Properties props = new Properties(); - try (InputStream is = Files.newInputStream(propsPath)) { - props.load(is); - } catch (IOException e) { - throw new AppModelResolverException("Failed to read properties from " + propsPath, e); - } - for (Map.Entry prop : props.entrySet()) { - final String name = String.valueOf(prop.getKey()); - if (name.startsWith(BootstrapConstants.PLATFORM_PROPERTY_PREFIX)) { - if (isPlatformReleaseInfo(name)) { - addPlatformRelease(name, String.valueOf(prop.getValue())); - } else { - collectedProps.putIfAbsent(name, String.valueOf(prop.getValue().toString())); + importedPlatformBoms.computeIfAbsent(groupId, g -> new ArrayList<>()); + if (!importedPlatformBoms.get(groupId).contains(bomCoords)) { + importedPlatformBoms.get(groupId).add(bomCoords); + + final Properties props = new Properties(); + try (InputStream is = Files.newInputStream(propsPath)) { + props.load(is); + } catch (IOException e) { + throw new AppModelResolverException("Failed to read properties from " + propsPath, e); + } + for (Map.Entry prop : props.entrySet()) { + final String name = String.valueOf(prop.getKey()); + if (name.startsWith(BootstrapConstants.PLATFORM_PROPERTY_PREFIX)) { + if (isPlatformReleaseInfo(name)) { + addPlatformRelease(name, String.valueOf(prop.getValue())); + } else { + collectedProps.putIfAbsent(name, String.valueOf(prop.getValue().toString())); + } } } } diff --git a/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/bootstrap/model/PlatformImportsTest.java b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/bootstrap/model/PlatformImportsTest.java index 56106751314b2..1a0e70abcbe7d 100644 --- a/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/bootstrap/model/PlatformImportsTest.java +++ b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/bootstrap/model/PlatformImportsTest.java @@ -114,6 +114,14 @@ public void multiplePlatformReleaseInTheSameStream() throws Exception { GACTV.fromString("io.playground:acme-bom::pom:2.2.2"))))); } + @Test + public void duplicatePlatformDescriptorsAreIgnored() { + final PlatformImportsImpl pi = new PlatformImportsImpl(); + pi.addPlatformDescriptor("io.playground", "acme-bom-quarkus-platform-descriptor", "", "", "1.1"); + pi.addPlatformDescriptor("io.playground", "acme-bom-quarkus-platform-descriptor", "", "", "1.1"); + assertEquals(1, pi.getImportedPlatformBoms().size()); + } + private PlatformProps newPlatformProps() throws IOException { final PlatformProps p = new PlatformProps(); platformProps.add(p); From 0fe636941aba7171ab034303c22a0e3e83b11646 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 7 Nov 2024 16:57:40 +0200 Subject: [PATCH 11/24] Make @QuarkusMainTest respect `quarkus.test.profile.tags` Fixes: #44118 (cherry picked from commit a82eba2b70152f717cab73f8fafecaee18cde7cc) --- .../AbstractJvmQuarkusTestExtension.java | 42 ++++++++++++++++++- .../test/junit/QuarkusMainTestExtension.java | 9 +++- .../test/junit/QuarkusTestExtension.java | 41 +----------------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index 36500263f4638..a36ee0cc8ac64 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -21,6 +22,8 @@ import org.jboss.jandex.Index; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import io.quarkus.bootstrap.BootstrapConstants; @@ -41,7 +44,8 @@ import io.quarkus.test.common.RestorableSystemProperties; import io.quarkus.test.common.TestClassIndexer; -public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithContextExtension { +public class AbstractJvmQuarkusTestExtension extends AbstractQuarkusTestWithContextExtension + implements ExecutionCondition { protected static final String TEST_LOCATION = "test-location"; protected static final String TEST_CLASS = "test-class"; @@ -267,6 +271,42 @@ private Class findTestProfileAnnotation(Class c return null; } + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if (!context.getTestClass().isPresent()) { + return ConditionEvaluationResult.enabled("No test class specified"); + } + if (context.getTestInstance().isPresent()) { + return ConditionEvaluationResult.enabled("Quarkus Test Profile tags only affect classes"); + } + String tagsStr = System.getProperty("quarkus.test.profile.tags"); + if ((tagsStr == null) || tagsStr.isEmpty()) { + return ConditionEvaluationResult.enabled("No Quarkus Test Profile tags"); + } + Class testProfile = getQuarkusTestProfile(context); + if (testProfile == null) { + return ConditionEvaluationResult.disabled("Test '" + context.getRequiredTestClass() + + "' is not annotated with '@QuarkusTestProfile' but 'quarkus.profile.test.tags' was set"); + } + QuarkusTestProfile profileInstance; + try { + profileInstance = testProfile.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + Set testProfileTags = profileInstance.tags(); + String[] tags = tagsStr.split(","); + for (String tag : tags) { + String trimmedTag = tag.trim(); + if (testProfileTags.contains(trimmedTag)) { + return ConditionEvaluationResult.enabled("Tag '" + trimmedTag + "' is present on '" + testProfile + + "' which is used on test '" + context.getRequiredTestClass()); + } + } + return ConditionEvaluationResult.disabled("Test '" + context.getRequiredTestClass() + + "' disabled because 'quarkus.profile.test.tags' don't match the tags of '" + testProfile + "'"); + } + protected static class PrepareResult { protected final AugmentAction augmentAction; protected final QuarkusTestProfile profileInstance; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java index f68b87436684b..71776250160ec 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusMainTestExtension.java @@ -16,6 +16,8 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ParameterContext; @@ -38,7 +40,7 @@ public class QuarkusMainTestExtension extends AbstractJvmQuarkusTestExtension implements InvocationInterceptor, BeforeEachCallback, AfterEachCallback, ParameterResolver, BeforeAllCallback, - AfterAllCallback { + AfterAllCallback, ExecutionCondition { PrepareResult prepareResult; @@ -321,4 +323,9 @@ public void afterAll(ExtensionContext context) throws Exception { public void beforeAll(ExtensionContext context) throws Exception { currentTestClassStack.push(context.getRequiredTestClass()); } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + return super.evaluateExecutionCondition(context); + } } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 8afb99cf10bb4..91da01372bb78 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -24,7 +24,6 @@ import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; -import java.util.Set; import java.util.concurrent.CompletionException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; @@ -54,8 +53,6 @@ import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ConditionEvaluationResult; -import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ParameterContext; @@ -105,7 +102,7 @@ public class QuarkusTestExtension extends AbstractJvmQuarkusTestExtension implements BeforeEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, AfterEachCallback, BeforeAllCallback, InvocationInterceptor, AfterAllCallback, - ParameterResolver, ExecutionCondition { + ParameterResolver { private static final Logger log = Logger.getLogger(QuarkusTestExtension.class); @@ -1136,42 +1133,6 @@ private boolean testMethodInvokerHandlesParamType(Object testMethodInvoker, Para } } - @Override - public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - if (!context.getTestClass().isPresent()) { - return ConditionEvaluationResult.enabled("No test class specified"); - } - if (context.getTestInstance().isPresent()) { - return ConditionEvaluationResult.enabled("Quarkus Test Profile tags only affect classes"); - } - String tagsStr = System.getProperty("quarkus.test.profile.tags"); - if ((tagsStr == null) || tagsStr.isEmpty()) { - return ConditionEvaluationResult.enabled("No Quarkus Test Profile tags"); - } - Class testProfile = getQuarkusTestProfile(context); - if (testProfile == null) { - return ConditionEvaluationResult.disabled("Test '" + context.getRequiredTestClass() - + "' is not annotated with '@QuarkusTestProfile' but 'quarkus.profile.test.tags' was set"); - } - QuarkusTestProfile profileInstance; - try { - profileInstance = testProfile.getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - Set testProfileTags = profileInstance.tags(); - String[] tags = tagsStr.split(","); - for (String tag : tags) { - String trimmedTag = tag.trim(); - if (testProfileTags.contains(trimmedTag)) { - return ConditionEvaluationResult.enabled("Tag '" + trimmedTag + "' is present on '" + testProfile - + "' which is used on test '" + context.getRequiredTestClass()); - } - } - return ConditionEvaluationResult.disabled("Test '" + context.getRequiredTestClass() - + "' disabled because 'quarkus.profile.test.tags' don't match the tags of '" + testProfile + "'"); - } - public static class ExtensionState extends QuarkusTestExtensionState { public ExtensionState(Closeable testResourceManager, Closeable resource, Runnable clearCallbacks) { From 6d1c3dddafe311ececd50d9381c27e69c0cbbb31 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 8 Nov 2024 08:43:36 +0100 Subject: [PATCH 12/24] Qute: fix generation of qute-i18n-examples - handle localized enums correctly - fixes #44366 (cherry picked from commit 959a2e1e7de1c608ab9a76dfcbed7bdf85be7153) --- .../MessageBundleMethodBuildItem.java | 17 ++++- .../deployment/MessageBundleProcessor.java | 25 ++++++-- .../MessageBundleEnumExampleFileTest.java | 64 +++++++++++++++++++ .../java/io/quarkus/qute/i18n/Message.java | 3 +- 4 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java index 56809719f7b0a..725999bbd0b9a 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleMethodBuildItem.java @@ -18,15 +18,17 @@ public final class MessageBundleMethodBuildItem extends MultiBuildItem { private final MethodInfo method; private final String template; private final boolean isDefaultBundle; + private final boolean hasGeneratedTemplate; MessageBundleMethodBuildItem(String bundleName, String key, String templateId, MethodInfo method, String template, - boolean isDefaultBundle) { + boolean isDefaultBundle, boolean hasGeneratedTemplate) { this.bundleName = bundleName; this.key = key; this.templateId = templateId; this.method = method; this.template = template; this.isDefaultBundle = isDefaultBundle; + this.hasGeneratedTemplate = hasGeneratedTemplate; } public String getBundleName() { @@ -54,6 +56,11 @@ public MethodInfo getMethod() { return method; } + /** + * + * @return {@code true} if there is a corresponding method declared on the message bundle interface + * @see #getMethod() + */ public boolean hasMethod() { return method != null; } @@ -79,6 +86,14 @@ public boolean isDefaultBundle() { return isDefaultBundle; } + /** + * + * @return {@code true} if the template was generated, e.g. a message bundle method for an enum + */ + public boolean hasGeneratedTemplate() { + return hasGeneratedTemplate; + } + /** * * @return the path diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 8b3af1267819e..03783fc656530 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -701,8 +701,22 @@ void generateExamplePropertiesFiles(List messageBu List messages = entry.getValue(); messages.sort(Comparator.comparing(MessageBundleMethodBuildItem::getKey)); Path exampleProperties = generatedExamplesDir.resolve(entry.getKey() + ".properties"); - Files.write(exampleProperties, - messages.stream().map(m -> m.getMethod().name() + "=" + m.getTemplate()).collect(Collectors.toList())); + List lines = new ArrayList<>(); + for (MessageBundleMethodBuildItem m : messages) { + if (m.hasMethod()) { + if (m.hasGeneratedTemplate()) { + // Skip messages with generated templates + continue; + } + // Keys are mapped to method names + lines.add(m.getMethod().name() + "=" + m.getTemplate()); + } else { + // No corresponding method declared - use the key instead + // For example, there is no method for generated enum constant message keys + lines.add(m.getKey() + "=" + m.getTemplate()); + } + } + Files.write(exampleProperties, lines); } } @@ -991,6 +1005,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } keyMap.put(key, new SimpleMessageMethod(method)); + boolean generatedTemplate = false; String messageTemplate = messageTemplates.get(method.name()); if (messageTemplate == null) { messageTemplate = getMessageAnnotationValue(messageAnnotation); @@ -1042,6 +1057,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } generatedMessageTemplate.append("{/when}"); messageTemplate = generatedMessageTemplate.toString(); + generatedTemplate = true; } } } @@ -1067,7 +1083,7 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d } MessageBundleMethodBuildItem messageBundleMethod = new MessageBundleMethodBuildItem(bundleName, key, templateId, - method, messageTemplate, defaultBundleInterface == null); + method, messageTemplate, defaultBundleInterface == null, generatedTemplate); messageTemplateMethods .produce(messageBundleMethod); @@ -1138,8 +1154,7 @@ private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, Strin } MessageBundleMethodBuildItem messageBundleMethod = new MessageBundleMethodBuildItem(bundleName, enumConstantKey, - templateId, null, messageTemplate, - defaultBundleInterface == null); + templateId, null, messageTemplate, defaultBundleInterface == null, true); messageTemplateMethods.produce(messageBundleMethod); MethodCreator enumConstantMethod = bundleCreator.getMethodCreator(enumConstantKey, diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java new file mode 100644 index 0000000000000..008a289fa6340 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumExampleFileTest.java @@ -0,0 +1,64 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Properties; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class MessageBundleEnumExampleFileTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot(root -> root + .addClasses(Messages.class, MyEnum.class) + .addAsResource(new StringAsset(""" + myEnum_ON=On + myEnum_OFF=Off + myEnum_UNDEFINED=Undefined + """), + "messages/enu.properties")); + + @ProdBuildResults + ProdModeTestResults testResults; + + @Test + public void testExampleProperties() throws FileNotFoundException, IOException { + Path path = testResults.getBuildDir().resolve("qute-i18n-examples").resolve("enu.properties"); + assertTrue(path.toFile().canRead()); + Properties props = new Properties(); + props.load(new FileInputStream(path.toFile())); + assertEquals(3, props.size()); + assertTrue(props.containsKey("myEnum_ON")); + assertTrue(props.containsKey("myEnum_OFF")); + assertTrue(props.containsKey("myEnum_UNDEFINED")); + } + + @MessageBundle(value = "enu", locale = "en") + public interface Messages { + + // Replaced with: + // @Message("{#when myEnum}" + // + "{#is ON}{enu:myEnum_ON}" + // + "{#is OFF}{enu:myEnum_OFF}" + // + "{#is UNDEFINED}{enu:myEnum_UNDEFINED}" + // + "{/when}") + @Message + String myEnum(MyEnum myEnum); + + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java index 93c5fbe6b1327..b8b8a43ae5955 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java @@ -27,8 +27,7 @@ * There is a convenient way to localize enums. *

* If there is a message bundle method that accepts a single parameter of an enum type and has no message template defined then - * it - * receives a generated template: + * it receives a generated template: * *

  * {#when enumParamName}

From ce813140e38f2f7cb6d167bbdbb55267bb0cee2f Mon Sep 17 00:00:00 2001
From: Guillaume Smet 
Date: Fri, 8 Nov 2024 14:46:20 +0100
Subject: [PATCH 13/24] Fix some invalid configuration cases

TransactionManagerConfiguration was just plain weird so fixed it.
For the others, we need to mark the interfaces as being config or we
won't generate the Javadoc for them.

(cherry picked from commit bab4a5a351ef89436ae056b7b2e860dc9e650244)
---
 .../common/deployment/CommonConfig.java       |  2 +
 .../TransactionManagerConfiguration.java      | 84 +++++++++----------
 2 files changed, 44 insertions(+), 42 deletions(-)

diff --git a/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java
index 426240ae842ed..e3ff5f25f4984 100644
--- a/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java
+++ b/extensions/container-image/container-image-docker-common/deployment/src/main/java/io/quarkus/container/image/docker/common/deployment/CommonConfig.java
@@ -6,7 +6,9 @@
 
 import io.quarkus.runtime.annotations.ConfigDocDefault;
 import io.quarkus.runtime.annotations.ConfigDocMapKey;
+import io.quarkus.runtime.annotations.ConfigGroup;
 
+@ConfigGroup
 public interface CommonConfig {
     /**
      * Path to the JVM Dockerfile.
diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java
index 6719ea8e1d128..1f813fb8d7c5c 100644
--- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java
+++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java
@@ -72,47 +72,47 @@ public final class TransactionManagerConfiguration {
      */
     @ConfigItem
     public ObjectStoreConfig objectStore;
-}
-
-@ConfigGroup
-class ObjectStoreConfig {
-    /**
-     * The name of the directory where the transaction logs will be stored when using the {@code file-system} object store.
-     * If the value is not absolute then the directory is relative
-     * to the user.dir system property.
-     */
-    @ConfigItem(defaultValue = "ObjectStore")
-    public String directory;
-
-    /**
-     * The type of object store.
-     */
-    @ConfigItem(defaultValue = "file-system")
-    public ObjectStoreType type;
-
-    /**
-     * The name of the datasource where the transaction logs will be stored when using the {@code jdbc} object store.
-     * 

- * If undefined, it will use the default datasource. - */ - @ConfigItem - public Optional datasource = Optional.empty(); - /** - * Whether to create the table if it does not exist. - */ - @ConfigItem(defaultValue = "false") - public boolean createTable; - - /** - * Whether to drop the table on startup. - */ - @ConfigItem(defaultValue = "false") - public boolean dropTable; - - /** - * The prefix to apply to the table. - */ - @ConfigItem(defaultValue = "quarkus_") - public String tablePrefix; + @ConfigGroup + public static class ObjectStoreConfig { + /** + * The name of the directory where the transaction logs will be stored when using the {@code file-system} object store. + * If the value is not absolute then the directory is relative + * to the user.dir system property. + */ + @ConfigItem(defaultValue = "ObjectStore") + public String directory; + + /** + * The type of object store. + */ + @ConfigItem(defaultValue = "file-system") + public ObjectStoreType type; + + /** + * The name of the datasource where the transaction logs will be stored when using the {@code jdbc} object store. + *

+ * If undefined, it will use the default datasource. + */ + @ConfigItem + public Optional datasource = Optional.empty(); + + /** + * Whether to create the table if it does not exist. + */ + @ConfigItem(defaultValue = "false") + public boolean createTable; + + /** + * Whether to drop the table on startup. + */ + @ConfigItem(defaultValue = "false") + public boolean dropTable; + + /** + * The prefix to apply to the table. + */ + @ConfigItem(defaultValue = "quarkus_") + public String tablePrefix; + } } From 53dc7964d4b8c53e393972b15d027f8972952a09 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 8 Nov 2024 23:18:02 +0000 Subject: [PATCH 14/24] Fix Keycloak DevService property doc typo (cherry picked from commit bfb59fce578411d927959578f75aff2e0b2873d1) --- .../quarkus/devservices/keycloak/KeycloakDevServicesConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesConfig.java b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesConfig.java index 5652b15cc1ff9..77bdc8c63d43b 100644 --- a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesConfig.java +++ b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesConfig.java @@ -140,7 +140,7 @@ public interface KeycloakDevServicesConfig { boolean createRealm(); /** - * Specifies whether to create the default client id `quarkus-app` with a secret `secret`and register them + * Specifies whether to create the default client id `quarkus-app` with a secret `secret` and register them * if the {@link #createRealm} property is set to true. * For OIDC extension configuration properties `quarkus.oidc.client.id` and `quarkus.oidc.credentials.secret` will * be configured. From ec0a40d06e7ae4944ce7137e4941583b6cf9a0ce Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 11 Nov 2024 14:56:20 +0100 Subject: [PATCH 15/24] QuteErrorPageSetup: support templates that are not backed by a file - fixes #44412 (cherry picked from commit 8ce3dc5d4189ddd656e6f7394af299170e08be49) --- .../qute/deployment/QuteDevModeProcessor.java | 31 +++++++++++++++++++ .../runtime/devmode/QuteErrorPageSetup.java | 17 ++++++++++ 2 files changed, 48 insertions(+) create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteDevModeProcessor.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteDevModeProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteDevModeProcessor.java new file mode 100644 index 0000000000000..4baf3b0756616 --- /dev/null +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteDevModeProcessor.java @@ -0,0 +1,31 @@ +package io.quarkus.qute.deployment; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.qute.runtime.devmode.QuteErrorPageSetup; + +@BuildSteps(onlyIf = IsDevelopment.class) +public class QuteDevModeProcessor { + + @BuildStep + void collectGeneratedContents(List templatePaths, + BuildProducer errors) { + Map contents = new HashMap<>(); + for (TemplatePathBuildItem template : templatePaths) { + if (!template.isFileBased()) { + contents.put(template.getPath(), template.getContent()); + } + } + // Set the global that could be used at runtime when a qute error page is rendered + DevConsoleManager.setGlobal(QuteErrorPageSetup.GENERATED_CONTENTS, contents); + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/devmode/QuteErrorPageSetup.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/devmode/QuteErrorPageSetup.java index b7ced362defac..916522c98443f 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/devmode/QuteErrorPageSetup.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/devmode/QuteErrorPageSetup.java @@ -2,6 +2,7 @@ import java.io.BufferedReader; import java.io.IOException; +import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; @@ -12,6 +13,7 @@ import java.util.Comparator; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; @@ -19,6 +21,7 @@ import org.jboss.logging.Logger; import io.quarkus.dev.ErrorPageGenerators; +import io.quarkus.dev.console.DevConsoleManager; import io.quarkus.dev.spi.HotReplacementContext; import io.quarkus.dev.spi.HotReplacementSetup; import io.quarkus.qute.Engine; @@ -33,6 +36,8 @@ public class QuteErrorPageSetup implements HotReplacementSetup { private static final Logger LOG = Logger.getLogger(QuteErrorPageSetup.class); + public static final String GENERATED_CONTENTS = "io.quarkus.qute.generatedContents"; + private static final String TEMPLATE_EXCEPTION = "io.quarkus.qute.TemplateException"; private static final String ORIGIN = "io.quarkus.qute.TemplateNode$Origin"; @@ -139,6 +144,10 @@ String getProblemInfo(int index, Throwable problem, Template problemTemplate, Es LOG.warn("Unable to read the template source: " + templateId, e); } + if (sourceLines.isEmpty()) { + return Arrays.stream(messageLines).collect(Collectors.joining("
")); + } + List realLines = new ArrayList<>(); boolean endLinesSkipped = false; if (sourceLines.size() > 15) { @@ -187,6 +196,14 @@ private BufferedReader getBufferedReader(String templateId) throws IOException { } } } + // Source file not available - try to search the generated contents + Map generatedContents = DevConsoleManager.getGlobal(GENERATED_CONTENTS); + if (generatedContents != null) { + String template = generatedContents.get(templateId); + if (template != null) { + return new BufferedReader(new StringReader(template)); + } + } throw new IllegalStateException("Template source not available"); } From 2e2c25a6ba5f4a7d962d2f21b50d90698c5bfd72 Mon Sep 17 00:00:00 2001 From: mariofusco Date: Mon, 11 Nov 2024 17:47:29 +0100 Subject: [PATCH 16/24] Fix deserialization of null maps in reflection-free Jackson deserializers (cherry picked from commit f23faa26f7ac7926a18bbc1eaf9af5b5052e8f8a) --- .../processor/JacksonDeserializerFactory.java | 16 +++++----- .../jackson/deployment/test/MapWrapper.java | 32 +++++++++++++++++++ .../deployment/test/SimpleJsonResource.java | 7 ++++ .../deployment/test/SimpleJsonTest.java | 16 +++++++++- ...JsonWithReflectionFreeSerializersTest.java | 2 +- 5 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MapWrapper.java diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonDeserializerFactory.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonDeserializerFactory.java index fa1fdb002bf0a..ebc47ab056de6 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonDeserializerFactory.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/JacksonDeserializerFactory.java @@ -239,18 +239,18 @@ private boolean deserializeObject(ClassInfo classInfo, ResultHandle objHandle, C ResultHandle nextField = loopCreator .invokeInterfaceMethod(ofMethod(Iterator.class, "next", Object.class), fieldsIterator); ResultHandle mapEntry = loopCreator.checkCast(nextField, Map.Entry.class); - ResultHandle fieldName = loopCreator - .invokeInterfaceMethod(ofMethod(Map.Entry.class, "getKey", Object.class), mapEntry); ResultHandle fieldValue = loopCreator.checkCast(loopCreator .invokeInterfaceMethod(ofMethod(Map.Entry.class, "getValue", Object.class), mapEntry), JsonNode.class); - loopCreator.ifTrue(loopCreator.invokeVirtualMethod(ofMethod(JsonNode.class, "isNull", boolean.class), fieldValue)) - .trueBranch().continueScope(loopCreator); + BytecodeCreator fieldReader = loopCreator + .ifTrue(loopCreator.invokeVirtualMethod(ofMethod(JsonNode.class, "isNull", boolean.class), fieldValue)) + .falseBranch(); + + ResultHandle fieldName = fieldReader + .invokeInterfaceMethod(ofMethod(Map.Entry.class, "getKey", Object.class), mapEntry); + Switch.StringSwitch strSwitch = fieldReader.stringSwitch(fieldName); - Set deserializedFields = new HashSet<>(); - ResultHandle deserializationContext = deserialize.getMethodParam(1); - Switch.StringSwitch strSwitch = loopCreator.stringSwitch(fieldName); - return deserializeFields(classCreator, classInfo, deserializationContext, objHandle, fieldValue, deserializedFields, + return deserializeFields(classCreator, classInfo, deserialize.getMethodParam(1), objHandle, fieldValue, new HashSet<>(), strSwitch, parseTypeParameters(classInfo, classCreator)); } diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MapWrapper.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MapWrapper.java new file mode 100644 index 0000000000000..6bc5bda55d642 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MapWrapper.java @@ -0,0 +1,32 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import java.util.Map; + +public class MapWrapper { + + private String name; + private Map properties; + + public MapWrapper() { + } + + public MapWrapper(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } +} diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java index 855c43625c09e..861f01ce08a96 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java @@ -123,6 +123,13 @@ public StateRecord echoDog(StateRecord stateRecord) { return stateRecord; } + @POST + @Path("/null-map-echo") + @Consumes(MediaType.APPLICATION_JSON) + public MapWrapper echoNullMap(MapWrapper mapWrapper) { + return mapWrapper; + } + @EnableSecureSerialization @GET @Path("/abstract-cat") diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java index d2f22569f9a7a..a5fa4d498c923 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonTest.java @@ -36,7 +36,7 @@ public JavaArchive get() { AbstractPet.class, Dog.class, Cat.class, Veterinarian.class, AbstractNamedPet.class, AbstractUnsecuredPet.class, UnsecuredPet.class, SecuredPersonInterface.class, Frog.class, Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class, - NestedInterface.class, StateRecord.class) + NestedInterface.class, StateRecord.class, MapWrapper.class) .addAsResource(new StringAsset("admin-expression=admin\n" + "user-expression=user\n" + "birth-date-roles=alice,bob\n"), "application.properties"); @@ -733,4 +733,18 @@ public void testRecordEcho() { assertTrue(first >= 0); assertEquals(first, last); } + + @Test + public void testNullMapEcho() { + RestAssured + .with() + .body(new MapWrapper("test")) + .contentType("application/json; charset=utf-8") + .post("/simple/null-map-echo") + .then() + .statusCode(200) + .contentType("application/json") + .body("name", Matchers.is("test")) + .body("properties", Matchers.nullValue()); + } } diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java index 65dec05aa59a4..10ea3d373ce91 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonWithReflectionFreeSerializersTest.java @@ -25,7 +25,7 @@ public JavaArchive get() { AbstractPet.class, Dog.class, Cat.class, Veterinarian.class, AbstractNamedPet.class, AbstractUnsecuredPet.class, UnsecuredPet.class, SecuredPersonInterface.class, Frog.class, Pond.class, FrogBodyParts.class, FrogBodyParts.BodyPart.class, ContainerDTO.class, - NestedInterface.class, StateRecord.class) + NestedInterface.class, StateRecord.class, MapWrapper.class) .addAsResource(new StringAsset("admin-expression=admin\n" + "user-expression=user\n" + "birth-date-roles=alice,bob\n" + From 7f81c6639c3a8c8033396876f4ca6f7e6475e6fd Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 11 Nov 2024 13:37:42 +0000 Subject: [PATCH 17/24] Propagate Runtime properties in JBang Dev mode (cherry picked from commit 0878a19dabd97cdfaaebff060eac016efc0f4430) --- .../runner/bootstrap/AugmentActionImpl.java | 1 + .../jbang/JBangDevModeLauncherImpl.java | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java index 9e252dcb0fb33..50f315897cd65 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java @@ -305,6 +305,7 @@ private BuildResult runAugment(boolean firstRun, Set changedResources, .setTargetDir(quarkusBootstrap.getTargetDirectory()) .setDeploymentClassLoader(deploymentClassLoader) .setBuildSystemProperties(quarkusBootstrap.getBuildSystemProperties()) + .setRuntimeProperties(quarkusBootstrap.getRuntimeProperties()) .setEffectiveModel(curatedApplication.getApplicationModel()) .setDependencyInfoProvider(quarkusBootstrap.getDependencyInfoProvider()); if (quarkusBootstrap.getBaseName() != null) { diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangDevModeLauncherImpl.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangDevModeLauncherImpl.java index 207281a09cb35..49dbca6aee629 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangDevModeLauncherImpl.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/jbang/JBangDevModeLauncherImpl.java @@ -15,6 +15,7 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -85,7 +86,7 @@ public static Closeable main(String... args) { Path srcDir = projectRoot.resolve("src/main/java"); Files.createDirectories(srcDir); - Files.createSymbolicLink(srcDir.resolve(sourceFile.getFileName().toString()), sourceFile); + Path source = Files.createSymbolicLink(srcDir.resolve(sourceFile.getFileName().toString()), sourceFile); final LocalProject currentProject = LocalProject.loadWorkspace(projectRoot); final ResolvedDependency appArtifact = ResolvedDependencyBuilder.newInstance() .setCoords(currentProject.getAppArtifact(ArtifactCoords.TYPE_JAR)) @@ -93,6 +94,8 @@ public static Closeable main(String... args) { .setWorkspaceModule(currentProject.toWorkspaceModule()) .build(); + Properties configurationProperties = getConfigurationProperties(source); + //todo : proper support for everything final QuarkusBootstrap.Builder builder = QuarkusBootstrap.builder() .setBaseClassLoader(JBangDevModeLauncherImpl.class.getClassLoader()) @@ -117,7 +120,9 @@ public static Closeable main(String... args) { return artifact; }).collect(Collectors.toList())) .setApplicationRoot(targetClasses) - .setProjectRoot(projectRoot); + .setProjectRoot(projectRoot) + .setBuildSystemProperties(configurationProperties) + .setRuntimeProperties(configurationProperties); Map context = new HashMap<>(); context.put("app-project", currentProject); @@ -174,4 +179,19 @@ private static String getQuarkusVersion() { throw new RuntimeException(e); } } + + private static Properties getConfigurationProperties(final Path source) throws IOException { + Properties properties = new Properties(); + for (String line : Files.readAllLines(source)) { + if (line.startsWith("//Q:CONFIG")) { + String conf = line.substring(10).trim(); + int equals = conf.indexOf("="); + if (equals == -1) { + throw new RuntimeException("invalid config " + line); + } + properties.setProperty(conf.substring(0, equals), conf.substring(equals + 1)); + } + } + return properties; + } } From ed41748e3817df030f8b3ad9ff9b179afd9facb1 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 6 Nov 2024 15:27:52 +0200 Subject: [PATCH 18/24] Enable Brotli decompression Note: Zstd and Snappy remain unsupported. Fixes https://github.com/quarkusio/quarkus/issues/43392 (cherry picked from commit 588d3db0b47a9ad09a30209248a80f67461e8dae) --- .../runtime/graal/NettySubstitutions.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/NettySubstitutions.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/NettySubstitutions.java index 37a79003cd594..ce7cd265223b3 100644 --- a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/NettySubstitutions.java +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/graal/NettySubstitutions.java @@ -1,5 +1,6 @@ package io.quarkus.netty.runtime.graal; +import static io.netty.handler.codec.http.HttpHeaderValues.BR; import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; @@ -43,13 +44,13 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.compression.Brotli; +import io.netty.handler.codec.compression.BrotliDecoder; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.compression.ZlibWrapper; -import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; @@ -518,6 +519,10 @@ protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Excep return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper)); } + if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) { + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), new BrotliDecoder()); + } // 'identity' or unsupported return null; @@ -533,21 +538,23 @@ final class Target_io_netty_handler_codec_http2_DelegatingDecompressorFrameListe @Substitute protected EmbeddedChannel newContentDecompressor(ChannelHandlerContext ctx, CharSequence contentEncoding) throws Http2Exception { - if (!HttpHeaderValues.GZIP.contentEqualsIgnoreCase(contentEncoding) - && !HttpHeaderValues.X_GZIP.contentEqualsIgnoreCase(contentEncoding)) { - if (!HttpHeaderValues.DEFLATE.contentEqualsIgnoreCase(contentEncoding) - && !HttpHeaderValues.X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { - return null; - } else { - ZlibWrapper wrapper = this.strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE; - return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), - ctx.channel().config(), - new ChannelHandler[] { ZlibCodecFactory.newZlibDecoder(wrapper) }); - } - } else { - return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), ctx.channel().config(), - new ChannelHandler[] { ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP) }); + if (GZIP.contentEqualsIgnoreCase(contentEncoding) || X_GZIP.contentEqualsIgnoreCase(contentEncoding)) { + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)); } + if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { + final ZlibWrapper wrapper = strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE; + // To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly. + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper)); + } + if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) { + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), new BrotliDecoder()); + } + + // 'identity' or unsupported + return null; } } From cbc47eda4d0e1a1fea435600b151e4e628259338 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Sat, 9 Nov 2024 14:32:34 +0100 Subject: [PATCH 19/24] Adds decompressor tests (cherry picked from commit 1aa22234b20c1cd27d624f59ddd823f4839ab430) --- .../compressors/it/AllDecompressResource.java | 7 ++ .../compressors/it/RESTEndpointsTest.java | 92 +++++++++++++- .../src/test/resources/application.properties | 3 + .../compressors/it/DecompressResource.java | 90 ++++++++++++++ .../io/quarkus/compressors/it/Testflow.java | 113 ++++++++++++++---- .../compressors/it/RESTEndpointsTest.java | 4 +- 6 files changed, 279 insertions(+), 30 deletions(-) create mode 100644 integration-tests/vertx-http-compressors/all/src/main/java/io/quarkus/compressors/it/AllDecompressResource.java create mode 100644 integration-tests/vertx-http-compressors/app/src/main/java/io/quarkus/compressors/it/DecompressResource.java diff --git a/integration-tests/vertx-http-compressors/all/src/main/java/io/quarkus/compressors/it/AllDecompressResource.java b/integration-tests/vertx-http-compressors/all/src/main/java/io/quarkus/compressors/it/AllDecompressResource.java new file mode 100644 index 0000000000000..43f8794d8f3cd --- /dev/null +++ b/integration-tests/vertx-http-compressors/all/src/main/java/io/quarkus/compressors/it/AllDecompressResource.java @@ -0,0 +1,7 @@ +package io.quarkus.compressors.it; + +import jakarta.ws.rs.Path; + +@Path("/decompressed") +public class AllDecompressResource extends DecompressResource { +} diff --git a/integration-tests/vertx-http-compressors/all/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java b/integration-tests/vertx-http-compressors/all/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java index 1786ff5b8e7f2..e2a59400de177 100644 --- a/integration-tests/vertx-http-compressors/all/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java +++ b/integration-tests/vertx-http-compressors/all/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java @@ -1,6 +1,7 @@ package io.quarkus.compressors.it; -import static io.quarkus.compressors.it.Testflow.runTest; +import static io.quarkus.compressors.it.Testflow.runCompressorsTest; +import static io.quarkus.compressors.it.Testflow.runDecompressorsTest; import java.net.URL; @@ -14,7 +15,10 @@ public class RESTEndpointsTest { @TestHTTPResource(value = "/compressed") - URL url; + URL urlCompressed; + + @TestHTTPResource(value = "/decompressed") + URL urlDEcompressed; @ParameterizedTest @CsvSource(value = { @@ -31,6 +35,88 @@ public class RESTEndpointsTest { //@formatter:on }, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null") public void testCompressors(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) { - runTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength); + runCompressorsTest(urlCompressed.toString() + endpoint, acceptEncoding, contentEncoding, contentLength); + } + + @ParameterizedTest + @CsvSource(value = { + //@formatter:off + // Context | Accept-Encoding | Content-Encoding | Method + "/text | identity | br | POST", + "/text | identity | gzip | POST", + "/text | identity | deflate | POST", + "/text | identity | br | PUT", + "/text | identity | gzip | PUT", + "/text | identity | deflate | PUT", + "/text | deflate | br | POST", + "/text | deflate | gzip | POST", + "/text | deflate | deflate | POST", + "/text | gzip | br | PUT", + "/text | gzip | gzip | PUT", + "/text | gzip | deflate | PUT", + "/text | br | br | POST", + "/text | br | gzip | POST", + "/text | br | deflate | POST", + "/text | br | br | PUT", + "/text | br | gzip | PUT", + "/text | gzip,br,deflate | deflate | PUT", + "/json | identity | br | POST", + "/json | identity | gzip | POST", + "/json | identity | deflate | POST", + "/json | identity | br | PUT", + "/json | identity | gzip | PUT", + "/json | identity | deflate | PUT", + "/json | deflate | br | POST", + "/json | deflate | gzip | POST", + "/json | deflate | deflate | POST", + "/json | gzip | br | PUT", + "/json | gzip | gzip | PUT", + "/json | gzip | deflate | PUT", + "/json | br | br | POST", + "/json | br | gzip | POST", + "/json | br | deflate | POST", + "/json | br | br | PUT", + "/json | br | gzip | PUT", + "/json | gzip,br,deflate | deflate | PUT", + "/xml | identity | br | POST", + "/xml | identity | gzip | POST", + "/xml | identity | deflate | POST", + "/xml | identity | br | PUT", + "/xml | identity | gzip | PUT", + "/xml | identity | deflate | PUT", + "/xml | deflate | br | POST", + "/xml | deflate | gzip | POST", + "/xml | deflate | deflate | POST", + "/xml | gzip | br | PUT", + "/xml | gzip | gzip | PUT", + "/xml | gzip | deflate | PUT", + "/xml | br | br | POST", + "/xml | br | gzip | POST", + "/xml | br | deflate | POST", + "/xml | br | br | PUT", + "/xml | br | gzip | PUT", + "/xml | gzip,br,deflate | deflate | PUT", + "/xhtml | identity | br | POST", + "/xhtml | identity | gzip | POST", + "/xhtml | identity | deflate | POST", + "/xhtml | identity | br | PUT", + "/xhtml | identity | gzip | PUT", + "/xhtml | identity | deflate | PUT", + "/xhtml | deflate | br | POST", + "/xhtml | deflate | gzip | POST", + "/xhtml | deflate | deflate | POST", + "/xhtml | gzip | br | PUT", + "/xhtml | gzip | gzip | PUT", + "/xhtml | gzip | deflate | PUT", + "/xhtml | br | br | POST", + "/xhtml | br | gzip | POST", + "/xhtml | br | deflate | POST", + "/xhtml | br | br | PUT", + "/xhtml | br | gzip | PUT", + "/xhtml | gzip,br,deflate | deflate | PUT" + //@formatter:on + }, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null") + public void testDecompressors(String endpoint, String acceptEncoding, String contentEncoding, String method) { + runDecompressorsTest(urlDEcompressed.toString() + endpoint, acceptEncoding, contentEncoding, method); } } diff --git a/integration-tests/vertx-http-compressors/all/src/test/resources/application.properties b/integration-tests/vertx-http-compressors/all/src/test/resources/application.properties index d7bbc1ca381a0..9e910a8390a20 100644 --- a/integration-tests/vertx-http-compressors/all/src/test/resources/application.properties +++ b/integration-tests/vertx-http-compressors/all/src/test/resources/application.properties @@ -1,4 +1,7 @@ +# Enables sending clients compressed responses. quarkus.http.enable-compression=true +# Enables decompressing requests from clients. +quarkus.http.enable-decompression=true # Brotli is not present by default, so we add it all here: quarkus.http.compressors=deflate,gzip,br # This test the level actually makes impact. When left to default, diff --git a/integration-tests/vertx-http-compressors/app/src/main/java/io/quarkus/compressors/it/DecompressResource.java b/integration-tests/vertx-http-compressors/app/src/main/java/io/quarkus/compressors/it/DecompressResource.java new file mode 100644 index 0000000000000..6e1a19bd6f18f --- /dev/null +++ b/integration-tests/vertx-http-compressors/app/src/main/java/io/quarkus/compressors/it/DecompressResource.java @@ -0,0 +1,90 @@ +package io.quarkus.compressors.it; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +/** + * Resource with endpoints that consume compressed data + * in POST and PUT bodies from the client. + * Depending on the accept-encoding, the data is then + * compressed again and sent to the client + *
+ * e.g. Client sends a gzipped POST body and receives + * a brotli compressed response body. + *
+ * The endpoint looks like a dummy echo service, but + * there is compression and decompression going on behind + * the scenes in Vert.x. -> Netty. + *
+ * See: https://github.com/quarkusio/quarkus/pull/44348 + */ +public class DecompressResource { + + @POST + @Path("/text") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + public String textPost(String text) { + return text; + } + + @PUT + @Path("/text") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + public String textPut(String text) { + return text; + } + + @POST + @Path("/json") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public String jsonPost(String json) { + return json; + } + + @PUT + @Path("/json") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public String jsonPut(String json) { + return json; + } + + @POST + @Path("/xml") + @Produces(MediaType.TEXT_XML) + @Consumes(MediaType.TEXT_XML) + public String xmlPost(String xml) { + return xml; + } + + @PUT + @Path("/xml") + @Produces(MediaType.TEXT_XML) + @Consumes(MediaType.TEXT_XML) + public String xmlPut(String xml) { + return xml; + } + + @POST + @Path("/xhtml") + @Produces(MediaType.APPLICATION_XHTML_XML) + @Consumes(MediaType.APPLICATION_XHTML_XML) + public String xhtmlPost(String xhtml) { + return xhtml; + } + + @PUT + @Path("/xhtml") + @Produces(MediaType.APPLICATION_XHTML_XML) + @Consumes(MediaType.APPLICATION_XHTML_XML) + public String xhtmlPut(String xhtml) { + return xhtml; + } +} diff --git a/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java b/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java index 378486123faf9..f8a60d61c60ad 100644 --- a/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java +++ b/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java @@ -9,9 +9,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + +import com.aayushatharva.brotli4j.encoder.BrotliOutputStream; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -34,15 +40,15 @@ public class Testflow { public static final int COMPRESSION_TOLERANCE_PERCENT = 2; /** - * This test logic is shared by both "all" module and "some" module. - * See their RESTEndpointsTest classes. + * This test logic is shared by both "all" module and "some" module. See their RESTEndpointsTest classes. * * @param endpoint * @param acceptEncoding * @param contentEncoding * @param contentLength */ - public static void runTest(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) { + public static void runCompressorsTest(String endpoint, String acceptEncoding, String contentEncoding, + String contentLength) { LOG.infof("Endpoint %s; Accept-Encoding: %s; Content-Encoding: %s; Content-Length: %s", endpoint, acceptEncoding, contentEncoding, contentLength); // RestAssured @@ -97,31 +103,88 @@ public static void runTest(String endpoint, String acceptEncoding, String conten expectedLength + " plus " + COMPRESSION_TOLERANCE_PERCENT + "% tolerance, i.e. " + expectedLengthWithTolerance + "."); } + assertEquals(TEXT, decompress(actualEncoding, response.body().getBytes()), "Unexpected body text."); + } catch (InterruptedException | ExecutionException e) { + fail(e); + } + } - final String body; - if (actualEncoding != null && !"identity".equalsIgnoreCase(actualEncoding)) { - EmbeddedChannel channel = null; - if ("gzip".equalsIgnoreCase(actualEncoding)) { - channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.GZIP)); - } else if ("deflate".equalsIgnoreCase(actualEncoding)) { - channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.ZLIB)); - } else if ("br".equalsIgnoreCase(actualEncoding)) { - channel = new EmbeddedChannel(new BrotliDecoder()); - } else { - fail("Unexpected compression used by server: " + actualEncoding); - } - channel.writeInbound(Unpooled.copiedBuffer(response.body().getBytes())); - channel.finish(); - final ByteBuf decompressed = channel.readInbound(); - body = decompressed.readCharSequence(decompressed.readableBytes(), StandardCharsets.UTF_8).toString(); - } else { - body = response.body().toString(StandardCharsets.UTF_8); - } - - assertEquals(TEXT, body, - "Unexpected body text."); + public static void runDecompressorsTest(String endpoint, String acceptEncoding, String contentEncoding, + String method) { + LOG.infof("Endpoint %s; Accept-Encoding: %s; Content-Encoding: %s; Method: %s", + endpoint, acceptEncoding, contentEncoding, method); + final WebClient client = WebClient.create(Vertx.vertx(), new WebClientOptions() + .setLogActivity(true) + .setFollowRedirects(true) + .setDecompressionSupported(false)); + final CompletableFuture> future = new CompletableFuture<>(); + client.postAbs(endpoint) + .putHeader(HttpHeaders.CONTENT_ENCODING.toString(), contentEncoding) + .putHeader(HttpHeaders.ACCEPT.toString(), "*/*") + .sendBuffer(compress(contentEncoding, TEXT), ar -> { + if (ar.succeeded()) { + future.complete(ar.result()); + } else { + future.completeExceptionally(ar.cause()); + } + }); + try { + final HttpResponse response = future.get(); + final String actualEncoding = response.headers().get("content-encoding"); + final String body = decompress(actualEncoding, response.body().getBytes()); + assertEquals(OK.code(), response.statusCode(), "Http status must be OK."); + assertEquals(TEXT, body, "Unexpected body text."); } catch (InterruptedException | ExecutionException e) { fail(e); } } + + public static Buffer compress(String algorithm, String payload) { + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + if ("gzip".equalsIgnoreCase(algorithm)) { + try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) { + gzipStream.write(payload.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException("Gzip compression failed", e); + } + return Buffer.buffer(byteStream.toByteArray()); + } else if ("br".equalsIgnoreCase(algorithm)) { + try (BrotliOutputStream brotliStream = new BrotliOutputStream(byteStream)) { + brotliStream.write(payload.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException("Brotli compression failed", e); + } + return Buffer.buffer(byteStream.toByteArray()); + } else if ("deflate".equalsIgnoreCase(algorithm)) { + try (DeflaterOutputStream deflateStream = new DeflaterOutputStream(byteStream)) { + deflateStream.write(payload.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException("Deflate compression failed", e); + } + return Buffer.buffer(byteStream.toByteArray()); + } else { + throw new IllegalArgumentException("Unsupported encoding: " + algorithm); + } + } + + public static String decompress(String algorithm, byte[] payload) { + if (algorithm != null && !"identity".equalsIgnoreCase(algorithm)) { + final EmbeddedChannel channel; + if ("gzip".equalsIgnoreCase(algorithm)) { + channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.GZIP)); + } else if ("deflate".equalsIgnoreCase(algorithm)) { + channel = new EmbeddedChannel(newZlibDecoder(ZlibWrapper.ZLIB)); + } else if ("br".equalsIgnoreCase(algorithm)) { + channel = new EmbeddedChannel(new BrotliDecoder()); + } else { + throw new RuntimeException("Unexpected compression used by server: " + algorithm); + } + channel.writeInbound(Unpooled.copiedBuffer(payload)); + channel.finish(); + final ByteBuf decompressed = channel.readInbound(); + return decompressed.readCharSequence(decompressed.readableBytes(), StandardCharsets.UTF_8).toString(); + } else { + return new String(payload, StandardCharsets.UTF_8); + } + } } diff --git a/integration-tests/vertx-http-compressors/some/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java b/integration-tests/vertx-http-compressors/some/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java index d2f9efc94e39c..9763e151fd69f 100644 --- a/integration-tests/vertx-http-compressors/some/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java +++ b/integration-tests/vertx-http-compressors/some/src/test/java/io/quarkus/compressors/it/RESTEndpointsTest.java @@ -1,6 +1,6 @@ package io.quarkus.compressors.it; -import static io.quarkus.compressors.it.Testflow.runTest; +import static io.quarkus.compressors.it.Testflow.runCompressorsTest; import java.net.URL; @@ -30,6 +30,6 @@ public class RESTEndpointsTest { //@formatter:on }, delimiter = '|', ignoreLeadingAndTrailingWhitespace = true, nullValues = "null") public void testCompressors(String endpoint, String acceptEncoding, String contentEncoding, String contentLength) { - runTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength); + runCompressorsTest(url.toString() + endpoint, acceptEncoding, contentEncoding, contentLength); } } From dd35a81186161507bbe7b4c6c269ec4ffa379900 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Mon, 11 Nov 2024 17:38:40 +0100 Subject: [PATCH 20/24] Test loads brotli to do compression (cherry picked from commit 4174ccedf7261d850b028d21b273ad50354dc765) --- .../src/test/java/io/quarkus/compressors/it/Testflow.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java b/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java index f8a60d61c60ad..3f1e72cf6eb71 100644 --- a/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java +++ b/integration-tests/vertx-http-compressors/app/src/test/java/io/quarkus/compressors/it/Testflow.java @@ -17,6 +17,7 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; +import com.aayushatharva.brotli4j.Brotli4jLoader; import com.aayushatharva.brotli4j.encoder.BrotliOutputStream; import io.netty.buffer.ByteBuf; @@ -39,6 +40,11 @@ public class Testflow { // Vert.x/Netty versions over time. public static final int COMPRESSION_TOLERANCE_PERCENT = 2; + static { + // Our test code does compression + Brotli4jLoader.ensureAvailability(); + } + /** * This test logic is shared by both "all" module and "some" module. See their RESTEndpointsTest classes. * @@ -121,6 +127,7 @@ public static void runDecompressorsTest(String endpoint, String acceptEncoding, client.postAbs(endpoint) .putHeader(HttpHeaders.CONTENT_ENCODING.toString(), contentEncoding) .putHeader(HttpHeaders.ACCEPT.toString(), "*/*") + .putHeader(HttpHeaders.USER_AGENT.toString(), "Tester") .sendBuffer(compress(contentEncoding, TEXT), ar -> { if (ar.succeeded()) { future.complete(ar.result()); From 93072af0ae99d28d880edde0aa08b58bbbedf5de Mon Sep 17 00:00:00 2001 From: Inaki Villar Date: Mon, 11 Nov 2024 20:14:22 -0800 Subject: [PATCH 21/24] forcing initializing compilejava if empty (cherry picked from commit 0462367228f31e5fab319884c79bdde946dcc5e3) --- .../tasks/QuarkusApplicationModelTask.java | 32 +- .../descriptors/DefaultProjectDescriptor.java | 12 +- .../descriptors/ProjectDescriptorBuilder.java | 11 +- .../application/build.gradle.kts | 17 ++ .../main/kotlin/org/acme/GreetingResource.kt | 23 ++ .../resources/META-INF/resources/index.html | 285 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../build.gradle.kts | 43 +++ .../gradle.properties | 5 + .../module1/build.gradle.kts | 0 .../kotlin/org/acme/module1/SomeClass1.kt | 7 + .../src/main/resources/proto/module1.proto | 23 ++ .../grpc-multi-module-no-java/settings.gradle | 22 ++ ...GrpcMultiModuleNoJavaQuarkusBuildTest.java | 27 ++ 14 files changed, 498 insertions(+), 10 deletions(-) create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/build.gradle.kts create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/kotlin/org/acme/GreetingResource.kt create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/META-INF/resources/index.html create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/application.properties create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/build.gradle.kts create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/gradle.properties create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/build.gradle.kts create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/kotlin/org/acme/module1/SomeClass1.kt create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/resources/proto/module1.proto create mode 100644 integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/settings.gradle create mode 100644 integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleNoJavaQuarkusBuildTest.java diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java index 0532cddf38cf4..6d21c80b275b2 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.function.Consumer; @@ -70,7 +71,6 @@ import io.quarkus.fs.util.ZipUtils; import io.quarkus.gradle.tooling.ToolingUtils; import io.quarkus.gradle.workspace.descriptors.DefaultProjectDescriptor; -import io.quarkus.gradle.workspace.descriptors.ProjectDescriptor; import io.quarkus.gradle.workspace.descriptors.ProjectDescriptor.TaskType; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; @@ -194,10 +194,13 @@ private ResolvedDependencyBuilder getProjectArtifact() { .setBuildDir(getLayout().getBuildDirectory().getAsFile().get().toPath()) .setBuildFile(getProjectBuildFile().getAsFile().get().toPath()); - ProjectDescriptor projectDescriptor = getProjectDescriptor().get(); - initProjectModule(projectDescriptor, mainModule, ArtifactSources.MAIN, DEFAULT_CLASSIFIER); + DefaultProjectDescriptor projectDescriptor = getProjectDescriptor().get(); + + initProjectModule(projectDescriptor, mainModule, ArtifactSources.MAIN, DEFAULT_CLASSIFIER, + getProjectDescriptor().get().getSourceSetTasksRaw()); if (getLaunchMode().get().isDevOrTest()) { - initProjectModule(projectDescriptor, mainModule, ArtifactSources.TEST, "tests"); + initProjectModule(projectDescriptor, mainModule, ArtifactSources.TEST, "tests", + getProjectDescriptor().get().getSourceSetTasksRaw()); } final PathList.Builder paths = PathList.builder(); collectDestinationDirs(mainModule.getMainSources().getSourceDirs(), paths); @@ -206,8 +209,8 @@ private ResolvedDependencyBuilder getProjectArtifact() { return appArtifact.setWorkspaceModule(mainModule).setResolvedPaths(paths.build()); } - private static void initProjectModule(ProjectDescriptor projectDescriptor, WorkspaceModule.Mutable module, - String sourceSetName, String classifier) { + private static void initProjectModule(DefaultProjectDescriptor projectDescriptor, WorkspaceModule.Mutable module, + String sourceSetName, String classifier, Map> sourceSetTasksRaw) { List sourceDirs = new ArrayList<>(); List resources = new ArrayList<>(); Set tasks = projectDescriptor.getTasksForSourceSet(sourceSetName.isEmpty() @@ -223,6 +226,23 @@ private static void initProjectModule(ProjectDescriptor projectDescriptor, Works resources.add(new DefaultSourceDir(source, destDir, null)); } } + + // Issue https://github.com/quarkusio/quarkus/issues/44384 + // no java sources are detected for compileJava before grpc configuration + // so we need to verify if the destination source for the task exist and add it manually + boolean containsJavaCompile = sourceDirs.stream() + .anyMatch(sourceDir -> "compileJava".equals(sourceDir.getValue("compiler", String.class))); + if (!containsJavaCompile && sourceSetTasksRaw.get("compileJava") != null) { + + sourceSetTasksRaw.get("compileJava").forEach(s -> { + File output = new File(s); + if (output.exists() && Objects.requireNonNull(output.listFiles()).length > 0) { + sourceDirs.add( + new DefaultSourceDir(output.toPath(), output.toPath(), null, Map.of("compiler", "compileJava"))); + } + }); + + } module.addArtifactSources(new DefaultArtifactSources(classifier, sourceDirs, resources)); } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/DefaultProjectDescriptor.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/DefaultProjectDescriptor.java index da56ac82b63db..5ef42d35a75ef 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/DefaultProjectDescriptor.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/DefaultProjectDescriptor.java @@ -18,14 +18,16 @@ public class DefaultProjectDescriptor implements Serializable, ProjectDescriptor private final Map tasks; private final Map> sourceSetTasks; + private final Map> sourceSetTasksRaw; public DefaultProjectDescriptor(File projectDir, File buildDir, File buildFile, Map tasks, - Map> sourceSetTasks) { + Map> sourceSetTasks, Map> sourceSetTasksRaw) { this.projectDir = projectDir; this.buildDir = buildDir; this.buildFile = buildFile; this.tasks = tasks; this.sourceSetTasks = sourceSetTasks; + this.sourceSetTasksRaw = sourceSetTasksRaw; } @Override @@ -47,6 +49,10 @@ public Map> getSourceSetTasks() { return sourceSetTasks; } + public Map> getSourceSetTasksRaw() { + return sourceSetTasksRaw; + } + public Map getTasks() { return tasks; } @@ -81,7 +87,8 @@ public DefaultProjectDescriptor withSourceSetView(Set acceptedSourceSets Map filteredTasks = tasks.entrySet().stream() .filter(e -> filteredSourceSets.values().stream().anyMatch(tasks -> tasks.contains(e.getKey()))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, TreeMap::new)); - return new DefaultProjectDescriptor(projectDir, buildDir, buildFile, filteredTasks, filteredSourceSets); + return new DefaultProjectDescriptor(projectDir, buildDir, buildFile, filteredTasks, filteredSourceSets, + sourceSetTasksRaw); } @Override @@ -92,6 +99,7 @@ public String toString() { ",\nbuildFile=" + buildFile + ",\ntasks=" + tasks + ",\nsourceSetTasks=" + sourceSetTasks + + ",\nsourceSetTasksRaw=" + sourceSetTasksRaw + "\n}"; } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/ProjectDescriptorBuilder.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/ProjectDescriptorBuilder.java index 9eab36470cb12..4bfbb5a6b1bbf 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/ProjectDescriptorBuilder.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/workspace/descriptors/ProjectDescriptorBuilder.java @@ -32,11 +32,13 @@ public class ProjectDescriptorBuilder { private final File buildFile; private final Map tasks; private final Map> sourceSetTasks; + private final Map> sourceSetTasksRaw; private final Set collectOnlySourceSets; private ProjectDescriptorBuilder(Project project, Set collectOnlySourceSets) { this.tasks = new LinkedHashMap<>(); this.sourceSetTasks = new LinkedHashMap<>(); + this.sourceSetTasksRaw = new LinkedHashMap<>(); this.buildFile = project.getBuildFile(); this.projectDir = project.getLayout().getProjectDirectory().getAsFile(); this.buildDir = project.getLayout().getBuildDirectory().get().getAsFile(); @@ -59,7 +61,8 @@ public static Provider buildForApp(Project target) { builder.buildDir, builder.buildFile, builder.tasks, - builder.sourceSetTasks)); + builder.sourceSetTasks, + builder.sourceSetTasksRaw)); } public static Provider buildForDependency(Project target) { @@ -76,10 +79,14 @@ public static Provider buildForDependency(Project targ builder.buildDir, builder.buildFile, builder.tasks, - builder.sourceSetTasks)); + builder.sourceSetTasks, + builder.sourceSetTasksRaw)); } private void readConfigurationFor(AbstractCompile task) { + sourceSetTasksRaw.computeIfAbsent(task.getName(), s -> new HashSet<>()) + .add(task.getDestinationDirectory().getAsFile().get().getAbsolutePath()); + if (task.getEnabled() && !task.getSource().isEmpty()) { File destDir = task.getDestinationDirectory().getAsFile().get(); task.getSource().visit(fileVisitDetails -> { diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/build.gradle.kts b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/build.gradle.kts new file mode 100644 index 0000000000000..8a97977aab3f8 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("io.quarkus") +} + +dependencies { + implementation(project(":module1")) + + implementation("io.quarkus:quarkus-rest-jackson") + implementation("io.quarkus:quarkus-rest") + implementation("io.quarkus:quarkus-kotlin") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("io.quarkus:quarkus-arc") + implementation("io.quarkus:quarkus-grpc") + + testImplementation("io.quarkus:quarkus-junit5") + testImplementation("io.rest-assured:rest-assured") +} diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/kotlin/org/acme/GreetingResource.kt b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/kotlin/org/acme/GreetingResource.kt new file mode 100644 index 0000000000000..3fe47cc77e17a --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/kotlin/org/acme/GreetingResource.kt @@ -0,0 +1,23 @@ +package org.acme + +import io.quarkus.grpc.GrpcClient +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType +import org.acme.module1.SomeClass1 +import org.acme.proto.Greeter + +@Path("/version") +class GreetingResource { + + @GrpcClient + lateinit var hello: Greeter + + @GET + @Produces(MediaType.TEXT_PLAIN) + fun getVersion(): String { + return SomeClass1().getVersion() + } +} + diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/META-INF/resources/index.html b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..c2ccecac788cf --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,285 @@ + + + + + code-with-quarkus - 1.0.0-SNAPSHOT + + + +

+
+
+ + + + + quarkus_logo_horizontal_rgb_1280px_reverse + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+

You just made a Quarkus application.

+

This page is served by Quarkus.

+ Visit the Dev UI +

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

+

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

+

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

+

Code: src/main/kotlin

+

Generated starter code:

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

Selected extensions

+
    +
  • RESTEasy Reactive Jackson
  • +
  • RESTEasy Reactive (guide)
  • +
  • Kotlin (guide)
  • +
+
Documentation
+

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

+
Set up your IDE
+

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

+
+
+
+ + diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/application.properties b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/application.properties new file mode 100644 index 0000000000000..dd47fa0de94ee --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/application/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.generate-code.grpc.scan-for-proto=org.acme:module1 \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/build.gradle.kts b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/build.gradle.kts new file mode 100644 index 0000000000000..8a0bba94937f6 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("jvm") version "2.0.21" + kotlin("plugin.allopen") version "2.0.21" + + id("io.quarkus") apply false +} +allprojects { + + repositories { + mavenLocal { + content { + includeGroupByRegex("io.quarkus.*") + includeGroup("org.hibernate.orm") + } + } + mavenCentral() + } + +} +subprojects { + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "org.jetbrains.kotlin.plugin.allopen") + + + tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") + } + + val quarkusPlatformGroupId: String by project + val quarkusPlatformArtifactId: String by project + val quarkusPlatformVersion: String by project + + dependencies { + implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + } + + allOpen { + annotation("jakarta.ws.rs.Path") + annotation("jakarta.enterprise.context.ApplicationScoped") + annotation("jakarta.persistence.Entity") + annotation("io.quarkus.test.junit.QuarkusTest") + } +} diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/gradle.properties b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/gradle.properties new file mode 100644 index 0000000000000..c1ec5e0323249 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/gradle.properties @@ -0,0 +1,5 @@ +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformGroupId=io.quarkus + +group=org.acme +version=1.0.0-SNAPSHOT diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/build.gradle.kts b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/build.gradle.kts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/kotlin/org/acme/module1/SomeClass1.kt b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/kotlin/org/acme/module1/SomeClass1.kt new file mode 100644 index 0000000000000..5bb8560fce908 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/kotlin/org/acme/module1/SomeClass1.kt @@ -0,0 +1,7 @@ +package org.acme.module1 + +class SomeClass1 { + fun getVersion(): String { + return "123" + } +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/resources/proto/module1.proto b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/resources/proto/module1.proto new file mode 100644 index 0000000000000..0378c5b7844a8 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/module1/src/main/resources/proto/module1.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.acme.proto"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/settings.gradle b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/settings.gradle new file mode 100644 index 0000000000000..7b5aee200c8e0 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/grpc-multi-module-no-java/settings.gradle @@ -0,0 +1,22 @@ +pluginManagement { + repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() + gradlePluginPortal() + } + //noinspection GroovyAssignabilityCheck + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } +} + +rootProject.name = 'quarkus-grpc-multi-module-no-java' + +include ':module1' +include ':application' + diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleNoJavaQuarkusBuildTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleNoJavaQuarkusBuildTest.java new file mode 100644 index 0000000000000..e31a2b8e1b78d --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/GrpcMultiModuleNoJavaQuarkusBuildTest.java @@ -0,0 +1,27 @@ +package io.quarkus.gradle; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +public class GrpcMultiModuleNoJavaQuarkusBuildTest extends QuarkusGradleWrapperTestBase { + + @Test + public void testGrpcMultiModuleBuild() throws Exception { + + final File projectDir = getProjectDir("grpc-multi-module-no-java"); + + final BuildResult build = runGradleWrapper(projectDir, "clean", "build"); + assertThat(BuildResult.isSuccessful(build.getTasks().get(":application:quarkusBuild"))).isTrue(); + assertThat(BuildResult.isSuccessful(build.getTasks().get(":application:quarkusAppPartsBuild"))).isTrue(); + + final Path applicationLib = projectDir.toPath().resolve("application").resolve("build").resolve("quarkus-app") + .resolve("lib").resolve("main"); + assertThat(applicationLib).exists(); + assertThat(applicationLib.resolve("org.acme.module1-1.0.0-SNAPSHOT.jar")).exists(); + } + +} From 4b1a5d88af3a3a5dc8a3e78d30cbb005612e7a02 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 11 Nov 2024 23:09:57 -0300 Subject: [PATCH 22/24] Hibernate: Silence DB connection info logging - Follow up from https://github.com/quarkusio/quarkus/commit/235f0a7535f8f1c50703804a1a59cdf27560994d (cherry picked from commit bcb01b2d8f894f64a51eb9794ef56155f5cef3b5) --- .../hibernate/orm/runtime/graal/DisableLoggingFeature.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java index ccd4be97371f3..b1ec1897f9ebe 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/graal/DisableLoggingFeature.java @@ -16,7 +16,8 @@ public class DisableLoggingFeature implements Feature { "org.hibernate.Version", "org.hibernate.annotations.common.Version", "SQL dialect", - "org.hibernate.cfg.Environment" + "org.hibernate.cfg.Environment", + "org.hibernate.orm.connections.pooling" }; private final Map categoryMap = new HashMap<>(CATEGORIES.length); From 8aaeac754e9ddf76816aef743d33c523958b5354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:16:54 +0000 Subject: [PATCH 23/24] Bump io.micrometer:micrometer-bom from 1.13.6 to 1.13.7 Bumps [io.micrometer:micrometer-bom](https://github.com/micrometer-metrics/micrometer) from 1.13.6 to 1.13.7. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.13.6...v1.13.7) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 3ac8e4fd2849f28f292073ed41fea233e6342e10) --- 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 153d34aace15b..b07533cf6edb0 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -37,7 +37,7 @@ 2.8.0-alpha 1.27.0-alpha 5.3.3 - 1.13.6 + 1.13.7 2.2.2 0.22.0 22.2 From 76a0ed6bff3d44e82f0a8f864c18dd4e8939f526 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:15:22 +0000 Subject: [PATCH 24/24] Bump resteasy.version from 6.2.10.Final to 6.2.11.Final Bumps `resteasy.version` from 6.2.10.Final to 6.2.11.Final. Updates `org.jboss.resteasy:resteasy-bom` from 6.2.10.Final to 6.2.11.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.10.Final...6.2.11.Final) Updates `org.jboss.resteasy:resteasy-core` from 6.2.10.Final to 6.2.11.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.10.Final...6.2.11.Final) Updates `org.jboss.resteasy:resteasy-core-spi` from 6.2.10.Final to 6.2.11.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.10.Final...6.2.11.Final) Updates `org.jboss.resteasy:resteasy-json-binding-provider` from 6.2.10.Final to 6.2.11.Final Updates `org.jboss.resteasy:resteasy-json-p-provider` from 6.2.10.Final to 6.2.11.Final Updates `org.jboss.resteasy:resteasy-jaxb-provider` from 6.2.10.Final to 6.2.11.Final Updates `org.jboss.resteasy:resteasy-jackson2-provider` from 6.2.10.Final to 6.2.11.Final Updates `org.jboss.resteasy:resteasy-rxjava2` from 6.2.10.Final to 6.2.11.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.10.Final...6.2.11.Final) Updates `org.jboss.resteasy:resteasy-links` from 6.2.10.Final to 6.2.11.Final - [Release notes](https://github.com/resteasy/resteasy/releases) - [Commits](https://github.com/resteasy/resteasy/compare/6.2.10.Final...6.2.11.Final) --- updated-dependencies: - dependency-name: org.jboss.resteasy:resteasy-bom dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-core-spi dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-json-binding-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-json-p-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-jaxb-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-jackson2-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-rxjava2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.jboss.resteasy:resteasy-links dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit b9904c4c1df35374ba8068193c412d6d10dcd1c8) --- 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 b07533cf6edb0..5836c5a86a11f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -28,7 +28,7 @@ 1.1.7 2.1.5.Final 3.1.3.Final - 6.2.10.Final + 6.2.11.Final 0.33.0 0.2.4 0.1.15