From cea8367ef40a869fb5486ba19b3f8c0e3164790e Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 15 Oct 2024 16:11:21 +0200 Subject: [PATCH 01/29] WebSockets Next: Security cleanup - do not force authentication in a dedicted handler so that it's possible to capture the SecurityIdentity before the HTTP upgrade but use the deferred identity instead - also change the HttpUpgradeContext to consume Uni instead of SecurityIdentity (cherry picked from commit 8d678da2ccd2ae52110fb006216b8f4fbf6d888b) --- .../next/deployment/WebSocketProcessor.java | 12 +----- .../upgrade/GlobalHttpUpgradeCheckTest.java | 15 +++----- .../websockets/next/HttpUpgradeCheck.java | 2 +- .../runtime/SecurityHttpUpgradeCheck.java | 7 ++-- .../next/runtime/WebSocketServerRecorder.java | 38 ++++--------------- 5 files changed, 19 insertions(+), 55 deletions(-) diff --git a/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java b/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java index c6c4444aef4e7..ec6054118bc56 100644 --- a/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java +++ b/extensions/websockets-next/deployment/src/main/java/io/quarkus/websockets/next/deployment/WebSocketProcessor.java @@ -88,7 +88,6 @@ import io.quarkus.security.spi.runtime.SecurityCheck; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HandlerType; -import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.websockets.next.HttpUpgradeCheck; import io.quarkus.websockets.next.InboundProcessingMode; import io.quarkus.websockets.next.WebSocketClientConnection; @@ -445,18 +444,11 @@ public String apply(String name) { @Record(RUNTIME_INIT) @BuildStep public void registerRoutes(WebSocketServerRecorder recorder, List generatedEndpoints, - HttpBuildTimeConfig httpConfig, Capabilities capabilities, BuildProducer routes) { for (GeneratedEndpointBuildItem endpoint : generatedEndpoints.stream().filter(GeneratedEndpointBuildItem::isServer) .toList()) { - RouteBuildItem.Builder builder = RouteBuildItem.builder(); - if (capabilities.isPresent(Capability.SECURITY) && !httpConfig.auth.proactive) { - // Add a special handler so that it's possible to capture the SecurityIdentity before the HTTP upgrade - builder.routeFunction(endpoint.path, recorder.initializeSecurityHandler()); - } else { - builder.route(endpoint.path); - } - builder + RouteBuildItem.Builder builder = RouteBuildItem.builder() + .route(endpoint.path) .displayOnNotFoundPage("WebSocket Endpoint") .handlerType(HandlerType.NORMAL) .handler(recorder.createEndpointHandler(endpoint.generatedClassName, endpoint.endpointId)); diff --git a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/upgrade/GlobalHttpUpgradeCheckTest.java b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/upgrade/GlobalHttpUpgradeCheckTest.java index cd00b64edd6e2..f4ce7202d1002 100644 --- a/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/upgrade/GlobalHttpUpgradeCheckTest.java +++ b/extensions/websockets-next/deployment/src/test/java/io/quarkus/websockets/next/test/upgrade/GlobalHttpUpgradeCheckTest.java @@ -118,10 +118,12 @@ public static abstract class ChainHttpUpgradeCheckBase implements HttpUpgradeChe @Override public Uni perform(HttpUpgradeContext request) { - if (identityPropagated(request) && testCheckChain(request)) { - return CheckResult.permitUpgrade(getResponseHeaders()); - } - return CheckResult.permitUpgrade(); + return request.securityIdentity().chain(identity -> { + if (identity != null && identity.isAnonymous() && testCheckChain(request)) { + return CheckResult.permitUpgrade(getResponseHeaders()); + } + return CheckResult.permitUpgrade(); + }); } protected Map> getResponseHeaders() { @@ -134,11 +136,6 @@ protected static boolean testCheckChain(HttpUpgradeContext context) { return context.httpRequest().headers().contains(TEST_CHECK_CHAIN); } - private static boolean identityPropagated(HttpUpgradeContext context) { - // point of this method is to check that identity is present in the context - return context.securityIdentity() != null && context.securityIdentity().isAnonymous(); - } - } @Dependent diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/HttpUpgradeCheck.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/HttpUpgradeCheck.java index 2e342aad4d00f..542efb782a146 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/HttpUpgradeCheck.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/HttpUpgradeCheck.java @@ -48,7 +48,7 @@ default boolean appliesTo(String endpointId) { * @param securityIdentity {@link SecurityIdentity}; the identity is null if the Quarkus Security extension is absent * @param endpointId {@link WebSocket#endpointId()} */ - record HttpUpgradeContext(HttpServerRequest httpRequest, SecurityIdentity securityIdentity, String endpointId) { + record HttpUpgradeContext(HttpServerRequest httpRequest, Uni securityIdentity, String endpointId) { } final class CheckResult { diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecurityHttpUpgradeCheck.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecurityHttpUpgradeCheck.java index f87baabf3244e..91f50a62c9406 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecurityHttpUpgradeCheck.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/SecurityHttpUpgradeCheck.java @@ -26,11 +26,10 @@ public class SecurityHttpUpgradeCheck implements HttpUpgradeCheck { @Override public Uni perform(HttpUpgradeContext context) { - return endpointToCheck - .get(context.endpointId()) - .nonBlockingApply(context.securityIdentity(), (MethodDescription) null, null) + return context.securityIdentity().chain(identity -> endpointToCheck.get(context.endpointId()) + .nonBlockingApply(identity, (MethodDescription) null, null) .replaceWith(CheckResult::permitUpgradeSync) - .onFailure(SecurityException.class).recoverWithItem(this::rejectUpgrade); + .onFailure(SecurityException.class).recoverWithItem(this::rejectUpgrade)); } @Override diff --git a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketServerRecorder.java b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketServerRecorder.java index c1e464e4b0190..ff5030af7ee24 100644 --- a/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketServerRecorder.java +++ b/extensions/websockets-next/runtime/src/main/java/io/quarkus/websockets/next/runtime/WebSocketServerRecorder.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import java.util.function.Supplier; import jakarta.enterprise.inject.Instance; @@ -31,7 +30,6 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.http.ServerWebSocket; -import io.vertx.ext.web.Route; import io.vertx.ext.web.RoutingContext; @Recorder @@ -62,34 +60,6 @@ public Object get() { }; } - public Consumer initializeSecurityHandler() { - return new Consumer() { - - @Override - public void accept(Route route) { - // Force authentication so that it's possible to capture the SecurityIdentity before the HTTP upgrade - route.handler(new Handler() { - - @Override - public void handle(RoutingContext ctx) { - if (ctx.user() == null) { - Uni deferredIdentity = ctx - .> get(QuarkusHttpUser.DEFERRED_IDENTITY_KEY); - deferredIdentity.subscribe().with(i -> { - if (ctx.response().ended()) { - return; - } - ctx.next(); - }, ctx::fail); - } else { - ctx.next(); - } - } - }); - } - }; - } - public Handler createEndpointHandler(String generatedEndpointClass, String endpointId) { ArcContainer container = Arc.container(); ConnectionManager connectionManager = container.instance(ConnectionManager.class).get(); @@ -142,7 +112,13 @@ private void httpUpgrade(RoutingContext ctx) { } private Uni checkHttpUpgrade(RoutingContext ctx, String endpointId) { - SecurityIdentity identity = ctx.user() instanceof QuarkusHttpUser user ? user.getSecurityIdentity() : null; + QuarkusHttpUser user = (QuarkusHttpUser) ctx.user(); + Uni identity; + if (user == null) { + identity = ctx.> get(QuarkusHttpUser.DEFERRED_IDENTITY_KEY); + } else { + identity = Uni.createFrom().item(user.getSecurityIdentity()); + } return checkHttpUpgrade(new HttpUpgradeContext(ctx.request(), identity, endpointId), httpUpgradeChecks, 0); } From 205fd4f11048c54720bb161e444e799a181e0fd2 Mon Sep 17 00:00:00 2001 From: Daniel Bobbert Date: Mon, 7 Oct 2024 09:49:40 +0200 Subject: [PATCH 02/29] Use generic return type of method to construct proper Jackson writer (cherry picked from commit 13f9095758ce9f04e5eedf68b00fb47f5e421d02) --- .../deployment/test/PolymorphicBase.java | 12 +++++ .../deployment/test/PolymorphicEndpoint.java | 26 +++++++++++ .../deployment/test/PolymorphicSub.java | 8 ++++ .../deployment/test/PolymorphicTest.java | 45 +++++++++++++++++++ .../runtime/mappers/JacksonMapperUtil.java | 35 +++++++++++++++ .../BasicServerJacksonMessageBodyWriter.java | 37 ++++++++++++++- ...eaturedServerJacksonMessageBodyWriter.java | 12 +++++ .../reactive/common/util/types/Types.java | 4 ++ 8 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicBase.java create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicEndpoint.java create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicSub.java create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicTest.java diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicBase.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicBase.java new file mode 100644 index 0000000000000..ee9785b993737 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicBase.java @@ -0,0 +1,12 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = PolymorphicSub.class, name = "sub") +}) +public class PolymorphicBase { + +} \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicEndpoint.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicEndpoint.java new file mode 100644 index 0000000000000..df74c0df2eaf5 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicEndpoint.java @@ -0,0 +1,26 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import java.util.List; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("poly") +public class PolymorphicEndpoint { + + @Produces(MediaType.APPLICATION_JSON) + @GET + @Path("single") + public PolymorphicBase getSingle() { + return new PolymorphicSub(); + } + + @Produces(MediaType.APPLICATION_JSON) + @GET + @Path("many") + public List getMany() { + return List.of(new PolymorphicSub(), new PolymorphicSub()); + } +} diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicSub.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicSub.java new file mode 100644 index 0000000000000..a28c321ffd951 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicSub.java @@ -0,0 +1,8 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import com.fasterxml.jackson.annotation.JsonTypeName; + +@JsonTypeName("sub") +public class PolymorphicSub extends PolymorphicBase { + +} \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicTest.java new file mode 100644 index 0000000000000..b6085250edd2e --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/PolymorphicTest.java @@ -0,0 +1,45 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import java.util.function.Supplier; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PolymorphicTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(PolymorphicEndpoint.class, PolymorphicBase.class, PolymorphicSub.class) + .addAsResource(new StringAsset(""), "application.properties"); + } + }); + + @Test + public void testSingle() { + RestAssured.get("/poly/single") + .then() + .statusCode(200) + .contentType("application/json") + .body(Matchers.is("{\"type\":\"sub\"}")); + } + + @Test + public void testMany() { + RestAssured.get("/poly/many") + .then() + .statusCode(200) + .contentType("application/json") + .body(Matchers.is("[{\"type\":\"sub\"},{\"type\":\"sub\"}]")); + } +} diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/JacksonMapperUtil.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/JacksonMapperUtil.java index 47f4e487e504b..f77e4b2563252 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/JacksonMapperUtil.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/JacksonMapperUtil.java @@ -1,6 +1,7 @@ package io.quarkus.resteasy.reactive.jackson.runtime.mappers; import java.lang.reflect.Array; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Map; import java.util.Optional; @@ -9,6 +10,7 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializerProvider; import io.quarkus.arc.Arc; @@ -47,6 +49,39 @@ public static boolean includeSecureField(String[] rolesAllowed) { return false; } + /** + * Determine the root type that should be used for serialization of generic types. + * Returns the appropriate root type or {@code null} if default serialization should be used. + */ + public static JavaType getGenericRootType(Type genericType, ObjectWriter defaultWriter) { + // Jackson needs additional type information when serializing generic types, as discussed here: + // https://github.com/FasterXML/jackson-databind/issues/336 and https://github.com/FasterXML/jackson-databind/issues/23 + // Parts of the code were taken from org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider + // which was used in quarkus-resteasy to handle this situation. + JavaType rootType = null; + if (genericType != null) { + /* + * 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root + * type since it prevents polymorphic type serialization. Since we really + * just need this for generics, let's only use generic type if it's truly + * generic. + */ + if (genericType.getClass() != Class.class) { + rootType = defaultWriter.getTypeFactory().constructType(genericType); + /* + * 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where + * type degenerates back into "Object.class" (as is the case with plain TypeVariable, + * for example), and not use that. + */ + if (rootType.getRawClass() == Object.class) { + rootType = null; + } + } + } + + return rootType; + } + private static class RolesAllowedHolder { private static final ArcContainer ARC_CONTAINER = Arc.container(); diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/BasicServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/BasicServerJacksonMessageBodyWriter.java index 48bfd7cfe7eba..2af17b6bc79f7 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/BasicServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/BasicServerJacksonMessageBodyWriter.java @@ -8,6 +8,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import jakarta.inject.Inject; import jakarta.ws.rs.WebApplicationException; @@ -17,18 +20,48 @@ import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil; + public class BasicServerJacksonMessageBodyWriter extends ServerMessageBodyWriter.AllWriteableMessageBodyWriter { private final ObjectWriter defaultWriter; + private final Map genericWriters = new ConcurrentHashMap<>(); @Inject public BasicServerJacksonMessageBodyWriter(ObjectMapper mapper) { this.defaultWriter = createDefaultWriter(mapper); } + private ObjectWriter getWriter(Type genericType, Object value) { + // make sure we properly handle polymorphism in generic collections + if (value != null && genericType != null) { + JavaType rootType = JacksonMapperUtil.getGenericRootType(genericType, defaultWriter); + // Check that the determined root type is really assignable from the given entity. + // A mismatch can happen, if a ServerResponseFilter replaces the response entity with another object + // that does not match the original signature of the method (see HalServerResponseFilter for an example) + if (rootType != null && rootType.isTypeOrSuperTypeOf(value.getClass())) { + ObjectWriter writer = genericWriters.get(rootType); + if (writer == null) { + // No cached writer for that type. Compute it once. + writer = genericWriters.computeIfAbsent(rootType, new Function<>() { + @Override + public ObjectWriter apply(JavaType type) { + return defaultWriter.forType(type); + } + }); + } + return writer; + } + } + + // no generic type given, or the generic type is just a class. Use the default writer. + return this.defaultWriter; + } + @Override public void writeResponse(Object o, Type genericType, ServerRequestContext context) throws WebApplicationException, IOException { @@ -36,7 +69,7 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte if (o instanceof String) { // YUK: done in order to avoid adding extra quotes... stream.write(((String) o).getBytes(StandardCharsets.UTF_8)); } else { - defaultWriter.writeValue(stream, o); + getWriter(genericType, o).writeValue(stream, o); } // we don't use try-with-resources because that results in writing to the http output without the exception mapping coming into play stream.close(); @@ -45,7 +78,7 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte @Override public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - doLegacyWrite(o, annotations, httpHeaders, entityStream, defaultWriter); + doLegacyWrite(o, annotations, httpHeaders, entityStream, getWriter(genericType, o)); } } diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java index df6601849601f..93d0b58600aae 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java @@ -26,10 +26,12 @@ import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder; +import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil; public class FullyFeaturedServerJacksonMessageBodyWriter extends ServerMessageBodyWriter.AllWriteableMessageBodyWriter { @@ -76,6 +78,16 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte } } + // make sure we properly handle polymorphism in generic collections + if (genericType != null && o != null) { + JavaType rootType = JacksonMapperUtil.getGenericRootType(genericType, effectiveWriter); + // Check that the determined root type is really assignable from the given entity. + // A mismatch can happen, if a ServerResponseFilter replaces the response entity with another object + // that does not match the original signature of the method (see HalServerResponseFilter for an example) + if (rootType != null && rootType.isTypeOrSuperTypeOf(o.getClass())) { + effectiveWriter = effectiveWriter.forType(rootType); + } + } effectiveWriter.writeValue(stream, o); } // we don't use try-with-resources because that results in writing to the http output without the exception mapping coming into play diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/types/Types.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/types/Types.java index f9955f940c370..ea2a3493cd6b4 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/types/Types.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/types/Types.java @@ -185,6 +185,10 @@ public static Type getEffectiveReturnType(Type returnType) { if (rawType == CompletionStage.class) { return getEffectiveReturnType(firstTypeArgument); } + // do another check, using isAssignableFrom() instead of "==" to catch derived types such as "CompletableFuture" as well + if (rawType instanceof Class rawClass && CompletionStage.class.isAssignableFrom(rawClass)) { + return getEffectiveReturnType(firstTypeArgument); + } if (rawType == Uni.class) { return getEffectiveReturnType(firstTypeArgument); } From 29476180d63807d358b41a45dd4d34f2179cd2ba Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Wed, 16 Oct 2024 12:39:14 +0200 Subject: [PATCH 03/29] Use the correct event type CertificateUpdatedEvent in TLS Registry reference (cherry picked from commit 7f56ac6531011d759a32e5749626651efb17e98a) --- docs/src/main/asciidoc/tls-registry-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/tls-registry-reference.adoc b/docs/src/main/asciidoc/tls-registry-reference.adoc index 4024783fe2e8a..86f9a23cc39b6 100644 --- a/docs/src/main/asciidoc/tls-registry-reference.adoc +++ b/docs/src/main/asciidoc/tls-registry-reference.adoc @@ -675,7 +675,7 @@ quarkus.tls.http.key-store.pem.0.cert=tls.crt quarkus.tls.http.key-store.pem.0.key=tls.key ---- -IMPORTANT: Impacted server and client may need to listen to the `CertificateReloadedEvent` to apply the new certificates. +IMPORTANT: Impacted server and client may need to listen to the `CertificateUpdatedEvent` to apply the new certificates. This is automatically done for the Quarkus HTTP server, including the management interface if it is enabled. ifndef::no-kubernetes-secrets-or-cert-manager[] From 52140422acb2b597eebc1ae13ee9cd7f1b98f767 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 16 Oct 2024 12:27:33 +0300 Subject: [PATCH 04/29] Move snappy native library resource registration to a feature Always registers the correct native library as the logic runs on the same architecture we are targeting (even when using containers). Closes https://github.com/quarkusio/quarkus/issues/43801 (cherry picked from commit b9ec68c48db047b41ff02004a76cc225d90686b4) --- .../client/deployment/KafkaProcessor.java | 23 ++----- .../kafka/client/deployment/SnappyUtils.java | 17 ----- .../client/runtime/graal/SnappyFeature.java | 63 +++++++++++++++++++ 3 files changed, 68 insertions(+), 35 deletions(-) delete mode 100644 extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/SnappyUtils.java create mode 100644 extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SnappyFeature.java diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index b3c0cf71e205f..70c2cd59df207 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -75,18 +75,17 @@ import io.quarkus.deployment.builditem.IndexDependencyBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LogCategoryBuildItem; +import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassConditionBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.deployment.pkg.builditem.NativeImageRunnerBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; import io.quarkus.kafka.client.runtime.KafkaAdminClient; import io.quarkus.kafka.client.runtime.KafkaBindingConverter; @@ -95,6 +94,7 @@ import io.quarkus.kafka.client.runtime.SnappyRecorder; import io.quarkus.kafka.client.runtime.devui.KafkaTopicClient; import io.quarkus.kafka.client.runtime.devui.KafkaUiUtils; +import io.quarkus.kafka.client.runtime.graal.SnappyFeature; import io.quarkus.kafka.client.serialization.BufferDeserializer; import io.quarkus.kafka.client.serialization.BufferSerializer; import io.quarkus.kafka.client.serialization.JsonArrayDeserializer; @@ -310,27 +310,14 @@ public void build( } @BuildStep(onlyIf = { HasSnappy.class, NativeOrNativeSourcesBuild.class }) - public void handleSnappyInNative(NativeImageRunnerBuildItem nativeImageRunner, - BuildProducer reflectiveClass, - BuildProducer nativeLibs) { + public void handleSnappyInNative(BuildProducer reflectiveClass, + BuildProducer feature) { reflectiveClass.produce(ReflectiveClassBuildItem.builder("org.xerial.snappy.SnappyInputStream", "org.xerial.snappy.SnappyOutputStream") .reason(getClass().getName() + " snappy support") .methods().fields().build()); - String root = "org/xerial/snappy/native/"; - // add linux64 native lib when targeting containers - if (nativeImageRunner.isContainerBuild()) { - String dir = "Linux/x86_64"; - String snappyNativeLibraryName = "libsnappyjava.so"; - String path = root + dir + "/" + snappyNativeLibraryName; - nativeLibs.produce(new NativeImageResourceBuildItem(path)); - } else { // otherwise the native lib of the platform this build runs on - String dir = SnappyUtils.getNativeLibFolderPathForCurrentOS(); - String snappyNativeLibraryName = System.mapLibraryName("snappyjava"); - String path = root + dir + "/" + snappyNativeLibraryName; - nativeLibs.produce(new NativeImageResourceBuildItem(path)); - } + feature.produce(new NativeImageFeatureBuildItem(SnappyFeature.class)); } @BuildStep(onlyIf = HasSnappy.class) diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/SnappyUtils.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/SnappyUtils.java deleted file mode 100644 index 35a9060e72073..0000000000000 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/SnappyUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.kafka.client.deployment; - -import org.xerial.snappy.OSInfo; - -/** - * This class should only be used if Snappy is available on the classpath. - */ -public class SnappyUtils { - - private SnappyUtils() { - // Avoid direct instantiation - } - - public static String getNativeLibFolderPathForCurrentOS() { - return OSInfo.getNativeLibFolderPathForCurrentOS(); - } -} diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SnappyFeature.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SnappyFeature.java new file mode 100644 index 0000000000000..e335b8ccbac78 --- /dev/null +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SnappyFeature.java @@ -0,0 +1,63 @@ +package io.quarkus.kafka.client.runtime.graal; + +import java.io.File; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; +import org.xerial.snappy.OSInfo; + +public class SnappyFeature implements Feature { + + /** + * This method uses code from org.xerial.snappy.SnappyLoader#findNativeLibrary + * to load the Snappy native library. The original code is licensed under the + * Apache License, Version 2.0 and includes the following notice: + * + *
+     *--------------------------------------------------------------------------
+     *  Copyright 2011 Taro L. Saito
+     *
+     *  Licensed under the Apache License, Version 2.0 (the "License");
+     *  you may not use this file except in compliance with the License.
+     *  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     *  Unless required by applicable law or agreed to in writing, software
+     *  distributed under the License is distributed on an "AS IS" BASIS,
+     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     *  See the License for the specific language governing permissions and
+     *  limitations under the License.
+     *--------------------------------------------------------------------------
+     * 
+ */ + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + final String KEY_SNAPPY_LIB_PATH = "org.xerial.snappy.lib.path"; + final String KEY_SNAPPY_LIB_NAME = "org.xerial.snappy.lib.name"; + + // Try to load the library in org.xerial.snappy.lib.path */ + String snappyNativeLibraryPath = System.getProperty(KEY_SNAPPY_LIB_PATH); + String snappyNativeLibraryName = System.getProperty(KEY_SNAPPY_LIB_NAME); + + // Resolve the library file name with a suffix (e.g., dll, .so, etc.) + if (snappyNativeLibraryName == null) { + snappyNativeLibraryName = System.mapLibraryName("snappyjava"); + } + + if (snappyNativeLibraryPath != null) { + File nativeLib = new File(snappyNativeLibraryPath, snappyNativeLibraryName); + if (nativeLib.exists()) { + RuntimeResourceAccess.addResource(OSInfo.class.getModule(), + snappyNativeLibraryPath + "/" + snappyNativeLibraryName); + return; + } + } + + // Load an OS-dependent native library inside a jar file + snappyNativeLibraryPath = "org/xerial/snappy/native/" + OSInfo.getNativeLibFolderPathForCurrentOS(); + String path = snappyNativeLibraryPath + "/" + snappyNativeLibraryName; + RuntimeResourceAccess.addResource(OSInfo.class.getModule(), path); + } + +} From b38ff31a320b3b97bbfd74be221e430589752c24 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 16 Oct 2024 09:46:24 +0200 Subject: [PATCH 05/29] ArC: register synthetic injection points in a separate phase - this fixes the problem where an synthetic injection point from a SyntheticBeanBuiltItem was not considered when detecting unused beans (cherry picked from commit 03ca63250a36f4c9fd537989bbbfce5894f6741b) --- .../quarkus/arc/deployment/ArcProcessor.java | 6 +- ...nremovableSyntheticInjectionPointTest.java | 108 ++++++++++++++++++ .../quarkus/arc/processor/BeanDeployment.java | 9 +- .../quarkus/arc/processor/BeanProcessor.java | 14 ++- .../test/unused/RemoveUnusedBeansTest.java | 2 +- 5 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnremovableSyntheticInjectionPointTest.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 6e1969a7caf00..ff175046a6960 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -460,10 +460,12 @@ public ObserverRegistrationPhaseBuildItem registerSyntheticObservers(BeanRegistr configurator.getValues().forEach(BeanConfigurator::done); } + BeanProcessor beanProcessor = beanRegistrationPhase.getBeanProcessor(); + beanProcessor.registerSyntheticInjectionPoints(beanRegistrationPhase.getContext()); + // Initialize the type -> bean map - beanRegistrationPhase.getBeanProcessor().getBeanDeployment().initBeanByTypeMap(); + beanProcessor.getBeanDeployment().initBeanByTypeMap(); - BeanProcessor beanProcessor = beanRegistrationPhase.getBeanProcessor(); ObserverRegistrar.RegistrationContext registrationContext = beanProcessor.registerSyntheticObservers(); return new ObserverRegistrationPhaseBuildItem(registrationContext, beanProcessor); diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnremovableSyntheticInjectionPointTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnremovableSyntheticInjectionPointTest.java new file mode 100644 index 0000000000000..c448e758da48f --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnremovableSyntheticInjectionPointTest.java @@ -0,0 +1,108 @@ +package io.quarkus.arc.test.unused; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.function.Consumer; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Vetoed; + +import org.jboss.jandex.ClassType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.BeanCreator; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.test.QuarkusUnitTest; + +public class UnremovableSyntheticInjectionPointTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(UnremovableSyntheticInjectionPointTest.class, Alpha.class, Gama.class, GamaCreator.class)) + .addBuildChainCustomizer(buildCustomizer()); + + static Consumer buildCustomizer() { + return new Consumer() { + + @Override + public void accept(BuildChainBuilder builder) { + builder.addBuildStep(new BuildStep() { + + @Override + public void execute(BuildContext context) { + context.produce(SyntheticBeanBuildItem.configure(Gama.class) + .scope(BuiltinScope.SINGLETON.getInfo()) + .unremovable() + .addInjectionPoint(ClassType.create(Alpha.class)) + .creator(GamaCreator.class) + .done()); + } + }).produces(SyntheticBeanBuildItem.class).build(); + } + }; + } + + @Test + public void testBeans() { + ArcContainer container = Arc.container(); + InstanceHandle alpha = container.instance(Alpha.class); + assertTrue(alpha.isAvailable()); + assertTrue(alpha.get().ping()); + InstanceHandle gama = container.instance(Gama.class); + assertTrue(gama.isAvailable()); + assertTrue(gama.get().ping()); + } + + // unused bean injected into a synthetic injection point + @ApplicationScoped + public static class Alpha { + + volatile boolean flag; + + @PostConstruct + void init() { + flag = true; + } + + public boolean ping() { + return flag; + } + + } + + @Vetoed + public static class Gama { + + private final Alpha alpha; + + private Gama(Alpha alpha) { + this.alpha = alpha; + } + + public boolean ping() { + return alpha.ping(); + } + + } + + public static class GamaCreator implements BeanCreator { + + @Override + public Gama create(SyntheticCreationalContext context) { + return new Gama(context.getInjectedReference(Alpha.class)); + } + + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 682aa3975cfb3..b6667a991631d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -1503,10 +1503,17 @@ private RegistrationContext registerSyntheticBeans(List beanRegis buildCompatibleExtensions.runSynthesis(beanArchiveComputingIndex); buildCompatibleExtensions.registerSyntheticBeans(context, applicationClassPredicate); } - this.injectionPoints.addAll(context.syntheticInjectionPoints); return context; } + void registerSyntheticInjectionPoints(RegistrationContext context) { + if (context instanceof BeanRegistrationContextImpl beanRegistrationContext) { + this.injectionPoints.addAll(beanRegistrationContext.syntheticInjectionPoints); + } else { + throw new IllegalArgumentException("Invalid registration context found:" + context.getClass()); + } + } + io.quarkus.arc.processor.ObserverRegistrar.RegistrationContext registerSyntheticObservers( List observerRegistrars) { ObserverRegistrationContextImpl context = new ObserverRegistrationContextImpl(buildContext, this); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 6604c38013350..2c0f6dd7fc064 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -33,6 +33,7 @@ import io.quarkus.arc.InjectableBean; import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; +import io.quarkus.arc.processor.BeanRegistrar.RegistrationContext; import io.quarkus.arc.processor.BuildExtension.BuildContext; import io.quarkus.arc.processor.BuildExtension.Key; import io.quarkus.arc.processor.CustomAlterableContexts.CustomAlterableContextInfo; @@ -51,6 +52,7 @@ *
  • {@link #registerCustomContexts()}
  • *
  • {@link #registerScopes()}
  • *
  • {@link #registerBeans()}
  • + *
  • {@link #registerSyntheticInjectionPoints(io.quarkus.arc.processor.BeanRegistrar.RegistrationContext)}
  • *
  • {@link BeanDeployment#initBeanByTypeMap()}
  • *
  • {@link #registerSyntheticObservers()}
  • *
  • {@link #initialize(Consumer, List)}
  • @@ -153,6 +155,15 @@ public BeanRegistrar.RegistrationContext registerBeans() { return beanDeployment.registerBeans(beanRegistrars); } + /** + * Register synthetic injection points from all synthetic beans. + * + * @param context + */ + public void registerSyntheticInjectionPoints(BeanRegistrar.RegistrationContext context) { + beanDeployment.registerSyntheticInjectionPoints(context); + } + public ObserverRegistrar.RegistrationContext registerSyntheticObservers() { return beanDeployment.registerSyntheticObservers(observerRegistrars); } @@ -560,7 +571,8 @@ public void accept(BytecodeTransformer transformer) { }; registerCustomContexts(); registerScopes(); - registerBeans(); + RegistrationContext registrationContext = registerBeans(); + registerSyntheticInjectionPoints(registrationContext); beanDeployment.initBeanByTypeMap(); registerSyntheticObservers(); initialize(unsupportedBytecodeTransformer, Collections.emptyList()); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java index 7532b145b3394..fe546a91293ae 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java @@ -36,7 +36,7 @@ public class RemoveUnusedBeansTest extends RemoveUnusedComponentsTest { .beanClasses(HasObserver.class, Foo.class, FooAlternative.class, HasName.class, UnusedProducers.class, InjectedViaInstance.class, InjectedViaInstanceWithWildcard.class, InjectedViaProvider.class, Excluded.class, UsedProducers.class, UnusedProducerButInjected.class, UsedViaInstanceWithUnusedProducer.class, - UsesBeanViaInstance.class, UsedViaAllList.class) + UsesBeanViaInstance.class, UsedViaAllList.class, UnusedBean.class, OnlyInjectedInUnusedBean.class) .removeUnusedBeans(true) .addRemovalExclusion(b -> b.getBeanClass().toString().equals(Excluded.class.getName())) .build(); From 16f1e1e5939676037d8baeb53736ea146d1ebd49 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 16 Oct 2024 11:16:51 +0300 Subject: [PATCH 06/29] Update Google Cloud SQL guide Closes https://github.com/quarkusio/quarkus/issues/43876 (cherry picked from commit 489f9198747fe094f8e5954e0205a8ffcbef17ef) --- docs/src/main/asciidoc/deploying-to-google-cloud.adoc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc index b700c6c3d710d..f51d3f76131ef 100644 --- a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc +++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc @@ -291,13 +291,20 @@ WARNING: This only works when your application is running inside a Google Cloud === Using Cloud SQL with native executables -When generating native executables, you must also mark `jnr.ffi.provider.jffi.NativeFinalizer$SingletonHolder` as runtime initialized. +When generating native executables, you must mark `jnr.ffi.provider.jffi.NativeFinalizer$SingletonHolder` as runtime initialized. [source,properties] ---- quarkus.native.additional-build-args=--initialize-at-run-time=jnr.ffi.provider.jffi.NativeFinalizer$SingletonHolder ---- +Additionally, starting with `com.google.cloud.sql:postgres-socket-factory:1.17.0`, you must also mark `com.kenai.jffi.internal.Cleaner` as runtime initialized. + +[source,properties] +---- +quarkus.native.additional-build-args=--initialize-at-run-time=jnr.ffi.provider.jffi.NativeFinalizer$SingletonHolder\\,com.kenai.jffi.internal.Cleaner +---- + == Going further You can find a set of extensions to access various Google Cloud Services in the Quarkiverse (a GitHub organization for Quarkus extensions maintained by the community), From b9976d55b92c6d632563c61eb9400102186f0111 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Tue, 10 Sep 2024 20:06:50 +0300 Subject: [PATCH 07/29] fix: prevent syncing before running the sync command (cherry picked from commit 9f917a7dda46b1150727df057b9b5d80d93c50ed) --- .../main/java/io/quarkus/cli/QuarkusCli.java | 5 ++- .../io/quarkus/cli/plugin/PluginManager.java | 41 +++++++++++-------- .../quarkus/cli/plugin/PluginMangerState.java | 10 +++++ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java index ef9ad75138415..52ac95068681f 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -104,6 +104,7 @@ public int run(String... args) throws Exception { boolean noCommand = args.length == 0 || args[0].startsWith("-"); boolean helpCommand = Arrays.stream(args).anyMatch(arg -> arg.equals("--help")); boolean pluginCommand = args.length >= 1 && (args[0].equals("plug") || args[0].equals("plugin")); + boolean pluginSyncCommand = pluginCommand && args.length >= 2 && args[1].equals("sync"); try { Optional missingCommand = checkMissingCommand(cmd, args); @@ -117,7 +118,9 @@ public int run(String... args) throws Exception { } PluginCommandFactory pluginCommandFactory = new PluginCommandFactory(output); PluginManager pluginManager = pluginManager(output, testDir, interactiveMode); - pluginManager.syncIfNeeded(); + if (!pluginSyncCommand) { // Let`s not sync before the actual command + pluginManager.syncIfNeeded(); + } Map plugins = new HashMap<>(pluginManager.getInstalledPlugins()); pluginCommandFactory.populateCommands(cmd, plugins); missingCommand.filter(m -> !plugins.containsKey(m)).ifPresent(m -> { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java index aabcfa800b8ae..56bc8d219bf65 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java @@ -279,24 +279,31 @@ private boolean reconcile(PluginCatalog catalog) { * @return true if changes any catalog was modified. */ public boolean sync() { - boolean catalogModified = reconcile(); - Map installedPlugins = getInstalledPlugins(); - Map extensionPlugins = state.getExtensionPlugins(); - Map pluginsToInstall = extensionPlugins.entrySet().stream() - .filter(e -> !installedPlugins.containsKey(e.getKey())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - catalogModified = catalogModified || !pluginsToInstall.isEmpty(); - pluginsToInstall.forEach((name, plugin) -> { - addPlugin(plugin); - }); - state.invalidate(); - if (!catalogModified) { - PluginCatalogService pluginCatalogService = state.getPluginCatalogService(); - PluginCatalog catalog = state.pluginCatalog(false); - pluginCatalogService.writeCatalog(catalog); - // here we are just touching the catalog, no need to invalidate + if (state.isSynced()) { + return false; + } + try { + boolean catalogModified = reconcile(); + Map installedPlugins = getInstalledPlugins(); + Map extensionPlugins = state.getExtensionPlugins(); + Map pluginsToInstall = extensionPlugins.entrySet().stream() + .filter(e -> !installedPlugins.containsKey(e.getKey())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + catalogModified = catalogModified || !pluginsToInstall.isEmpty(); + pluginsToInstall.forEach((name, plugin) -> { + addPlugin(plugin); + }); + state.invalidate(); + if (!catalogModified) { + PluginCatalogService pluginCatalogService = state.getPluginCatalogService(); + PluginCatalog catalog = state.pluginCatalog(false); + pluginCatalogService.writeCatalog(catalog); + // here we are just touching the catalog, no need to invalidate + } + return catalogModified; + } finally { + state.synced(); } - return catalogModified; } /** diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java index 71abd3b42c2fa..baa8496f7ff72 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java @@ -61,6 +61,8 @@ class PluginMangerState { private PluginCatalog _combinedCatalog; + private boolean synced; + public PluginCatalogService getPluginCatalogService() { return pluginCatalogService; } @@ -249,6 +251,14 @@ public Optional getProjectRoot() { return this.projectRoot; } + public boolean isSynced() { + return synced; + } + + public void synced() { + this.synced = true; + } + public void invalidateCatalogs() { _projectCatalog = null; _userCatalog = null; From 18336f44c7c06599636ced161f0718a397f03fbe Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Tue, 10 Sep 2024 20:08:28 +0300 Subject: [PATCH 08/29] feat: Extension plguin type and fix sync ext plugs (cherry picked from commit 0ae451cf93016917526c2b383e400f335e7aee71) --- .../java/io/quarkus/cli/plugin/PluginCommandFactory.java | 6 ++++++ .../src/main/java/io/quarkus/cli/plugin/Plugin.java | 4 ++++ .../src/main/java/io/quarkus/cli/plugin/PluginManager.java | 2 +- .../main/java/io/quarkus/cli/plugin/PluginMangerState.java | 7 +++++-- .../src/main/java/io/quarkus/cli/plugin/PluginType.java | 3 ++- .../src/main/java/io/quarkus/cli/plugin/PluginUtil.java | 2 +- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java index 2e2f67ca0d5b0..5b5cc2d6a1dd5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/plugin/PluginCommandFactory.java @@ -40,6 +40,12 @@ private Optional createPluginCommand(Plugin plugin) { return plugin.getLocation().map(l -> new JBangCommand(l, output)); case executable: return plugin.getLocation().map(l -> new ShellCommand(plugin.getName(), Paths.get(l), output)); + case extension: + if (PluginUtil.checkGACTV(plugin.getLocation()).isPresent()) { + return plugin.getLocation().flatMap(PluginUtil::checkGACTV).map(g -> new JBangCommand(toGAVC(g), output)); + } else if (plugin.getLocation().filter(l -> l.endsWith(".jar")).isPresent()) { + return plugin.getLocation().map(l -> new JBangCommand(l, output)); + } default: throw new IllegalStateException("Unknown plugin type!"); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/Plugin.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/Plugin.java index 00cce05c386ed..b9224f6484b08 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/Plugin.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/Plugin.java @@ -92,6 +92,10 @@ public Plugin withCatalogLocation(Optional catalogLocation) { return new Plugin(name, type, location, description, catalogLocation, inUserCatalog); } + public Plugin withType(PluginType type) { + return new Plugin(name, type, location, description, catalogLocation, inUserCatalog); + } + public Plugin inUserCatalog() { return new Plugin(name, type, location, description, catalogLocation, true); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java index 56bc8d219bf65..06f9bcc5eeb22 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginManager.java @@ -236,7 +236,7 @@ public boolean reconcile() { */ private boolean reconcile(PluginCatalog catalog) { Path location = catalog.getCatalogLocation() - .orElseThrow(() -> new IllegalArgumentException("Unknwon plugin catalog location.")); + .orElseThrow(() -> new IllegalArgumentException("Unknown plugin catalog location.")); List installedTypes = catalog.getPlugins().entrySet().stream().map(Map.Entry::getValue).map(Plugin::getType) .collect(Collectors.toList()); //Let's only fetch installable plugins of the corresponding types. diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java index baa8496f7ff72..ee1744e7a9b3e 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginMangerState.java @@ -121,6 +121,9 @@ public Map installablePlugins(List types) { case executable: installablePlugins.putAll(executablePlugins()); break; + case extension: + installablePlugins.putAll(extensionPlugins()); + break; } } installablePlugins.putAll(executablePlugins().entrySet().stream().filter(e -> types.contains(e.getValue().getType())) @@ -188,8 +191,8 @@ public Map extensionPlugins() { for (ArtifactKey key : allKeys) { Extension extension = allExtensions.get(key); for (String cliPlugin : ExtensionProcessor.getCliPlugins(extension)) { - Plugin plugin = cliPlugin.contains(ALIAS_SEPARATOR) ? util.fromAlias(cliPlugin) - : util.fromLocation(cliPlugin); + Plugin plugin = (cliPlugin.contains(ALIAS_SEPARATOR) ? util.fromAlias(cliPlugin) + : util.fromLocation(cliPlugin)).withType(PluginType.extension); extensionPlugins.put(plugin.getName(), plugin); } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginType.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginType.java index 1b8b54d7c4172..3a751f419a312 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginType.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginType.java @@ -5,5 +5,6 @@ public enum PluginType { java, maven, executable, - jbang; + jbang, + extension } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginUtil.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginUtil.java index 40f5c3bab818a..c3dbdbf410cb5 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginUtil.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/cli/plugin/PluginUtil.java @@ -84,7 +84,7 @@ public static boolean shouldRemove(Plugin p) { if (checkUrl(p.getLocation()).isPresent()) { //We don't want to remove remotely located plugins return false; } - if (checkGACTV(p.getLocation()).isPresent()) { //We don't want to remove remotely located plugins + if (checkGACTV(p.getLocation()).isPresent() && p.getType() != PluginType.extension) { //We don't want to remove remotely located plugins return false; } if (p.getLocation().map(PluginUtil::isLocalFile).orElse(false)) { From 10230bd563d08dfe40dd35b63157d64e6a7faa95 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 16 Oct 2024 10:05:23 +0300 Subject: [PATCH 09/29] Don't pass '--enable-monitoring=heapdump' unconditionally on windows The feature is not supported on Windows Closes https://github.com/quarkusio/quarkus/issues/43895 (cherry picked from commit ac3e7b3e00d2c3202aff224c59c982d736715dbf) --- .../deployment/pkg/steps/NativeImageBuildStep.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 5a6f9643ceb58..2c4be668f23d7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -932,13 +932,18 @@ public NativeImageInvokerInfo build() { } List monitoringOptions = new ArrayList<>(); - monitoringOptions.add(NativeConfig.MonitoringOption.HEAPDUMP); + if (!SystemUtils.IS_OS_WINDOWS || containerBuild) { + // --enable-monitoring=heapdump is not supported on Windows + monitoringOptions.add(NativeConfig.MonitoringOption.HEAPDUMP); + } if (nativeConfig.monitoring().isPresent()) { monitoringOptions.addAll(nativeConfig.monitoring().get()); } - nativeImageArgs.add("--enable-monitoring=" + monitoringOptions.stream() - .distinct() - .map(o -> o.name().toLowerCase(Locale.ROOT)).collect(Collectors.joining(","))); + if (!monitoringOptions.isEmpty()) { + nativeImageArgs.add("--enable-monitoring=" + monitoringOptions.stream() + .distinct() + .map(o -> o.name().toLowerCase(Locale.ROOT)).collect(Collectors.joining(","))); + } if (nativeConfig.autoServiceLoaderRegistration()) { addExperimentalVMOption(nativeImageArgs, "-H:+UseServiceLoaderFeature"); From 1d84f088c860801d1335e57bdff84dfe9e9f702f Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 16 Oct 2024 11:24:37 +0300 Subject: [PATCH 10/29] Refactor: Use io.smallrye.common.os.OS Instead of org.apache.commons.lang3.SystemUtils; (cherry picked from commit 26c2c4833aa4860220824cbd5710222e66889fc2) --- .../deployment/pkg/steps/NativeImageBuildStep.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 2c4be668f23d7..287b61399b985 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -18,7 +18,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.SystemUtils; import org.jboss.logging.Logger; import io.quarkus.bootstrap.util.IoUtils; @@ -53,6 +52,7 @@ import io.quarkus.runtime.graal.DisableLoggingFeature; import io.quarkus.sbom.ApplicationComponent; import io.quarkus.sbom.ApplicationManifestConfig; +import io.smallrye.common.os.OS; public class NativeImageBuildStep { @@ -210,7 +210,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, LocalesBuildTimeCon String pie = ""; boolean isContainerBuild = nativeImageRunner.isContainerBuild(); - if (!isContainerBuild && SystemUtils.IS_OS_LINUX) { + if (!isContainerBuild && OS.LINUX.isCurrent()) { if (nativeConfig.pie().isPresent() && nativeConfig.pie().get()) { pie = detectPIE(); } else { @@ -332,7 +332,7 @@ private String getNativeImageName(OutputTargetBuildItem outputTargetBuildItem, P private String getResultingExecutableName(String nativeImageName, boolean isContainerBuild) { String resultingExecutableName = nativeImageName; - if (SystemUtils.IS_OS_WINDOWS && !isContainerBuild) { + if (OS.WINDOWS.isCurrent() && !isContainerBuild) { //once image is generated it gets added .exe on Windows resultingExecutableName = resultingExecutableName + ".exe"; } @@ -354,7 +354,7 @@ public NativeImageRunnerBuildItem resolveNativeImageBuildRunner(NativeConfig nat String executableName = getNativeImageExecutableName(); String errorMessage = "Cannot find the `" + executableName + "` in the GRAALVM_HOME, JAVA_HOME and System PATH."; - if (!SystemUtils.IS_OS_LINUX) { + if (!OS.LINUX.isCurrent()) { // Delay the error: if we're just building native sources, we may not need the build runner at all. return new NativeImageRunnerBuildItem(new NativeImageBuildRunnerError(errorMessage)); } @@ -474,7 +474,7 @@ private static void copySourcesToSourceCache(OutputTargetBuildItem outputTargetB private RuntimeException imageGenerationFailed(int exitValue, boolean isContainerBuild) { if (exitValue == OOM_ERROR_VALUE) { - if (isContainerBuild && !SystemUtils.IS_OS_LINUX) { + if (isContainerBuild && !OS.LINUX.isCurrent()) { return new ImageGenerationFailureException("Image generation failed. Exit code was " + exitValue + " which indicates an out of memory error. The most likely cause is Docker not being given enough memory. Also consider increasing the Xmx value for native image generation by setting the \"" + QUARKUS_XMX_PROPERTY + "\" property"); @@ -554,7 +554,7 @@ private static NativeImageBuildLocalRunner getNativeImageBuildLocalRunner(Native } private static String getNativeImageExecutableName() { - return SystemUtils.IS_OS_WINDOWS ? "native-image.cmd" : "native-image"; + return OS.WINDOWS.isCurrent() ? "native-image.cmd" : "native-image"; } private static String detectNoPIE() { @@ -932,7 +932,7 @@ public NativeImageInvokerInfo build() { } List monitoringOptions = new ArrayList<>(); - if (!SystemUtils.IS_OS_WINDOWS || containerBuild) { + if (!OS.WINDOWS.isCurrent() || containerBuild) { // --enable-monitoring=heapdump is not supported on Windows monitoringOptions.add(NativeConfig.MonitoringOption.HEAPDUMP); } From a9a31117b426e1e79abe7993dfeac146015cd6d4 Mon Sep 17 00:00:00 2001 From: brunobat Date: Wed, 16 Oct 2024 19:00:47 +0100 Subject: [PATCH 11/29] Fix Class Cast Exception (cherry picked from commit cd4d5791c87990f7ee28749c8034343a70b4da5d) --- .../metrics/ClassCastExceptionTest.java | 44 +++++++++++++++++++ .../OpenTelemetryVertxMetricsFactory.java | 27 ++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/ClassCastExceptionTest.java diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/ClassCastExceptionTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/ClassCastExceptionTest.java new file mode 100644 index 0000000000000..b23fa2f0daedc --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/ClassCastExceptionTest.java @@ -0,0 +1,44 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.exporter.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class ClassCastExceptionTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .add(new StringAsset( + "quarkus.otel.metrics.enabled=true\n" + + "quarkus.otel.metrics.exporter=in-memory\n" + + "quarkus.otel.metric.export.interval=300ms\n" + + "quarkus.http.limits.max-connections=50\n" + + "quarkus.otel.traces.exporter=none\n" + + "quarkus.otel.logs.exporter=none\n"), + "application.properties")); + + /** + * ClassCastException when using OpenTelemetry and max-connections property + * See https://github.com/quarkusio/quarkus/issues/43852 + */ + @Test + public void testReproducer() { + // This test is successful if it does not throw an exception + Assertions.assertTrue(true); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java index 58bef36792bb7..32e7d006de6ec 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java @@ -2,6 +2,7 @@ import java.util.Optional; +import io.quarkus.vertx.http.runtime.ExtendedQuarkusVertxHttpMetrics; import io.vertx.core.Context; import io.vertx.core.VertxOptions; import io.vertx.core.http.HttpServerOptions; @@ -16,7 +17,7 @@ * This is used to retrieve the route name from Vert.x. This is useful for OpenTelemetry to generate the Span name and * http.route attribute. Right now, there is no other way to retrieve the route name from Vert.x using the * Telemetry SPI, so we need to rely on the Metrics SPI. - * + *

    * Right now, it is not possible to register multiple VertxMetrics, meaning that only a single one is * available per Quarkus instance. To avoid clashing with other extensions that provide Metrics data (like the * Micrometer extension), we only register the {@link OpenTelemetryVertxMetricsFactory} if the @@ -25,17 +26,19 @@ public class OpenTelemetryVertxMetricsFactory implements VertxMetricsFactory { @Override public VertxMetrics metrics(final VertxOptions options) { - return new VertxMetrics() { - @Override - public HttpServerMetrics createHttpServerMetrics(final HttpServerOptions options, - final SocketAddress localAddress) { - return new OpenTelemetryHttpServerMetrics(); - } - }; + return new OpenTelemetryHttpServerMetrics(); } public static class OpenTelemetryHttpServerMetrics - implements HttpServerMetrics { + implements HttpServerMetrics, + VertxMetrics, ExtendedQuarkusVertxHttpMetrics { + + @Override + public HttpServerMetrics createHttpServerMetrics(final HttpServerOptions options, + final SocketAddress localAddress) { + return this; + } + @Override public MetricRequest requestBegin(final Object socketMetric, final HttpRequest request) { return MetricRequest.request(request); @@ -48,6 +51,12 @@ public void requestRouted(final MetricRequest requestMetric, final String route) } } + @Override + public ConnectionTracker getHttpConnectionTracker() { + // To be implemented if we decide to instrument with OpenTelemetry. See VertxMeterBinderAdapter for an example. + return ExtendedQuarkusVertxHttpMetrics.NOOP_CONNECTION_TRACKER; + } + static final class MetricRequest { private final HttpRequest request; From 36b8eef95ad1724c2a03c567ecd3ab7afe2dfd33 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 16 Oct 2024 19:26:32 +0200 Subject: [PATCH 12/29] CI - Do not populate the cache for PRs as we won't store it (cherry picked from commit 583ef95194b562c149f6560763a430aca7b80848) --- .github/workflows/ci-actions-incremental.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 0b5889fbcb0c3..a141bc27e384b 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -174,6 +174,8 @@ jobs: restore-keys: | develocity-cache-Initial JDK 17 Build-${{ github.event.pull_request.number }}- - name: Populate the cache + # only populate the cache if it's not a pull request as we only store the cache in this case + if: github.event_name != 'pull_request' run: | ./mvnw -T2C $COMMON_MAVEN_ARGS dependency:go-offline - name: Verify native-tests.json From 0cd0c56b8974ad6c9129eb4bc12566146bd3f1a6 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 16 Oct 2024 19:03:13 +0200 Subject: [PATCH 13/29] Revert "Bump org.junit:junit-bom from 5.11.0 to 5.11.1 in /devtools/gradle" This reverts commit 9e793f1c79c5a9c7c526c7866a6b2082ecbf3d05. (cherry picked from commit 68afa8afd64bb26b33420b7b655495a64720a692) --- devtools/gradle/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index 87549af216850..ba9aa08c68fd2 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -5,7 +5,7 @@ plugin-publish = "1.3.0" kotlin = "2.0.21" smallrye-config = "3.9.1" -junit5 = "5.11.1" +junit5 = "5.11.0" assertj = "3.26.3" [plugins] From 549f3beddcd2534968e664a09a665b98bce23b43 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 16 Oct 2024 19:03:39 +0200 Subject: [PATCH 14/29] Revert "Update system-stubs-jupiter to 2.1.6" This reverts commit 55a04b3bb1934d20a2d8d8f2124d2e0ff9a60a01. (cherry picked from commit 42daf59bdd31593b0cf637e6870e6f721198e39d) --- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index c962034a3f7f9..7d981c9a3a8e2 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -361,7 +361,7 @@ uk.org.webcompere system-stubs-jupiter - 2.1.6 + 2.0.1 test diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 7765ca39007a9..4e8be1f03331c 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -58,7 +58,7 @@ ${project.version} 37 3.2.2 - 2.1.6 + 2.0.2 4.2.2 0.0.7 From bc140ede51910bcc9bc90b28d9d889330aef2a20 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 16 Oct 2024 19:04:37 +0200 Subject: [PATCH 15/29] Revert "Bump org.junit.jupiter:junit-jupiter from 5.10.3 to 5.11.0" This reverts commit 2917b5c818c4a3931d4993406caf9b466970ca09. (cherry picked from commit ddd49bceffd1cc2241a20b68b85af40d86e96725) --- devtools/gradle/gradle/libs.versions.toml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/junit5-virtual-threads/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index ba9aa08c68fd2..fce62ae94baaf 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -5,7 +5,7 @@ plugin-publish = "1.3.0" kotlin = "2.0.21" smallrye-config = "3.9.1" -junit5 = "5.11.0" +junit5 = "5.10.3" assertj = "3.26.3" [plugins] diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index a78dd29bda37a..dc0b0834bd751 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -41,7 +41,7 @@ 3.26.3 0.9.5 3.6.1.Final - 5.11.0 + 5.10.3 3.9.9 0.9.0.M3 3.13.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index 7d981c9a3a8e2..b15ccd55e4a39 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -40,7 +40,7 @@ 3.9.9 2.18.0 1.5.2 - 5.11.0 + 5.10.3 diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index ad813042f56c6..452201b9ae087 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -43,7 +43,7 @@ 3.5.0 3.2.2 - 5.11.0 + 5.10.3 1.10.3 3.26.3 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 4e8be1f03331c..8d6dfebd9486b 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -51,7 +51,7 @@ 3.26.3 2.18.0 4.1.0 - 5.11.0 + 5.10.3 1.27.1 3.6.1.Final 5.14.1 From fb448db034d9ce97604253f330771e3a9262cf76 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 17 Oct 2024 10:18:36 +0200 Subject: [PATCH 16/29] Update to JUnit 5.10.5 (cherry picked from commit 8cd6f2c0784fb708bfdfb89a750e053c89d17cb9) --- bom/application/pom.xml | 2 +- devtools/gradle/gradle/libs.versions.toml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/extension-maven-plugin/pom.xml | 2 +- independent-projects/junit5-virtual-threads/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 45902bbe55396..29b004cc105ad 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -133,7 +133,7 @@ 11.5.8.0 1.2.6 2.2 - 5.10.3 + 5.10.5 15.0.10.Final 5.0.12.Final 3.1.5 diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index fce62ae94baaf..1203e4d3f29e1 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -5,7 +5,7 @@ plugin-publish = "1.3.0" kotlin = "2.0.21" smallrye-config = "3.9.1" -junit5 = "5.10.3" +junit5 = "5.10.5" assertj = "3.26.3" [plugins] diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 20411698b333c..d1ded96f565c9 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -51,7 +51,7 @@ 1.6.Final 3.26.3 - 5.10.3 + 5.10.5 2.0.21 1.9.0 5.14.2 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index dc0b0834bd751..861ff4a7b2b29 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -41,7 +41,7 @@ 3.26.3 0.9.5 3.6.1.Final - 5.10.3 + 5.10.5 3.9.9 0.9.0.M3 3.13.1 diff --git a/independent-projects/extension-maven-plugin/pom.xml b/independent-projects/extension-maven-plugin/pom.xml index b15ccd55e4a39..a4593cf828fc8 100644 --- a/independent-projects/extension-maven-plugin/pom.xml +++ b/independent-projects/extension-maven-plugin/pom.xml @@ -40,7 +40,7 @@ 3.9.9 2.18.0 1.5.2 - 5.10.3 + 5.10.5 diff --git a/independent-projects/junit5-virtual-threads/pom.xml b/independent-projects/junit5-virtual-threads/pom.xml index 452201b9ae087..51865ed523cfb 100644 --- a/independent-projects/junit5-virtual-threads/pom.xml +++ b/independent-projects/junit5-virtual-threads/pom.xml @@ -43,7 +43,7 @@ 3.5.0 3.2.2 - 5.10.3 + 5.10.5 1.10.3 3.26.3 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 7beef20f62da6..6f45c8420299b 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -38,7 +38,7 @@ UTF-8 - 5.10.3 + 5.10.5 3.26.3 3.2.2 1.8.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 0bfbfd9635b3f..115d2c2ae83d5 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -47,7 +47,7 @@ 4.1.0 3.2.2 1.14.11 - 5.10.3 + 5.10.5 3.9.9 3.26.3 3.6.1.Final diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 8d6dfebd9486b..9f31a25464dbb 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -51,7 +51,7 @@ 3.26.3 2.18.0 4.1.0 - 5.10.3 + 5.10.5 1.27.1 3.6.1.Final 5.14.1 From 90ca4f896601a89c4c430838afbceb662fefff64 Mon Sep 17 00:00:00 2001 From: RobinDM Date: Thu, 17 Oct 2024 21:01:35 +0200 Subject: [PATCH 17/29] Update appcds.adoc Mention the appcds.use-container flag (cherry picked from commit bb65595bb3662277088085a987ac2fe50495afc7) --- docs/src/main/asciidoc/appcds.adoc | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/appcds.adoc b/docs/src/main/asciidoc/appcds.adoc index db08254f2cc41..c3cf8afc63708 100644 --- a/docs/src/main/asciidoc/appcds.adoc +++ b/docs/src/main/asciidoc/appcds.adoc @@ -14,9 +14,11 @@ This reference guide explains how to enable Application Class Data Sharing in yo == What is Application Class Data Sharing (AppCDS)? link:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#application-class-data-sharing[Application Class Data Sharing] is a JVM feature that helps reduce the startup time and memory footprint of a JVM application. -This is achieved by having the JVM create a pre-processed shared archived of classes that are loaded at startup time. As these classes +This is achieved by having the JVM create a pre-processed shared archived of classes that are loaded at startup time. +As these classes are loaded every time the application starts, AppCDS is a conceptually simple way of improving the application startup time, -without the application itself having to be coded or configured in a specific way. How much of an improvement depends on many factors, such as the number of classes loaded, the underlying hardware etc. +without the application itself having to be coded or configured in a specific way. +How much of an improvement depends on many factors, such as the number of classes loaded, the underlying hardware etc. == Vanilla AppCDS generation @@ -24,7 +26,7 @@ The main downside of creating AppCDS manually for an application is that their g NOTE: The exact process depends on the JVM version being used as newer JVM have incrementally made the process easier. -This fact makes the use of AppCDS difficult to use for real world deployments where a CI pipeline is responsible for building and deploying applications. +This fact makes AppCDS difficult to use for real world deployments where a CI pipeline is responsible for building and deploying applications. == AppCDS in Quarkus @@ -95,7 +97,11 @@ AppCDS has improved significantly in the latest JDK releases. This means that to === Usage in containers -When building container images using the `quarkus-container-image-jib` extension, Quarkus automatically takes care of all the steps needed to generate the archive -and make it usable at runtime in the container. +When building container images using the `quarkus-container-image-jib` extension, Quarkus automatically takes care of all the steps needed to generate the archive and make it usable at runtime in the container. -This way, by simply setting `quarkus.package.jar.appcds.enabled` to `true` the generated container can benefit from a slight reduction in startup time and memory usage. +This way, by simply setting `quarkus.package.jar.appcds.enabled` to `true`, containers using the generated image can benefit from a slight reduction in startup time and memory usage. + +You may see that Quarkus starts a container to generate the AppCDS archive. +It does this to ensure that the Java version of the build align with that of the generated container image. +It is possible to opt out of this by setting `quarkus.package.jar.appcds.use-container` to `false`. +In that case, it is your responsibility to ensure that the Java version that will run the Quarkus application matches that of the machine building it. From 2381b5e24b78b856c4bb48df6259c1c4134c60e8 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 25 Sep 2024 08:39:54 +1000 Subject: [PATCH 18/29] Add GraphQL Footer Log in Dev UI Signed-off-by: Phillip Kruger (cherry picked from commit 078ee6134946a54f0729bbdc7ef7ace1ed5b9c08) --- .../deployment/GraphQLDevUILogBuildItem.java | 21 +++++++++++++++++ .../deployment/SmallRyeGraphQLProcessor.java | 23 +++++++++++++++++-- .../runtime/SmallRyeGraphQLRecorder.java | 12 +++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/GraphQLDevUILogBuildItem.java diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/GraphQLDevUILogBuildItem.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/GraphQLDevUILogBuildItem.java new file mode 100644 index 0000000000000..750ba80cb0a64 --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/GraphQLDevUILogBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.smallrye.graphql.deployment; + +import java.util.concurrent.SubmissionPublisher; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * Used to create the publisher for the graphql trafic log in dev ui + */ +public final class GraphQLDevUILogBuildItem extends SimpleBuildItem { + private final RuntimeValue> publisher; + + public GraphQLDevUILogBuildItem(RuntimeValue> publisher) { + this.publisher = publisher; + } + + public RuntimeValue> getPublisher() { + return this.publisher; + } +} diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 1ff5438078a37..f0ea44583d256 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -16,6 +16,7 @@ import java.util.Optional; import java.util.Scanner; import java.util.Set; +import java.util.concurrent.SubmissionPublisher; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -36,6 +37,7 @@ import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Consume; @@ -56,6 +58,7 @@ import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.devui.spi.buildtime.FooterLogBuildItem; import io.quarkus.maven.dependency.GACT; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; @@ -332,6 +335,16 @@ void buildFinalIndex( smallRyeGraphQLFinalIndexProducer.produce(new SmallRyeGraphQLFinalIndexBuildItem(overridableIndex)); } + @BuildStep(onlyIf = IsDevelopment.class) + @Record(ExecutionTime.STATIC_INIT) + void createDevUILog(BuildProducer footerLogProducer, + SmallRyeGraphQLRecorder recorder, + BuildProducer graphQLDevUILogProducer) { + RuntimeValue> publisher = recorder.createTraficLogPublisher(); + footerLogProducer.produce(new FooterLogBuildItem("GraphQL", publisher)); + graphQLDevUILogProducer.produce(new GraphQLDevUILogBuildItem(publisher)); + } + @Record(ExecutionTime.STATIC_INIT) @BuildStep void buildExecutionService( @@ -342,14 +355,20 @@ void buildExecutionService( SmallRyeGraphQLFinalIndexBuildItem graphQLFinalIndexBuildItem, BeanContainerBuildItem beanContainer, BuildProducer systemPropertyProducer, - SmallRyeGraphQLConfig graphQLConfig) { + SmallRyeGraphQLConfig graphQLConfig, + Optional graphQLDevUILogBuildItem) { activateFederation(graphQLConfig, systemPropertyProducer, graphQLFinalIndexBuildItem); graphQLConfig.extraScalars.ifPresent(this::registerExtraScalarsInSchema); Schema schema = SchemaBuilder.build(graphQLFinalIndexBuildItem.getFinalIndex(), Converters.getImplicitConverter(TypeAutoNameStrategy.class).convert(graphQLConfig.autoNameStrategy)); - RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema, graphQLConfig); + Optional publisher = Optional.empty(); + if (graphQLDevUILogBuildItem.isPresent()) { + publisher = Optional.of(graphQLDevUILogBuildItem.get().getPublisher()); + } + RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema, graphQLConfig, + publisher); graphQLInitializedProducer.produce(new SmallRyeGraphQLInitializedBuildItem(initialized)); // Make sure the complex object from the application can work in native mode diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java index 8cea023f71160..1378ba001b72c 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java @@ -1,6 +1,8 @@ package io.quarkus.smallrye.graphql.runtime; import java.util.List; +import java.util.Optional; +import java.util.concurrent.SubmissionPublisher; import java.util.function.Consumer; import graphql.schema.GraphQLSchema; @@ -26,13 +28,21 @@ @Recorder public class SmallRyeGraphQLRecorder { + public RuntimeValue> createTraficLogPublisher() { + return new RuntimeValue<>(new SubmissionPublisher<>()); + } + public RuntimeValue createExecutionService(BeanContainer beanContainer, Schema schema, - SmallRyeGraphQLConfig graphQLConfig) { + SmallRyeGraphQLConfig graphQLConfig, + Optional>> publisher) { GraphQLProducer graphQLProducer = beanContainer.beanInstance(GraphQLProducer.class); if (graphQLConfig.extraScalars.isPresent()) { registerExtraScalars(graphQLConfig.extraScalars.get()); } + if (publisher.isPresent()) { + graphQLProducer.setTraficPublisher(publisher.get().getValue()); + } GraphQLSchema graphQLSchema = graphQLProducer.initialize(schema); return new RuntimeValue<>(graphQLSchema != null); } From 77efb11c8d48589ab8d2ba1b01f232318b3c530d Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 18 Oct 2024 09:47:57 +0200 Subject: [PATCH 19/29] Qute message bundles: change the way message templates are loaded - introduce a dedicated TemplateLocator so that the message templates can be reloaded automatically when a no-restart change occurs during the dev mode - fixes #43944 (cherry picked from commit af4554678be70893c7bc9ca0a8496ac57008f2e0) --- .../deployment/MessageBundleProcessor.java | 13 +++- .../CustomTemplateLocatorTest.java | 2 +- .../io/quarkus/qute/i18n/MessageBundles.java | 24 ++++---- .../qute/i18n/MessageTemplateLocator.java | 59 +++++++++++++++++++ 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageTemplateLocator.java 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 2a9ff85c3a60c..8b3af1267819e 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 @@ -52,6 +52,7 @@ import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.GeneratedClassGizmoAdaptor; @@ -98,6 +99,7 @@ import io.quarkus.qute.i18n.Message; import io.quarkus.qute.i18n.MessageBundle; import io.quarkus.qute.i18n.MessageBundles; +import io.quarkus.qute.i18n.MessageTemplateLocator; import io.quarkus.qute.runtime.MessageBundleRecorder; import io.quarkus.qute.runtime.QuteConfig; import io.quarkus.runtime.LocalesBuildTimeConfig; @@ -115,7 +117,8 @@ public class MessageBundleProcessor { @BuildStep AdditionalBeanBuildItem beans() { - return new AdditionalBeanBuildItem(MessageBundles.class, MessageBundle.class, Message.class, Localized.class); + return new AdditionalBeanBuildItem(MessageBundles.class, MessageBundle.class, Message.class, Localized.class, + MessageTemplateLocator.class); } @BuildStep @@ -349,6 +352,10 @@ void initBundleContext(MessageBundleRecorder recorder, List bundles, BuildProducer syntheticBeans) throws ClassNotFoundException { + if (bundles.isEmpty()) { + return; + } + Map>> bundleInterfaces = new HashMap<>(); for (MessageBundleBuildItem bundle : bundles) { final Class bundleClass = Class.forName(bundle.getDefaultBundleInterface().toString(), true, @@ -372,7 +379,9 @@ void initBundleContext(MessageBundleRecorder recorder, MessageBundleMethodBuildItem::getTemplate)); syntheticBeans.produce(SyntheticBeanBuildItem.configure(MessageBundleRecorder.BundleContext.class) - .supplier(recorder.createContext(templateIdToContent, bundleInterfaces)).done()); + .scope(BuiltinScope.DEPENDENT.getInfo()) + .supplier(recorder.createContext(templateIdToContent, bundleInterfaces)) + .done()); } @BuildStep diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templatelocator/CustomTemplateLocatorTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templatelocator/CustomTemplateLocatorTest.java index 26ee729793825..0f7cefe0ea19f 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templatelocator/CustomTemplateLocatorTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/templatelocator/CustomTemplateLocatorTest.java @@ -89,7 +89,7 @@ public void testCheckedTemplate() { @Test public void testLocatorsAreRegisteredAsSingletons() { - assertEquals(4, locatorList.size()); + assertEquals(5, locatorList.size()); } @Singleton diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java index 6191405d631fc..0b4fc5fcd5ff8 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java @@ -10,8 +10,6 @@ import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.Instance; -import org.jboss.logging.Logger; - import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; @@ -31,8 +29,6 @@ public final class MessageBundles { public static final String ATTRIBUTE_LOCALE = TemplateInstance.LOCALE; public static final String DEFAULT_LOCALE = "<>"; - private static final Logger LOGGER = Logger.getLogger(MessageBundles.class); - private MessageBundles() { } @@ -62,11 +58,14 @@ public static T get(Class bundleInterface, Localized localized) { .render()); } - static void setupNamespaceResolvers(@Observes EngineBuilder builder, BundleContext context) { + static void setupNamespaceResolvers(@Observes EngineBuilder builder, Instance context) { + if (!context.isResolvable()) { + return; + } // Avoid injecting "Instance instance" which prevents unused beans removal ArcContainer container = Arc.container(); // For every bundle register a new resolver - for (Entry>> entry : context.getBundleInterfaces().entrySet()) { + for (Entry>> entry : context.get().getBundleInterfaces().entrySet()) { final String bundleName = entry.getKey(); final Map interfaces = new HashMap<>(); Resolver resolver = null; @@ -121,10 +120,15 @@ public String getNamespace() { } } - static void setupMessageTemplates(@Observes Engine engine, BundleContext context) { - for (Entry entry : context.getMessageTemplates().entrySet()) { - LOGGER.debugf("Register template for message [%s]", entry.getKey()); - engine.putTemplate(entry.getKey(), engine.parse(entry.getValue())); + static void preloadMessageTemplates(@Observes Engine engine, Instance context) { + if (!context.isResolvable()) { + return; + } + for (String key : context.get().getMessageTemplates().keySet()) { + Template messageTemplate = engine.getTemplate(key); + if (messageTemplate == null) { + throw new IllegalStateException("Unable to preload message template: " + key); + } } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageTemplateLocator.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageTemplateLocator.java new file mode 100644 index 0000000000000..dc2bec8f4de7b --- /dev/null +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageTemplateLocator.java @@ -0,0 +1,59 @@ +package io.quarkus.qute.i18n; + +import java.io.Reader; +import java.io.StringReader; +import java.util.Optional; + +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import io.quarkus.arc.WithCaching; +import io.quarkus.qute.TemplateLocator; +import io.quarkus.qute.Variant; +import io.quarkus.qute.runtime.MessageBundleRecorder.BundleContext; + +@Singleton +public class MessageTemplateLocator implements TemplateLocator { + + @WithCaching // BundleContext is dependent + @Inject + Instance bundleContext; + + @Override + public int getPriority() { + return DEFAULT_PRIORITY - 1; + } + + @Override + public Optional locate(String id) { + if (bundleContext.isResolvable()) { + String template = bundleContext.get().getMessageTemplates().get(id); + if (template != null) { + return Optional.of(new MessageTemplateLocation(template)); + } + } + return Optional.empty(); + } + + static final class MessageTemplateLocation implements TemplateLocation { + + private final String content; + + private MessageTemplateLocation(String content) { + this.content = content; + } + + @Override + public Reader read() { + return new StringReader(content); + } + + @Override + public Optional getVariant() { + return Optional.empty(); + } + + } + +} From 7f9521e98935c71dc32531ca289c37538c854673 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 18 Oct 2024 17:01:20 +0300 Subject: [PATCH 20/29] Remove outdated section about DNS in Mongo We use the custom DNS resolver by default in all modes (cherry picked from commit 0852c5142099802b72fbe6225aafa36d383f16cd) --- docs/src/main/asciidoc/mongodb.adoc | 34 ----------------------------- 1 file changed, 34 deletions(-) diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 811a332c59d76..59d936c5544c0 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -708,40 +708,6 @@ This means that the `org.acme.MyVariable` class is not known to GraalVM, the rem More details about the `@RegisterForReflection` annotation can be found on the xref:writing-native-applications-tips.adoc#registerForReflection[native application tips] page. ==== -== Using mongo+srv:// urls - -`mongo+srv://` urls are supported out of the box in JVM mode. -However, in native, the default DNS resolver, provided by the MongoDB client, uses JNDI and does not work in native mode. - -If you need to use `mongo+srv://` in native mode, you can configure an alternative DNS resolver. -This feature is **experimental** and may introduce a difference between JVM applications and native applications. - -To enable the alternative DNS resolver, use: - -[source, properties] ----- -quarkus.mongodb.native.dns.use-vertx-dns-resolver=true ----- - -As indicated in the property name, it uses Vert.x to retrieve the DNS records. -By default, it tries to read the first `nameserver` from `/etc/resolv.conf`, if this file exists. -You can also configure your DNS server: - -[source,properties] ----- -quarkus.mongodb.native.dns.use-vertx-dns-resolver=true -quarkus.mongodb.native.dns.server-host=10.0.0.1 -quarkus.mongodb.native.dns.server-port=53 # 53 is the default port ----- - -Also, you can configure the lookup timeout using: - -[source,properties] ----- -quarkus.mongodb.native.dns.use-vertx-dns-resolver=true -quarkus.mongodb.native.dns.lookup-timeout=10s # the default is 5s ----- - == Customize the Mongo client configuration programmatically If you need to customize the Mongo client configuration programmatically, you need to implement the `io.quarkus.mongodb.runtime.MongoClientCustomizer` interface and expose it as a CDI _application scoped_ bean: From d0816763d0e235ae6e5445105a95e59a6de6ca01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Fri, 18 Oct 2024 15:58:19 +0200 Subject: [PATCH 21/29] Use original query case for fast count queries (cherry picked from commit 9257b636b3c452c2d8e9b741c9b94e7f744303fe) --- .../panache/hibernate/common/runtime/PanacheJpaUtil.java | 7 ++++--- .../panache/hibernate/common/runtime/CountTest.java | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/panache/panache-hibernate-common/runtime/src/main/java/io/quarkus/panache/hibernate/common/runtime/PanacheJpaUtil.java b/extensions/panache/panache-hibernate-common/runtime/src/main/java/io/quarkus/panache/hibernate/common/runtime/PanacheJpaUtil.java index 6cfa4b97582c2..7b9719d4df1fe 100644 --- a/extensions/panache/panache-hibernate-common/runtime/src/main/java/io/quarkus/panache/hibernate/common/runtime/PanacheJpaUtil.java +++ b/extensions/panache/panache-hibernate-common/runtime/src/main/java/io/quarkus/panache/hibernate/common/runtime/PanacheJpaUtil.java @@ -52,11 +52,12 @@ public static String getFastCountQuery(String query) { Matcher selectMatcher = SELECT_PATTERN.matcher(query); if (selectMatcher.matches()) { // this one cannot be null - String firstSelection = selectMatcher.group(1).trim().toLowerCase(Locale.ROOT); - if (firstSelection.startsWith("distinct")) { + String firstSelection = selectMatcher.group(1).trim(); + String firstSelectionForMatching = firstSelection.toLowerCase(Locale.ROOT); + if (firstSelectionForMatching.startsWith("distinct")) { // if firstSelection matched distinct only, we have something wrong in our selection list, probably functions/parens // so bail out - if (firstSelection.length() == 8) { + if (firstSelectionForMatching.length() == 8) { return getCountQueryUsingParser(query); } // this one can be null diff --git a/extensions/panache/panache-hibernate-common/runtime/src/test/java/io/quarkus/panache/hibernate/common/runtime/CountTest.java b/extensions/panache/panache-hibernate-common/runtime/src/test/java/io/quarkus/panache/hibernate/common/runtime/CountTest.java index a9727e2b4cd6b..4c92a8a4a468a 100644 --- a/extensions/panache/panache-hibernate-common/runtime/src/test/java/io/quarkus/panache/hibernate/common/runtime/CountTest.java +++ b/extensions/panache/panache-hibernate-common/runtime/src/test/java/io/quarkus/panache/hibernate/common/runtime/CountTest.java @@ -47,6 +47,8 @@ public void testFastVersion() { assertFastCountQuery("SELECT COUNT(*) from bar", "select foo,gee from bar"); // one column distinct assertFastCountQuery("SELECT COUNT(distinct foo) from bar", "select distinct foo from bar"); + // with case preserved + assertFastCountQuery("SELECT COUNT(distinct fOO) from bar", "select distinct fOO from bar"); // two columns distinct Assertions.assertThrows(RuntimeException.class, () -> assertFastCountQuery("XX", "select distinct foo,gee from bar")); // nested order by not touched From d09e3ee025614bfb31a32a8d551b0acf865f5831 Mon Sep 17 00:00:00 2001 From: Jakub Jedlicka Date: Fri, 18 Oct 2024 21:36:57 +0200 Subject: [PATCH 22/29] Small fixes for OIDC client guides (cherry picked from commit ddc01638015eaf90745d51d3563e42ecce2a9983) --- ...urity-openid-connect-client-reference.adoc | 71 ++++++++++++++++--- .../security-openid-connect-client.adoc | 20 +++--- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 8052f7ac76c5c..253ae2b5132ff 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -238,9 +238,9 @@ You can inject `Tokens` that use `OidcClient` internally. `Tokens` can be used t [source,java] ---- -import jakarta.inject.PostConstruct; import jakarta.inject.Inject; import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import io.quarkus.oidc.client.Tokens; @@ -281,6 +281,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.OidcClients; @@ -404,6 +405,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; import io.quarkus.oidc.client.runtime.TokensHelper; @@ -442,7 +444,10 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import io.smallrye.mutiny.Uni; +import io.quarkus.oidc.client.NamedOidcClient; +import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.runtime.TokensHelper; @Path("/clients") @@ -473,6 +478,20 @@ The same qualifier can be used to specify the `OidcClient` used for a `Tokens` i [source,java] ---- +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.ext.Provider; + +import io.quarkus.oidc.client.NamedOidcClient; +import io.quarkus.oidc.client.Tokens; + @Provider @Priority(Priorities.AUTHENTICATION) @RequestScoped @@ -502,11 +521,11 @@ Add the following Maven Dependency: ---- -Note it will also bring `io.quarkus:quarkus-oidc-client`. +NOTE: It will also bring `io.quarkus:quarkus-oidc-client`. `quarkus-rest-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter`. -It works similarly to the way `OidcClientRequestFilter` does (see <>) - it uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization` `Bearer` scheme value. The difference is that it works with xref:rest-client.adoc[Reactive RestClient] and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens. +It works similarly to the way `OidcClientRequestFilter` does (see <>) - it uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization Bearer` scheme value. The difference is that it works with xref:rest-client.adoc[Reactive RestClient] and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens. `OidcClientRequestReactiveFilter` delays an initial token acquisition until it is executed to avoid blocking an IO thread. @@ -514,10 +533,11 @@ You can selectively register `OidcClientRequestReactiveFilter` by using either ` [source,java] ---- -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter @@ -537,6 +557,8 @@ import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter; import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(OidcClientRequestReactiveFilter.class) @@ -554,10 +576,11 @@ For example, given <> `jwt-secret` named OIDC client decl [source,java] ---- -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter("jwt-secret") @@ -582,9 +605,9 @@ Add the following Maven Dependency: ---- -Note it will also bring `io.quarkus:quarkus-oidc-client`. +NOTE: It will also bring `io.quarkus:quarkus-oidc-client`. -`quarkus-resteasy-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestFilter` Jakarta REST ClientRequestFilter which uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization` `Bearer` scheme value. +`quarkus-resteasy-client-oidc-filter` extension provides `io.quarkus.oidc.client.filter.OidcClientRequestFilter` Jakarta REST ClientRequestFilter which uses `OidcClient` to acquire the access token, refresh it if needed, and set it as an HTTP `Authorization Bearer` scheme value. By default, this filter will get `OidcClient` to acquire the first pair of access and refresh tokens at its initialization time. If the access tokens are short-lived and refresh tokens are unavailable, then the token acquisition should be delayed with `quarkus.oidc-client.early-tokens-acquisition=false`. @@ -594,6 +617,8 @@ You can selectively register `OidcClientRequestFilter` by using either `io.quark ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter @@ -612,6 +637,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(OidcClientRequestFilter.class) @@ -633,6 +660,8 @@ For example, given <> `jwt-secret` named OIDC client decl ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @OidcClientFilter("jwt-secret") @@ -650,6 +679,14 @@ If you prefer, you can use your own custom filter and inject `Tokens`: [source,java] ---- +import java.io.IOException; +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.ext.Provider; import io.quarkus.oidc.client.Tokens; @Provider @@ -1003,8 +1040,10 @@ Add the following dependencies to your test project: org.wiremock wiremock test + ${wiremock.version} // <1> ---- +<1> Use a proper Wiremock version. All available versions can be found link:https://search.maven.org/artifact/org.wiremock/wiremock[here]. Write a Wiremock-based `QuarkusTestResourceLifecycleManager`, for example: [source, java] @@ -1200,6 +1239,8 @@ You can selectively register `AccessTokenRequestReactiveFilter` by using either ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @AccessToken @@ -1218,6 +1259,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(AccessTokenRequestReactiveFilter.class) @@ -1246,7 +1289,7 @@ quarkus.resteasy-client-oidc-token-propagation.exchange-token=true <1> ---- <1> Please note that the `exchange-token` configuration property is ignored when the OidcClient name is set with the `io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient` annotation attribute. -Note `AccessTokenRequestReactiveFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. +NOTE: `AccessTokenRequestReactiveFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. If you work with providers such as `Azure` that link:https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#example[require using] link:https://www.rfc-editor.org/rfc/rfc7523#section-2.1[JWT bearer token grant] to exchange the current token, then you can configure `AccessTokenRequestReactiveFilter` to exchange the token like this: @@ -1290,6 +1333,8 @@ You can selectively register `AccessTokenRequestFilter` by using either `io.quar ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @AccessToken @@ -1307,6 +1352,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessTokenRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(AccessTokenRequestFilter.class) @@ -1350,7 +1397,7 @@ quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access quarkus.resteasy-client-oidc-token-propagation.exchange-token=true ---- -Note `AccessTokenRequestFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. +NOTE: `AccessTokenRequestFilter` will use `OidcClient` to exchange the current token, and you can use `quarkus.oidc-client.grant-options.exchange` to set the additional exchange properties expected by your OpenID Connect Provider. `AccessTokenRequestFilter` uses a default `OidcClient` by default. A named `OidcClient` can be selected with a `quarkus.resteasy-client-oidc-token-propagation.client-name` configuration property. @@ -1358,7 +1405,7 @@ Note `AccessTokenRequestFilter` will use `OidcClient` to exchange the current to Using `JsonWebTokenRequestFilter` is recommended if you work with Bearer JWT tokens where these tokens can have their claims, such as `issuer` and `audience` modified and the updated tokens secured (for example, re-signed) again. It expects an injected `org.eclipse.microprofile.jwt.JsonWebToken` and, therefore, will not work with the opaque tokens. Also, if your OpenID Connect Provider supports a Token Exchange protocol, then it is recommended to use `AccessTokenRequestFilter` instead - as both JWT and opaque bearer tokens can be securely exchanged with `AccessTokenRequestFilter`. -`JsonWebTokenRequestFilter` makes it easy for `Service A` implementations to update the injected `org.eclipse.microprofile.jwt.JsonWebToken` with the new `issuer` and `audience` claim values and secure the updated token again with a new signature. The only difficult step is ensuring that `Service A` has a signing key; it should be provisioned from a secure file system or remote secure storage such as Vault. +`JsonWebTokenRequestFilter` makes it easy for `Service A` implementations to update the injected `org.eclipse.microprofile.jwt.JsonWebToken` with the new `issuer` and `audience` claim values and secure the updated token again with a new signature. The only difficult step is ensuring that `Service A` has a signing key which should be provisioned from a secure file system or remote secure storage such as Vault. You can selectively register `JsonWebTokenRequestFilter` by using either `io.quarkus.oidc.token.propagation.JsonWebToken` or `org.eclipse.microprofile.rest.client.annotation.RegisterProvider`, for example: @@ -1366,6 +1413,8 @@ You can selectively register `JsonWebTokenRequestFilter` by using either `io.qua ---- import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.JsonWebToken; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @JsonWebToken @@ -1383,6 +1432,8 @@ or import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; @RegisterRestClient @RegisterProvider(JsonWebTokenRequestFilter.class) diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index 318d963c1177b..134a70af24368 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -172,7 +172,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.client.filter.OidcClientFilter; @@ -237,7 +236,6 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; @@ -446,7 +444,7 @@ public class FrontendExceptionMapper implements ExceptionMapper> section. @@ -530,18 +528,18 @@ include::{includes}/devtools/dev.adoc[] xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] launches a Keycloak container and imports `quarkus-realm.json`. -Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click a `Provider: Keycloak` link in the *OpenID Connect Dev UI* card. +Open a xref:dev-ui.adoc[Dev UI] available at http://localhost:8080/q/dev-ui[/q/dev-ui] and click a `Keycloak provider` link in the *OpenID Connect Dev UI* card. When asked, log in to a `Single Page Application` provided by the OpenID Connect Dev UI: * Log in as `alice`, with the password, `alice`. -This user has a `user` role. - ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. - ** Access `/frontend/admin-name-with-propagated-token`, which returns `403`. - * Log out and back in as `admin` with the password, `admin`. This user has both `admin` and `user` roles. ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. ** Access `/frontend/admin-name-with-propagated-token`, which returns `200`. + * Log out and back in as `bob` with the password, `bob`. +This user has a `user` role. + ** Access `/frontend/user-name-with-propagated-token`, which returns `200`. + ** Access `/frontend/admin-name-with-propagated-token`, which returns `403`. You have tested that `FrontendResource` can propagate the access tokens from the OpenID Connect Dev UI. @@ -681,7 +679,7 @@ Call the `/admin-name-with-oidc-client-token-header-param`. In contrast with the [source,bash] ---- curl -i -X GET \ - http://localhost:8080/frontend/admin-name-with-oidc-client-token-param + http://localhost:8080/frontend/admin-name-with-oidc-client-token-header-param ---- Next, test the endpoints which use OIDC client in in the blocking mode. @@ -699,7 +697,7 @@ Call the `/admin-name-with-oidc-client-token-header-param-blocking`. In contrast [source,bash] ---- curl -i -X GET \ - http://localhost:8080/frontend/admin-name-with-oidc-client-token-param-blocking + http://localhost:8080/frontend/admin-name-with-oidc-client-token-header-param-blocking ---- == References From f7aa845991fe613ef86bac6db143bdf0b9aa3617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sol=C3=B3rzano?= Date: Sat, 19 Oct 2024 12:31:51 +0200 Subject: [PATCH 23/29] Fix AsciiDoc links syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jorge Solórzano (cherry picked from commit b89b9ef5a472193b3b0c72801853e8f8fc3ca2e2) --- docs/src/main/asciidoc/dev-ui.adoc | 10 +++++----- docs/src/main/asciidoc/rest-migration.adoc | 2 +- .../main/asciidoc/security-keycloak-authorization.adoc | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/dev-ui.adoc b/docs/src/main/asciidoc/dev-ui.adoc index ed1c767df0b35..75c4d41b0531a 100644 --- a/docs/src/main/asciidoc/dev-ui.adoc +++ b/docs/src/main/asciidoc/dev-ui.adoc @@ -463,12 +463,12 @@ customElements.define('qwc-arc-beans', QwcArcBeans); ===== Qomponent -We also include all components from the [Qomponent](https://github.com/qomponent) library +We also include all components from the https://github.com/qomponent[Qomponent] library -- [Card](https://www.npmjs.com/package/@qomponent/qui-card) -- [Badge](https://www.npmjs.com/package/@qomponent/qui-badge) -- [Alert](https://www.npmjs.com/package/@qomponent/qui-alert) -- [Code block](https://www.npmjs.com/package/@qomponent/qui-code-block) +- https://www.npmjs.com/package/@qomponent/qui-card[Card] +- https://www.npmjs.com/package/@qomponent/qui-badge[Badge] +- https://www.npmjs.com/package/@qomponent/qui-alert[Alert] +- https://www.npmjs.com/package/@qomponent/qui-code-block[Code block] ====== Card diff --git a/docs/src/main/asciidoc/rest-migration.adoc b/docs/src/main/asciidoc/rest-migration.adoc index e8b8b8680b061..6ba3120372fde 100644 --- a/docs/src/main/asciidoc/rest-migration.adoc +++ b/docs/src/main/asciidoc/rest-migration.adoc @@ -225,7 +225,7 @@ extension is used by a specific set of users / applications. When opting for supporting both extensions, the deployment module of the custom extension will usually depend on the SPI modules - `quarkus-jaxrs-spi-deployment`, `quarkus-resteasy-common-spi`, `quarkus-rest-spi-deployment`, while the runtime modules will have `optional` dependencies on the runtime modules of both RESTEasy flavors. -A couple good examples of how Quarkus uses this strategy to support both RESTEasy flavors in the core repository can be seen [here](https://github.com/quarkusio/quarkus/pull/21089) and [here](https://github.com/quarkusio/quarkus/pull/20874). +A couple good examples of how Quarkus uses this strategy to support both RESTEasy flavors in the core repository can be seen https://github.com/quarkusio/quarkus/pull/21089[here] and https://github.com/quarkusio/quarkus/pull/20874[here]. In general, it should not be needed to have two different versions of the custom extension to support both flavors. Such a choice is only strictly necessary if it is desired for the extension consumers (i.e. Quarkus applications) to not have to select a RESTEasy version themselves. diff --git a/docs/src/main/asciidoc/security-keycloak-authorization.adoc b/docs/src/main/asciidoc/security-keycloak-authorization.adoc index 22e7f26256f47..275bace269635 100644 --- a/docs/src/main/asciidoc/security-keycloak-authorization.adoc +++ b/docs/src/main/asciidoc/security-keycloak-authorization.adoc @@ -635,7 +635,7 @@ public class CustomTenantPolicyConfigResolver implements TenantPolicyConfigResol == Configuration reference -This configuration adheres to the official [Keycloak Policy Enforcer Configuration](https://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_filter) guidelines. +This configuration adheres to the official https://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_filter[Keycloak Policy Enforcer Configuration] guidelines. For detailed insights into various configuration options, see the following documentation: include::{generated-dir}/config/quarkus-keycloak-authorization.adoc[opts=optional] From 781daae3578cd20f0c971b1d657a98758c6126f5 Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sat, 19 Oct 2024 13:11:46 +0200 Subject: [PATCH 24/29] [docs] Wording fix in HTTP reference (cherry picked from commit c8f2926447cc7d34d39d256b6f201a14614b5a33) --- docs/src/main/asciidoc/http-reference.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 6c102f30f04fa..10b58e6b2866b 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -145,7 +145,7 @@ quarkus.tls.key-store.pem.0.key=server.key quarkus.http.insecure-requests=disabled # Reject HTTP requests ---- -So you a `p12` (PKCS12) key store, use the following configuration: +To use a `p12` (PKCS12) key store, apply the following configuration: [source,properties] ---- From ab26b22b94a840a4c6a5c742b33a5b8e13301c60 Mon Sep 17 00:00:00 2001 From: Melloware Date: Mon, 21 Oct 2024 07:23:48 -0400 Subject: [PATCH 25/29] MultiStage Docker instructions needed `chmod ./mvnw` (cherry picked from commit 3e0dfa9ca86fdeaecc5b5e2adeb5761116458bca) --- docs/src/main/asciidoc/building-native-image.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index 5780d839610b2..760fbe72fb972 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -579,7 +579,7 @@ Sample Dockerfile for building with Maven: ---- ## Stage 1 : build with maven builder image with native capabilities FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:{mandrel-flavor} AS build -COPY --chown=quarkus:quarkus mvnw /code/mvnw +COPY --chown=quarkus:quarkus --chmod=0755 mvnw /code/mvnw COPY --chown=quarkus:quarkus .mvn /code/.mvn COPY --chown=quarkus:quarkus pom.xml /code/ USER quarkus From f34a3cc409db4e87eff8a4363187c75219e2d1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 18 Oct 2024 16:58:27 +0200 Subject: [PATCH 26/29] Upgrade to Hibernate Commons Annotations 7.0.3.Final No change at runtime, as 7.0.3.Final is identical to 7.0.1.Final except for some build plugins. This is mainly useful to allow rebuilding Quarkus and all its dependencies from source, as 7.0.1.Final has build-time dependencies on artifacts that were hosted on JCenter and are no longer available. (cherry picked from commit 8a0f9e4e12ad2a05ee9a472881fab2a201c3e8a6) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a32db0d85e60f..3f17ed8e41f6b 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ 6.6.1.Final 4.13.0 1.14.18 - 7.0.1.Final + 7.0.3.Final 2.4.2.Final 8.0.1.Final 7.2.1.Final From f05eeee4bb9b4b6d5d958a8df842a72d321e421a Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sun, 20 Oct 2024 13:28:04 +0100 Subject: [PATCH 27/29] Check expiry of the cached OIDC token introspections (cherry picked from commit eeed776efa4bc4f033d2a66cad68bd1ac01c5e69) --- ...efaultTokenIntrospectionUserInfoCache.java | 21 +++++++- .../io/quarkus/oidc/runtime/MemoryCache.java | 2 +- .../runtime/TokenIntrospectionCacheTest.java | 53 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionCacheTest.java diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java index 214436d5e6988..647bb0ab695ef 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenIntrospectionUserInfoCache.java @@ -2,12 +2,15 @@ import jakarta.enterprise.event.Observes; +import org.jboss.logging.Logger; + import io.quarkus.oidc.OidcRequestContext; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TokenIntrospection; import io.quarkus.oidc.TokenIntrospectionCache; import io.quarkus.oidc.UserInfo; import io.quarkus.oidc.UserInfoCache; +import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.runtime.ShutdownEvent; import io.smallrye.mutiny.Uni; import io.vertx.core.Vertx; @@ -23,6 +26,7 @@ * which has been introspected which will be used to request UserInfo. */ public class DefaultTokenIntrospectionUserInfoCache implements TokenIntrospectionCache, UserInfoCache { + private static final Logger LOG = Logger.getLogger(DefaultTokenIntrospectionUserInfoCache.class); private static final Uni NULL_INTROSPECTION_UNI = Uni.createFrom().nullItem(); private static final Uni NULL_USERINFO_UNI = Uni.createFrom().nullItem(); @@ -50,7 +54,22 @@ public Uni addIntrospection(String token, TokenIntrospection introspection public Uni getIntrospection(String token, OidcTenantConfig oidcConfig, OidcRequestContext requestContext) { CacheEntry entry = cache.get(token); - return entry == null ? NULL_INTROSPECTION_UNI : Uni.createFrom().item(entry.introspection); + if (entry == null || entry.introspection == null) { + return NULL_INTROSPECTION_UNI; + } + if (isTokenExpired(entry.introspection.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP), oidcConfig)) { + LOG.debug("Introspected token has expired, removing it from the token introspection cache"); + cache.remove(token); + return NULL_INTROSPECTION_UNI; + } + + return Uni.createFrom().item(entry.introspection); + } + + private static boolean isTokenExpired(Long exp, OidcTenantConfig oidcConfig) { + final long lifespanGrace = oidcConfig != null ? oidcConfig.token.lifespanGrace.orElse(0) : 0; + return exp != null + && System.currentTimeMillis() / 1000 > (exp + lifespanGrace); } @Override diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java index dd8e4943ef029..6af39d5debe25 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/MemoryCache.java @@ -26,7 +26,7 @@ public MemoryCache(Vertx vertx, Optional cleanUpTimerInterval, } private void init(Vertx vertx, Optional cleanUpTimerInterval) { - if (cleanUpTimerInterval.isPresent()) { + if (vertx != null && cleanUpTimerInterval.isPresent()) { timerId = vertx.setPeriodic(cleanUpTimerInterval.get().toMillis(), new Handler() { @Override public void handle(Long event) { diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionCacheTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionCacheTest.java new file mode 100644 index 0000000000000..4f14689b62f71 --- /dev/null +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionCacheTest.java @@ -0,0 +1,53 @@ +package io.quarkus.oidc.runtime; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import io.quarkus.oidc.TokenIntrospection; +import io.quarkus.oidc.TokenIntrospectionCache; + +public class TokenIntrospectionCacheTest { + TokenIntrospectionCache cache = new DefaultTokenIntrospectionUserInfoCache(createOidcConfig(), null); + + @Test + public void testExpiredIntrospection() { + + TokenIntrospection introspectionValidFor10secs = new TokenIntrospection( + "{\"active\": true," + + "\"exp\":" + (System.currentTimeMillis() / 1000 + 10) + "}"); + TokenIntrospection introspectionValidFor3secs = new TokenIntrospection( + "{\"active\": true," + + "\"exp\":" + (System.currentTimeMillis() / 1000 + 3) + "}"); + cache.addIntrospection("tokenValidFor10secs", introspectionValidFor10secs, null, null); + cache.addIntrospection("tokenValidFor3secs", introspectionValidFor3secs, null, null); + + assertNotNull(cache.getIntrospection("tokenValidFor10secs", null, null).await().indefinitely()); + assertNotNull(cache.getIntrospection("tokenValidFor3secs", null, null).await().indefinitely()); + + await().atMost(Duration.ofSeconds(5)).pollInterval(1, TimeUnit.SECONDS) + .until(new Callable() { + + @Override + public Boolean call() throws Exception { + return cache.getIntrospection("tokenValidFor3secs", null, null).await().indefinitely() == null; + } + + }); + + assertNotNull(cache.getIntrospection("tokenValidFor10secs", null, null).await().indefinitely()); + assertNull(cache.getIntrospection("tokenValidFor3secs", null, null).await().indefinitely()); + } + + private static OidcConfig createOidcConfig() { + OidcConfig cfg = new OidcConfig(); + cfg.tokenCache.maxSize = 2; + return cfg; + } +} From 8c9bbc09a51eeb3b44d14c6f68d04e549f13906d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 21 Oct 2024 13:30:43 +0300 Subject: [PATCH 28/29] Add dev-mode setting for forcing the use of C2 (cherry picked from commit 19e1a7be2d93c94d35ee188e8f9c0ef57e4109f2) --- .../quarkus/deployment/dev/QuarkusDevModeLauncher.java | 9 ++++++++- .../main/java/io/quarkus/gradle/tasks/QuarkusDev.java | 10 ++++++++++ .../maven/src/main/java/io/quarkus/maven/DevMojo.java | 10 ++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java index d5e2b05b68da4..4f99d00ef5eb6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusDevModeLauncher.java @@ -67,6 +67,12 @@ public B preventnoverify(boolean preventnoverify) { return (B) this; } + @SuppressWarnings("unchecked") + public B forceC2(boolean force) { + forceC2 = force; + return (B) this; + } + @SuppressWarnings("unchecked") public B jvmArgs(String jvmArgs) { args.add(jvmArgs); @@ -316,6 +322,7 @@ public R build() throws Exception { private String targetJavaVersion; private Set buildFiles = new HashSet<>(0); private boolean deleteDevJar = true; + private boolean forceC2 = false; private String baseName; private Consumer entryPointCustomizer; private String applicationArgs; @@ -335,7 +342,7 @@ protected QuarkusDevModeLauncher() { protected void prepare() throws Exception { JBossVersion.disableVersionLogging(); - if (!JavaVersionUtil.isGraalvmJdk()) { + if (!JavaVersionUtil.isGraalvmJdk() && !forceC2) { // prevent C2 compiler for kicking in - makes startup a little faster // it only makes sense in dev-mode but it is not available when GraalVM is used as the JDK args.add("-XX:TieredStopAtLevel=1"); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index d748b2d1e078b..5cc8b1319ac2f 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -93,6 +93,7 @@ public abstract class QuarkusDev extends QuarkusTask { private final MapProperty environmentVariables; private final Property preventNoVerify; + private final Property forceC2; private final Property shouldPropagateJavaCompilerArgs; private final ListProperty args; private final ListProperty jvmArgs; @@ -128,6 +129,9 @@ public QuarkusDev( preventNoVerify = objectFactory.property(Boolean.class); preventNoVerify.convention(false); + forceC2 = objectFactory.property(Boolean.class); + forceC2.convention(false); + shouldPropagateJavaCompilerArgs = objectFactory.property(Boolean.class); shouldPropagateJavaCompilerArgs.convention(true); @@ -222,6 +226,11 @@ public boolean isPreventnoverify() { return getPreventNoVerify().get(); } + @Input + public Property getForceC2() { + return forceC2; + } + /** * @deprecated see {@link #getPreventNoVerify()} */ @@ -414,6 +423,7 @@ private QuarkusDevModeLauncher newLauncher(final AnalyticsService analyticsServi } GradleDevModeLauncher.Builder builder = GradleDevModeLauncher.builder(getLogger(), java) .preventnoverify(getPreventNoVerify().getOrElse(false)) + .forceC2(getForceC2().getOrElse(false)) .projectDir(projectDir) .buildDir(buildDir) .outputDir(buildDir) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 8879a3e604cab..f605744e82af5 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -296,6 +296,15 @@ public class DevMojo extends AbstractMojo { @Parameter(defaultValue = "${preventnoverify}") private boolean preventnoverify = false; + /** + * This value is intended to be set to true when we want to require C2 compilation instead of preventing it from + * ever kicking in. + * Setting this will likely have a small negative effect on startup time and should only be done when it absolutely + * makes sense. + */ + @Parameter(defaultValue = "${forceC2}") + private boolean forceC2 = false; + /** * Whether changes in the projects that appear to be dependencies of the project containing the application to be launched * should trigger hot-reload. By default, they do. @@ -1247,6 +1256,7 @@ private QuarkusDevModeLauncher newLauncher(String actualDebugPort, String bootst final MavenDevModeLauncher.Builder builder = MavenDevModeLauncher.builder(java, getLog()) .preventnoverify(preventnoverify) + .forceC2(forceC2) .buildDir(buildDir) .outputDir(outputDirectory) .suspend(suspend) From d0852f1893738ecc8d336f5684013e9693c768c6 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 22 Oct 2024 09:57:14 +0200 Subject: [PATCH 29/29] Better resolve Quarkus version for quickstarts tests --- .github/workflows/ci-actions-incremental.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index a141bc27e384b..df2ab3edf07bd 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -938,16 +938,16 @@ jobs: } else { return branch } + - name: Get Quarkus version + id: get-quarkus-version + run: | + echo "quarkus-version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT - name: Compile Quickstarts env: CAPTURE_BUILD_SCAN: true run: | git clone --depth=1 -b ${{ steps.get-quickstarts-branch.outputs.result }} https://github.com/quarkusio/quarkus-quickstarts.git && cd quarkus-quickstarts - if [ "${{ steps.get-quickstarts-branch.outputs.result }}" != "development" ]; then - QUARKUS_VERSION_ARGS="-Dquarkus.platform.version=${{ steps.get-quickstarts-branch.outputs.result }}.999-SNAPSHOT" - else - QUARKUS_VERSION_ARGS="" - fi + QUARKUS_VERSION_ARGS="-Dquarkus.platform.version=${{ steps.get-quarkus-version.outputs.quarkus-version }}" export LANG=en_US && ./mvnw -e -B -fae --settings .github/mvn-settings.xml clean verify -DskipTests -Dquarkus.platform.group-id=io.quarkus $QUARKUS_VERSION_ARGS - name: Prepare build reports archive if: always()