From 7e1e955c090668b534d1da4a706395968b5572a2 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 6 Nov 2023 13:24:10 +0200 Subject: [PATCH 01/29] Handle generic types for ParamConverter in REST Client Fixes: #36639 (cherry picked from commit 1aff10f92ca2bb57980983b5da3e7e9f3583868a) --- .../JaxrsClientReactiveProcessor.java | 4 +- .../converter/GenericsConverterTest.java | 127 ++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/GenericsConverterTest.java diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index fa0765d2559bf..0cbc6e7668f68 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -2644,7 +2644,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth if (isCollection(valueType, index)) { if (valueType.kind() == PARAMETERIZED_TYPE) { Type paramType = valueType.asParameterizedType().arguments().get(0); - if (paramType.kind() == CLASS) { + if ((paramType.kind() == CLASS) || (paramType.kind() == PARAMETERIZED_TYPE)) { componentType = paramType.name().toString(); } } @@ -2671,7 +2671,7 @@ private ResultHandle addQueryParam(MethodInfo jandexMethod, BytecodeCreator meth } else if (isCollection(type, index)) { if (type.kind() == PARAMETERIZED_TYPE) { Type paramType = type.asParameterizedType().arguments().get(0); - if (paramType.kind() == CLASS) { + if ((paramType.kind() == CLASS) || (paramType.kind() == PARAMETERIZED_TYPE)) { componentType = paramType.name().toString(); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/GenericsConverterTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/GenericsConverterTest.java new file mode 100644 index 0000000000000..61a8ad069bae6 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/GenericsConverterTest.java @@ -0,0 +1,127 @@ +package io.quarkus.rest.client.reactive.converter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.ext.ParamConverter; +import jakarta.ws.rs.ext.ParamConverterProvider; +import jakarta.ws.rs.ext.Provider; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class GenericsConverterTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + + @TestHTTPResource + URI baseUri; + + @Test + void testSingle() { + TestClient client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(TestClient.class); + var result = client.wrapper(new WrapperClass<>(StatusEnum.ACTIVE)); + assertEquals("ACTIVE", result); + } + + @Test + void testList() { + TestClient client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(TestClient.class); + var result = client + .wrapperList(List.of(new WrapperClass<>(StatusEnum.ACTIVE), new WrapperClass<>(StatusEnum.INACTIVE))); + assertEquals("ACTIVE,INACTIVE", result); + } + + public enum StatusEnum { + + ACTIVE, + INACTIVE + } + + public static class WrapperClass> { + + private final E value; + + public WrapperClass(final E value) { + this.value = value; + } + + public E getValue() { + return value; + } + } + + public static class WrapperClassParamConverter implements ParamConverter> { + + @Override + public WrapperClass fromString(String value) { + return new WrapperClass<>(Enum.valueOf(StatusEnum.class, value)); + } + + @Override + public String toString(WrapperClass wrapperClass) { + return wrapperClass != null ? wrapperClass.getValue().toString() : null; + } + + } + + @Provider + public static class WrapperClassParamConverterProvider implements ParamConverterProvider { + + @Override + @SuppressWarnings("unchecked") + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (WrapperClass.class.isAssignableFrom(rawType)) { + return (ParamConverter) new WrapperClassParamConverter(); + } + + return null; + } + } + + @Path("/test") + public static class TestResource { + + @GET + @Path("/single") + public String wrapper(@QueryParam("wrapper") final WrapperClass wrapper) { + return wrapper.getValue().toString(); + } + + @GET + @Path("/list") + public String wrapperList(@QueryParam("wrapperList") final List> wrapperList) { + return wrapperList.stream().map(WrapperClass::getValue).map(Enum::name).collect(Collectors.joining(",")); + } + + } + + @Path("/test") + public interface TestClient { + + @GET + @Path("/single") + String wrapper(@QueryParam("wrapper") final WrapperClass wrapper); + + @GET + @Path("/list") + String wrapperList(@QueryParam("wrapperList") final List> wrapperList); + + } + +} From 17281e0579f3e55043489e366ebeefc36bc3aec4 Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Fri, 10 Nov 2023 16:08:12 -0500 Subject: [PATCH 02/29] Fix vale errors and some warnings in OidcCommonConfig (cherry picked from commit 32f13d47a96ee74cb5da49226d63e06069772012) From 4da919f04316e1b009e120ef8a23db3db77ba203 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Fri, 10 Nov 2023 16:26:37 +0100 Subject: [PATCH 03/29] Never register server specific providers in REST Client (fixed) (cherry picked from commit 569ea37ab93d93f3d41d57dd733ddfa8404cf673) --- .../deployment/RestClientReactiveProcessor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 066d733223757..c0f0de3c3dea8 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -303,18 +303,20 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, continue; } } - DotName providerDotName = providerClass.name(); + + List providerInterfaceNames = providerClass.interfaceNames(); // don't register server specific types - if (providerDotName.equals(ResteasyReactiveDotNames.CONTAINER_REQUEST_FILTER) - || providerDotName.equals(ResteasyReactiveDotNames.CONTAINER_RESPONSE_FILTER) - || providerDotName.equals(ResteasyReactiveDotNames.EXCEPTION_MAPPER)) { + if (providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_REQUEST_FILTER) + || providerInterfaceNames.contains(ResteasyReactiveDotNames.CONTAINER_RESPONSE_FILTER) + || providerInterfaceNames.contains(ResteasyReactiveDotNames.EXCEPTION_MAPPER)) { continue; } - if (providerClass.interfaceNames().contains(ResteasyReactiveDotNames.FEATURE)) { + if (providerInterfaceNames.contains(ResteasyReactiveDotNames.FEATURE)) { continue; // features should not be automatically registered for the client, see javadoc for Feature } + DotName providerDotName = providerClass.name(); int priority = getAnnotatedPriority(index, providerDotName.toString(), Priorities.USER); constructor.invokeVirtualMethod( From 57fd2368fb96826e5fc277ec2becf16d14375ea1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 13 Nov 2023 11:51:50 +0200 Subject: [PATCH 04/29] Use empty string in Sse event when there is no data Using an empty string instead of null is what the classic rest client does, so let's align with it Closes: #37033 (cherry picked from commit 042aed288e3f27db4faa7a9ad0e29af817c0c47b) --- .../reactive/jsonb/deployment/test/sse/SseParserTest.java | 8 ++++---- .../reactive/server/test/stream/StreamTestCase.java | 2 +- .../jboss/resteasy/reactive/client/impl/SseParser.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java index 7f8bd584a0244..36cca2e23779e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jsonb/deployment/src/test/java/io/quarkus/resteasy/reactive/jsonb/deployment/test/sse/SseParserTest.java @@ -37,14 +37,14 @@ public void testParser() { testParser("data:foo\ndata:\ndata:bar\n\n", "foo\n\nbar", null, null, null, SseEvent.RECONNECT_NOT_SET); // no data: no event - testParser("\n", null, null, null, null, SseEvent.RECONNECT_NOT_SET); - testParser("data:\n\n", null, null, null, null, SseEvent.RECONNECT_NOT_SET); - testParser("data\n\n", null, null, null, null, SseEvent.RECONNECT_NOT_SET); + testParser("\n", "", null, null, null, SseEvent.RECONNECT_NOT_SET); + testParser("data:\n\n", "", null, null, null, SseEvent.RECONNECT_NOT_SET); + testParser("data\n\n", "", null, null, null, SseEvent.RECONNECT_NOT_SET); // all fields testParser("data:DATA\nid:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", "DATA", "COMMENT", "ID", "NAME", 23); // all fields and no data - testParser("id:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", null, "COMMENT", "ID", "NAME", 23); + testParser("id:ID\n:COMMENT\nretry:23\nevent:NAME\n\n", "", "COMMENT", "ID", "NAME", 23); // optional space after colon testParser("data:foo\n\n", "foo", null, null, null, SseEvent.RECONNECT_NOT_SET); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java index 7a2135bd216cd..9da6a2fa1a778 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java @@ -264,7 +264,7 @@ public void testSseForMultiWithOutboundSseEvent() throws InterruptedException { }); sse.open(); Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS)); - org.assertj.core.api.Assertions.assertThat(results).containsExactly(null, "uno", "dos", "tres"); + org.assertj.core.api.Assertions.assertThat(results).containsExactly("", "uno", "dos", "tres"); org.assertj.core.api.Assertions.assertThat(ids).containsExactly(null, "one", "two", "three"); org.assertj.core.api.Assertions.assertThat(names).containsExactly(null, "eins", "zwei", "drei"); org.assertj.core.api.Assertions.assertThat(comments).containsExactly("dummy", null, null, null); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java index 7728e87ff7098..ae885afd5fb59 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/SseParser.java @@ -156,7 +156,7 @@ private void dispatchEvent() { event.setComment(commentBuffer.length() == 0 ? null : commentBuffer.toString()); // SSE spec says empty string is the default, but JAX-RS says null if not specified event.setId(lastEventId); - event.setData(dataBuffer.length() == 0 ? null : dataBuffer.toString()); + event.setData(dataBuffer.length() == 0 ? "" : dataBuffer.toString()); // SSE spec says "message" is the default, but JAX-RS says null if not specified event.setName(eventType); event.setReconnectDelay(eventReconnectTime); From ed2aa741ad03abdaa3c9d4139319a369fb24791d Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Tue, 14 Nov 2023 10:11:53 +0100 Subject: [PATCH 05/29] Updates infinispan client intelligence section (cherry picked from commit 278a9dc2418cb632f9f851f8c484b92a63d1422f) --- .../asciidoc/infinispan-client-reference.adoc | 24 +++++++++++++++---- docs/src/main/asciidoc/infinispan-client.adoc | 12 ++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/infinispan-client-reference.adoc b/docs/src/main/asciidoc/infinispan-client-reference.adoc index 7df534fcb7e2d..f188edbe0bae5 100644 --- a/docs/src/main/asciidoc/infinispan-client-reference.adoc +++ b/docs/src/main/asciidoc/infinispan-client-reference.adoc @@ -93,28 +93,42 @@ quarkus.infinispan-client.hosts=localhost:11222 <1> quarkus.infinispan-client.username=admin <2> quarkus.infinispan-client.password=password <3> - -quarkus.infinispan-client.client-intelligence=BASIC <4> ---- <1> Sets Infinispan Server address list, separated with commas <2> Sets the authentication username <3> Sets the authentication password -<4> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac. Alternatively, you can use uri connection by providing a single connection property [source,properties] ---- quarkus.infinispan-client.uri=hotrod://admin:password@localhost:11222 <1> -quarkus.infinispan-client.client-intelligence=BASIC <2> ---- <1> Sets Infinispan URI connection. The following properties will be ignored: hosts, username and password. -<2> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac [TIP] ==== Use Infinispan Dev Services to run a server and connect without configuration. ==== +=== Client intelligence +Infinispan client uses intelligence mechanisms to efficiently send requests to Infinispan Server clusters. +By default, the *HASH_DISTRIBUTION_AWARE* intelligence mechanism is enabled. +However, locally with Docker for Mac, you might experience connectivity issues. +In this case, configure the client intelligence to *BASIC*. + +Learn more in the https://infinispan.org/docs/stable/titles/hotrod_java/hotrod_java.html#hotrod-client-intelligence_hotrod-java-client[Infinispan documentation]. + +[source,properties] +---- +quarkus.infinispan-client.client-intelligence=BASIC <1> +---- +<1> Docker for Mac workaround. + +[IMPORTANT] +==== +Don't use *BASIC* in production environments by default, performance might be impacted. +==== + === Default and named connections This extension lets you configure a _default_ Infinispan client connections and _named_ ones. Named connections are essential to connect to multiple Infinispan clusters. diff --git a/docs/src/main/asciidoc/infinispan-client.adoc b/docs/src/main/asciidoc/infinispan-client.adoc index f787de4bd232c..8501b0798b154 100644 --- a/docs/src/main/asciidoc/infinispan-client.adoc +++ b/docs/src/main/asciidoc/infinispan-client.adoc @@ -303,14 +303,22 @@ Then, open the `src/main/resources/application.properties` file and add: %prod.quarkus.infinispan-client.username=admin <2> %prod.quarkus.infinispan-client.password=password <3> -## Docker 4 Mac workaround -%prod.quarkus.infinispan-client.client-intelligence=BASIC <4> +## Docker 4 Mac workaround. Uncomment only if you are using Docker for Mac. +## Read more about it in the Infinispan Reference Guide +# %prod.quarkus.infinispan-client.client-intelligence=BASIC <4> ---- <1> Sets Infinispan Server address list, separated with commas <2> Sets the authentication username <3> Sets the authentication password <4> Sets the client intelligence. Use BASIC as a workaround if using Docker for Mac. +[IMPORTANT] +==== +Client intelligence changes impact your performance in production. +Don't change the client intelligence unless strictly necessary for your case. +Read more in the xref:infinispan-client-reference.adoc[Infinispan Client extension reference guide]. +==== + == Packaging and running in JVM mode You can run the application as a conventional jar file. From e43064ad3c7b8266062a73824e4d5cc140830a74 Mon Sep 17 00:00:00 2001 From: barreiro Date: Mon, 20 Nov 2023 01:12:39 +0000 Subject: [PATCH 06/29] recognize quarkus.tls.trust-all property by keycloak-admin-client extension (cherry picked from commit 6e414c29dcc77c8f168b233277a7989f78ab8b94) --- .../reactive/KeycloakAdminClientReactiveProcessor.java | 5 +++-- .../reactive/runtime/ResteasyReactiveClientProvider.java | 8 +++++++- .../ResteasyReactiveKeycloakAdminClientRecorder.java | 4 ++-- .../deployment/KeycloakAdminClientProcessor.java | 5 +++-- .../adminclient/ResteasyKeycloakAdminClientRecorder.java | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java b/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java index 0a486c5e024d1..27c8d4a6f95c2 100644 --- a/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java +++ b/extensions/keycloak-admin-client-reactive/deployment/src/main/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientReactiveProcessor.java @@ -25,6 +25,7 @@ import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled; import io.quarkus.keycloak.admin.client.reactive.runtime.ResteasyReactiveClientProvider; import io.quarkus.keycloak.admin.client.reactive.runtime.ResteasyReactiveKeycloakAdminClientRecorder; +import io.quarkus.runtime.TlsConfig; public class KeycloakAdminClientReactiveProcessor { @@ -53,8 +54,8 @@ public void nativeImage(BuildProducer serviceProviderP @Record(ExecutionTime.STATIC_INIT) @Produce(ServiceStartBuildItem.class) @BuildStep - public void integrate(ResteasyReactiveKeycloakAdminClientRecorder recorder) { - recorder.setClientProvider(); + public void integrate(ResteasyReactiveKeycloakAdminClientRecorder recorder, TlsConfig tlsConfig) { + recorder.setClientProvider(tlsConfig.trustAll); } @Record(ExecutionTime.RUNTIME_INIT) diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java index 510b8278e01ea..814bc2442c9b7 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java @@ -30,9 +30,15 @@ public class ResteasyReactiveClientProvider implements ResteasyClientProvider { private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON); private static final int PROVIDER_PRIORITY = Priorities.USER + 100; // ensures that it will be used first + private final boolean tlsTrustAll; + + public ResteasyReactiveClientProvider(boolean tlsTrustAll) { + this.tlsTrustAll = tlsTrustAll; + } + @Override public Client newRestEasyClient(Object messageHandler, SSLContext sslContext, boolean disableTrustManager) { - ClientBuilderImpl clientBuilder = new ClientBuilderImpl().trustAll(disableTrustManager); + ClientBuilderImpl clientBuilder = new ClientBuilderImpl().trustAll(tlsTrustAll || disableTrustManager); return registerJacksonProviders(clientBuilder).build(); } diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java index 61d7605485442..12458c795592f 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveKeycloakAdminClientRecorder.java @@ -21,8 +21,8 @@ public ResteasyReactiveKeycloakAdminClientRecorder( this.keycloakAdminClientConfigRuntimeValue = keycloakAdminClientConfigRuntimeValue; } - public void setClientProvider() { - Keycloak.setClientProvider(new ResteasyReactiveClientProvider()); + public void setClientProvider(boolean tlsTrustAll) { + Keycloak.setClientProvider(new ResteasyReactiveClientProvider(tlsTrustAll)); } public Supplier createAdminClient() { diff --git a/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java b/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java index 056b3c1d5bf93..5ac5f6fef3237 100644 --- a/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java +++ b/extensions/keycloak-admin-client/deployment/src/main/java/io/quarkus/keycloak/adminclient/deployment/KeycloakAdminClientProcessor.java @@ -25,6 +25,7 @@ import io.quarkus.keycloak.admin.client.common.AutoCloseableDestroyer; import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled; import io.quarkus.keycloak.adminclient.ResteasyKeycloakAdminClientRecorder; +import io.quarkus.runtime.TlsConfig; public class KeycloakAdminClientProcessor { @@ -48,8 +49,8 @@ ReflectiveClassBuildItem reflect() { @Record(ExecutionTime.STATIC_INIT) @Produce(ServiceStartBuildItem.class) @BuildStep - public void integrate(ResteasyKeycloakAdminClientRecorder recorder) { - recorder.setClientProvider(); + public void integrate(ResteasyKeycloakAdminClientRecorder recorder, TlsConfig tlsConfig) { + recorder.setClientProvider(tlsConfig.trustAll); } @Record(ExecutionTime.RUNTIME_INIT) diff --git a/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java b/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java index 9dda7e9c3c475..75fb6d2924896 100644 --- a/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java +++ b/extensions/keycloak-admin-client/runtime/src/main/java/io/quarkus/keycloak/adminclient/ResteasyKeycloakAdminClientRecorder.java @@ -58,13 +58,13 @@ public Keycloak get() { }; } - public void setClientProvider() { + public void setClientProvider(boolean tlsTrustAll) { Keycloak.setClientProvider(new ResteasyClientClassicProvider() { @Override public Client newRestEasyClient(Object customJacksonProvider, SSLContext sslContext, boolean disableTrustManager) { // point here is to use default Quarkus providers rather than org.keycloak.admin.client.JacksonProvider // as it doesn't work properly in native mode - return ClientBuilderWrapper.create(sslContext, disableTrustManager).build(); + return ClientBuilderWrapper.create(sslContext, tlsTrustAll || disableTrustManager).build(); } }); } From 010051e766eb677cbd17776378e3fa11405ade09 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 20 Nov 2023 13:34:12 +0100 Subject: [PATCH 07/29] Fix OpenTelemetry trace exclusion of endpoints served from the management interface (cherry picked from commit 1c55119344d30875fa008e00cea2fd6146f6c2d1) --- .../deployment/tracing/TracerProcessor.java | 17 ++++++++++++++++- .../http/deployment/VertxHttpProcessor.java | 18 +++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index c958840b8e702..112ca47f5ebc9 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.opentelemetry.deployment.tracing; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -126,7 +127,21 @@ void dropNames( // Drop framework paths List nonApplicationUris = new ArrayList<>(); frameworkEndpoints.ifPresent( - frameworkEndpointsBuildItem -> nonApplicationUris.addAll(frameworkEndpointsBuildItem.getEndpoints())); + frameworkEndpointsBuildItem -> { + for (String endpoint : frameworkEndpointsBuildItem.getEndpoints()) { + // Management routes are using full urls -> Extract the path. + if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) { + try { + nonApplicationUris.add(new URL(endpoint).getPath()); + } catch (Exception ignored) { // Not an URL + nonApplicationUris.add(endpoint); + } + } else { + nonApplicationUris.add(endpoint); + } + } + }); + dropNonApplicationUris.produce(new DropNonApplicationUrisBuildItem(nonApplicationUris)); // Drop Static Resources diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 8f7cdb6ce1495..28c3ea0fa3055 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -114,16 +114,24 @@ FrameworkEndpointsBuildItem frameworkEndpoints(NonApplicationRootPathBuildItem n for (RouteBuildItem route : routes) { if (FRAMEWORK_ROUTE.equals(route.getRouteType())) { if (route.getConfiguredPathInfo() != null) { - frameworkEndpoints.add(route.getConfiguredPathInfo().getEndpointPath(nonApplicationRootPath, - managementInterfaceBuildTimeConfig, launchModeBuildItem)); + String endpointPath = route.getConfiguredPathInfo().getEndpointPath(nonApplicationRootPath, + managementInterfaceBuildTimeConfig, launchModeBuildItem); + frameworkEndpoints.add(endpointPath); continue; } if (route.getRouteFunction() != null && route.getRouteFunction() instanceof BasicRoute) { BasicRoute basicRoute = (BasicRoute) route.getRouteFunction(); if (basicRoute.getPath() != null) { - // Calling TemplateHtmlBuilder does not see very correct here, but it is the underlying API for ConfiguredPathInfo - frameworkEndpoints - .add(adjustRoot(nonApplicationRootPath.getNonApplicationRootPath(), basicRoute.getPath())); + if (basicRoute.getPath().startsWith(nonApplicationRootPath.getNonApplicationRootPath())) { + // Do not repeat the non application root path. + frameworkEndpoints.add(basicRoute.getPath()); + } else { + // Calling TemplateHtmlBuilder does not see very correct here, but it is the underlying API for ConfiguredPathInfo + String adjustRoot = adjustRoot(nonApplicationRootPath.getNonApplicationRootPath(), + basicRoute.getPath()); + frameworkEndpoints.add(adjustRoot); + } + } } } From 9926bc3081f3a07edba2bada39aa621e508f24dc Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 21 Nov 2023 13:58:09 +0100 Subject: [PATCH 08/29] Add a test for the Duplicated Context handling in the CacheResultInterceptor (cherry picked from commit f5af6b40e34ecdf7bd7596d4806b5866af02b38f) --- .../DuplicatedContextHandlingTest.java | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/DuplicatedContextHandlingTest.java diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/DuplicatedContextHandlingTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/DuplicatedContextHandlingTest.java new file mode 100644 index 0000000000000..32229c90c02ed --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/DuplicatedContextHandlingTest.java @@ -0,0 +1,178 @@ +package io.quarkus.cache.test.runtime; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.CacheResult; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Context; +import io.vertx.core.Vertx; +import io.vertx.core.impl.ContextInternal; + +public class DuplicatedContextHandlingTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot(jar -> jar + .addClass(CachedService.class)); + + @Inject + CachedService cachedService; + + @Inject + Vertx vertx; + + @Test + @ActivateRequestContext + void testDuplicatedContextHandlingWhenCalledFromNoContext() { + cachedService.direct(false).await().indefinitely(); + cachedService.direct(true).await().indefinitely(); + } + + @Test + @ActivateRequestContext + void testDuplicatedContextHandlingWhenCalledOnContext() throws InterruptedException { + ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); + if (context.isDuplicate()) { + context = context.duplicate(); + } + + CountDownLatch latch = new CountDownLatch(1); + Context tmp = context; + context.runOnContext(x -> { + cachedService.direct(false) + .invoke(() -> { + if (!tmp.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch.countDown()); + }); + Assertions.assertTrue(latch.await(1, TimeUnit.SECONDS)); + + CountDownLatch latch2 = new CountDownLatch(1); + context.runOnContext(x -> { + cachedService.direct(true) + .invoke(() -> { + if (!tmp.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch2.countDown()); + }); + Assertions.assertTrue(latch2.await(1, TimeUnit.SECONDS)); + + CountDownLatch latch3 = new CountDownLatch(1); + context.runOnContext(x -> { + cachedService.direct(false) + .invoke(() -> { + if (!tmp.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch3.countDown()); + }); + Assertions.assertTrue(latch3.await(1, TimeUnit.SECONDS)); + + } + + @Test + @ActivateRequestContext + void testDuplicatedContextHandlingWhenCalledOnDifferentContexts() throws InterruptedException { + ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); + context = context.duplicate(); + var context2 = context.duplicate(); + + CountDownLatch latch = new CountDownLatch(1); + Context tmp = context; + context.runOnContext(x -> { + cachedService.direct(false) + .invoke(() -> { + if (!tmp.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch.countDown()); + }); + Assertions.assertTrue(latch.await(1, TimeUnit.SECONDS)); + + CountDownLatch latch2 = new CountDownLatch(1); + context2.runOnContext(x -> { + cachedService.direct(false) + .invoke(() -> { + if (!context2.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch2.countDown()); + }); + Assertions.assertTrue(latch2.await(1, TimeUnit.SECONDS)); + } + + @Test + @ActivateRequestContext + void testDuplicatedContextHandlingWhenCalledContextAndAnsweredFromAnotherContext() throws InterruptedException { + ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); + context = context.duplicate(); + var context2 = context.duplicate(); + + CountDownLatch latch = new CountDownLatch(1); + Context tmp = context; + context.runOnContext(x -> { + cachedService.directOnAnotherContext(false) + .invoke(() -> { + if (!tmp.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch.countDown()); + }); + Assertions.assertTrue(latch.await(1, TimeUnit.SECONDS)); + + CountDownLatch latch2 = new CountDownLatch(1); + context2.runOnContext(x -> { + cachedService.directOnAnotherContext(false) + .invoke(() -> { + if (!context2.equals(Vertx.currentContext())) { + throw new AssertionError("Expected to go back on the caller context"); + } + }) + .subscribe().with(y -> latch2.countDown()); + }); + Assertions.assertTrue(latch2.await(1, TimeUnit.SECONDS)); + } + + @ApplicationScoped + public static class CachedService { + + volatile boolean timedout = false; + + @CacheResult(cacheName = "duplicated-context-cache", lockTimeout = 100) + public Uni direct(boolean timeout) { + if (!timeout || timedout) { + return Uni.createFrom().item("foo"); + } + timedout = true; + return Uni.createFrom().nothing(); + } + + @CacheResult(cacheName = "duplicated-context-cache", lockTimeout = 100) + public Uni directOnAnotherContext(boolean timeout) { + if (!timeout || timedout) { + return Uni.createFrom().item("foo") + .emitOn(c -> ((ContextInternal) Vertx.currentContext().owner()).duplicate().runOnContext(x -> c.run())); + } + timedout = true; + return Uni.createFrom().nothing(); + } + } + +} From 3b09ce92750d929fc908bf640363d38f149f8fd5 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 21 Nov 2023 12:28:56 +0000 Subject: [PATCH 09/29] Always execute a JPA password action (cherry picked from commit 2c29d55a7cadc4fcd4d792aeee9e160f54961ab9) --- .../common/deployment/JpaSecurityIdentityUtil.java | 13 ++++++++++--- .../jpa/common/runtime/JpaIdentityProviderUtil.java | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java b/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java index 5501ea4444353..054fcb33d6e69 100644 --- a/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java +++ b/extensions/security-jpa-common/deployment/src/main/java/io/quarkus/security/jpa/common/deployment/JpaSecurityIdentityUtil.java @@ -45,18 +45,21 @@ public static void buildIdentity(Index index, JpaSecurityDefinition jpaSecurityD PanacheEntityPredicateBuildItem panacheEntityPredicate, FieldDescriptor passwordProviderField, MethodCreator outerMethod, ResultHandle userVar, BytecodeCreator innerMethod) { // if(user == null) throw new AuthenticationFailedException(); + + PasswordType passwordType = passwordTypeValue != null ? PasswordType.valueOf(passwordTypeValue.asEnum()) + : PasswordType.MCF; + try (BytecodeCreator trueBranch = innerMethod.ifNull(userVar).trueBranch()) { + ResultHandle exceptionInstance = trueBranch .newInstance(MethodDescriptor.ofConstructor(AuthenticationFailedException.class)); + trueBranch.invokeStaticMethod(passwordActionMethod(), trueBranch.load(passwordType)); trueBranch.throwException(exceptionInstance); } // :pass = user.pass | user.getPass() ResultHandle pass = jpaSecurityDefinition.password.readValue(innerMethod, userVar); - PasswordType passwordType = passwordTypeValue != null ? PasswordType.valueOf(passwordTypeValue.asEnum()) - : PasswordType.MCF; - if (passwordType == PasswordType.CUSTOM && passwordProviderValue == null) { throw new RuntimeException("Missing password provider for password type: " + passwordType); } @@ -245,4 +248,8 @@ private static MethodDescriptor getUtilMethod(String passwordProviderMethod) { return MethodDescriptor.ofMethod(JpaIdentityProviderUtil.class, passwordProviderMethod, org.wildfly.security.password.Password.class, String.class); } + + private static MethodDescriptor passwordActionMethod() { + return MethodDescriptor.ofMethod(JpaIdentityProviderUtil.class, "passwordAction", void.class, PasswordType.class); + } } diff --git a/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java b/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java index a65f771596a5d..15a3c4710d1c8 100644 --- a/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java +++ b/extensions/security-jpa-common/runtime/src/main/java/io/quarkus/security/jpa/common/runtime/JpaIdentityProviderUtil.java @@ -2,6 +2,7 @@ import java.security.spec.InvalidKeySpecException; import java.util.List; +import java.util.UUID; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; @@ -10,9 +11,11 @@ import org.wildfly.security.password.util.ModularCrypt; import org.wildfly.security.provider.util.ProviderUtil; +import io.quarkus.elytron.security.common.BcryptUtil; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.identity.request.TrustedAuthenticationRequest; import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; +import io.quarkus.security.jpa.PasswordType; import io.quarkus.security.runtime.QuarkusPrincipal; import io.quarkus.security.runtime.QuarkusSecurityIdentity; @@ -70,4 +73,13 @@ public static Password getMcfPassword(String pass) { throw new RuntimeException(e); } } + + public static void passwordAction(PasswordType type) { + String uuid = UUID.randomUUID().toString(); + if (type == PasswordType.CLEAR) { + ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, uuid.toCharArray()); + } else { + BcryptUtil.bcryptHash(uuid); + } + } } From cdf7ff2e5f77ba017ddf7c03d00646ae52b2a380 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 24 Nov 2023 13:53:49 +0100 Subject: [PATCH 10/29] Prepare docs/sync-web-site.sh for automated releases (cherry picked from commit 37fefb380dd1a537fdc2905d8f828cf72358c5cd) --- docs/sync-web-site.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index 3e323cd748ec0..4d0c834a5a3e0 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -36,7 +36,11 @@ if [ -z $TARGET_DIR ]; then if [[ "$QUARKUS_WEB_SITE_PUSH" != "true" ]]; then GIT_OPTIONS="--depth=1" fi - git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} + if [ -n "${RELEASE_GITHUB_TOKEN}" ]; then + git clone -b develop --single-branch $GIT_OPTIONS https://${RELEASE_GITHUB_TOKEN}:@github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} + else + git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} + fi fi if [ $BRANCH == "main" ] && [ "$QUARKUS_RELEASE" == "true" ]; then From 0a322818a6601cf7a666e65790524fdf93ebdf59 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 25 Nov 2023 11:58:13 +0100 Subject: [PATCH 11/29] Use batch mode for update-version.sh (cherry picked from commit 0685076e9a835c4eb29b52daa3ef2e1db773e923) --- update-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update-version.sh b/update-version.sh index 3eb07d975cbd4..1ebbaf2382826 100755 --- a/update-version.sh +++ b/update-version.sh @@ -9,7 +9,7 @@ if [ $# -eq 0 ]; then fi VERSION=$1 -./mvnw -Dscan=false -Dgradle.cache.local.enabled=false versions:set -Dtcks -DnewVersion="${VERSION}" -DgenerateBackupPoms=false -DprocessAllModules -Prelocations +./mvnw -e -B -Dscan=false -Dgradle.cache.local.enabled=false versions:set -Dtcks -DnewVersion="${VERSION}" -DgenerateBackupPoms=false -DprocessAllModules -Prelocations if [ -f devtools/gradle/gradle.properties ]; then sed -i -r "s/^version( ?= ?).*$/version\1${VERSION}/" devtools/gradle/gradle.properties From 1f69154c13fd510d202dc7e769714c84c27446b0 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 25 Nov 2023 11:49:18 +0100 Subject: [PATCH 12/29] Avoid asking for GPG passphrase on CI (cherry picked from commit 86673f962548e140636261add0490c03553334ba) --- independent-projects/parent/pom.xml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/independent-projects/parent/pom.xml b/independent-projects/parent/pom.xml index 4c93356c91a57..fe7f018fd14e2 100644 --- a/independent-projects/parent/pom.xml +++ b/independent-projects/parent/pom.xml @@ -466,5 +466,30 @@ + + ci + + + env.CI + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + + --pinentry-mode + loopback + + + + + + + From 981ad59960e6b4768230a37cf219ded0214a40c9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:12:42 +0100 Subject: [PATCH 13/29] quarkus-update - Improve cleanup of log lines (cherry picked from commit 30e10ff9f9bbcdd9c64aab08e504b3679fbc9c9a) --- .../project/update/rewrite/QuarkusUpdateCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index b7ea74069b464..9c2bea126433f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -351,13 +351,13 @@ private String clean(String line) { return line; } - String pattern = "[" + name() + "]"; + String pattern = "[" + name() + "] "; - if (line.length() < pattern.length()) { + if (!line.startsWith(pattern)) { return line; } - return line.substring(pattern.length()).trim(); + return line.substring(pattern.length()); } private boolean matches(String line) { From 2e01ee00da429f98bb48cd56cf0eaa04543cb97b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:13:11 +0100 Subject: [PATCH 14/29] quarkus update - Remove two unused methods (cherry picked from commit 397ca66e6aca443a950ed97f047bdaef24f816d2) --- .../project/update/rewrite/QuarkusUpdateCommand.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index 9c2bea126433f..0fafb6b0adcaa 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -311,14 +311,6 @@ public static boolean isWindows() { return OS.contains("win"); } - static boolean hasGradle(Path dir) { - return Files.exists(dir.resolve("build.gradle")); - } - - private static boolean hasMaven(Path dir) { - return Files.exists(dir.resolve("pom.xml")); - } - private enum LogLevel { ERROR, From 008f1649eb9ac80563d29107746cb9e60ee6cbeb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:15:53 +0100 Subject: [PATCH 15/29] quarkus update - Fix Windows detection (cherry picked from commit e3ba23c573222508462679b72cf5939fe992b0f6) --- independent-projects/tools/devtools-common/pom.xml | 4 ++++ .../project/update/rewrite/QuarkusUpdateCommand.java | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/independent-projects/tools/devtools-common/pom.xml b/independent-projects/tools/devtools-common/pom.xml index a0f31d88c2476..9f0f02453f82a 100644 --- a/independent-projects/tools/devtools-common/pom.xml +++ b/independent-projects/tools/devtools-common/pom.xml @@ -50,6 +50,10 @@ io.smallrye.common smallrye-common-version + + io.smallrye.common + smallrye-common-os + io.fabric8 maven-model-helper diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index 0fafb6b0adcaa..c8a5ae63fb8f1 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -24,6 +24,7 @@ import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.devtools.project.BuildTool; import io.quarkus.qute.Qute; +import io.smallrye.common.os.OS; public class QuarkusUpdateCommand { @@ -305,10 +306,8 @@ private static boolean isExecutable(Path file) { return false; } - private static String OS = System.getProperty("os.name").toLowerCase(); - public static boolean isWindows() { - return OS.contains("win"); + return OS.WINDOWS.isCurrent(); } private enum LogLevel { From 50def73a6f2649a79637865d769c85fb8b98a70e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 12:17:30 +0100 Subject: [PATCH 16/29] quarkus update - Add a clean in mvn process-sources We need to clean the project before the next compilation as things have changed a lot (e.g. in the case of a Quarkus 2.x -> 3.x update). (cherry picked from commit c6ad1fb15c51da32e9ccf242c5628c65ba05221c) --- .../update/rewrite/QuarkusUpdateCommand.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index c8a5ae63fb8f1..cde516b239d60 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -58,6 +58,16 @@ public static void handle(MessageWriter log, BuildTool buildTool, Path baseDir, } } + private static void runMavenUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, + Path recipe, + boolean dryRun) { + final String mvnBinary = findMvnBinary(baseDir); + executeCommand(baseDir, getMavenUpdateCommand(mvnBinary, rewritePluginVersion, recipesGAV, recipe, dryRun), log); + + // format the sources + executeCommand(baseDir, getMavenProcessSourcesCommand(mvnBinary), log); + } + private static void runGradleUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, Path recipe, boolean dryRun) { Path tempInit = null; @@ -120,19 +130,10 @@ private static void propagateSystemPropertyIfSet(String name, List comma } } - private static void runMavenUpdate(MessageWriter log, Path baseDir, String rewritePluginVersion, String recipesGAV, - Path recipe, - boolean dryRun) { - final String mvnBinary = findMvnBinary(baseDir); - executeCommand(baseDir, getMavenUpdateCommand(mvnBinary, rewritePluginVersion, recipesGAV, recipe, dryRun), log); - - // format the sources - executeCommand(baseDir, getMavenProcessSourcesCommand(mvnBinary), log); - } - private static List getMavenProcessSourcesCommand(String mvnBinary) { List command = new ArrayList<>(); command.add(mvnBinary); + command.add("clean"); command.add("process-sources"); final String mavenSettings = getMavenSettingsArg(); if (mavenSettings != null) { From 7c7b64448b05f4b16a758cab05725d00b6de130b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 30 Nov 2023 14:41:29 +0100 Subject: [PATCH 17/29] quarkus update - Execute Maven commands in batch mode (cherry picked from commit 553c361aa8559f3aca563ff6c2bee8bfa1b4dd11) --- .../devtools/project/update/rewrite/QuarkusUpdateCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java index cde516b239d60..f53c3def02dd4 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdateCommand.java @@ -133,6 +133,7 @@ private static void propagateSystemPropertyIfSet(String name, List comma private static List getMavenProcessSourcesCommand(String mvnBinary) { List command = new ArrayList<>(); command.add(mvnBinary); + command.add("-B"); command.add("clean"); command.add("process-sources"); final String mavenSettings = getMavenSettingsArg(); @@ -148,6 +149,7 @@ private static List getMavenUpdateCommand(String mvnBinary, String rewri boolean dryRun) { final List command = new ArrayList<>(); command.add(mvnBinary); + command.add("-B"); command.add("-e"); command.add( String.format("%s:%s:%s:%s", MAVEN_REWRITE_PLUGIN_GROUP, MAVEN_REWRITE_PLUGIN_ARTIFACT, rewritePluginVersion, From ed141f6b55baa7b1d19d523a4d78d2d13dc8680f Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 4 Dec 2023 16:45:20 +0100 Subject: [PATCH 18/29] Save pathParamValues encoded and perform decoding when requested Fixes #35960 Signed-off-by: rmartinc (cherry picked from commit aad19cac6d6a67fdd65885899acd0c96675280d7) --- .../core/ResteasyReactiveRequestContext.java | 41 ++++++++----------- .../LocatableResourcePathParamExtractor.java | 2 +- .../core/parameters/PathParamExtractor.java | 14 ++++--- .../reactive/server/jaxrs/UriInfoImpl.java | 2 +- .../server/mapping/RequestMapper.java | 4 +- .../vertx/test/matching/RegexMatchTest.java | 4 ++ 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index dedaf7dd127cd..2248a8f7763ac 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -226,18 +226,20 @@ public void setMaxPathParams(int maxPathParams) { } } - public String getPathParam(int index) { - return doGetPathParam(index, pathParamValues); + public String getPathParam(int index, boolean encoded) { + return doGetPathParam(index, pathParamValues, encoded); } - private String doGetPathParam(int index, Object pathParamValues) { + private String doGetPathParam(int index, Object pathParamValues, boolean encoded) { if (pathParamValues instanceof String[]) { - return ((String[]) pathParamValues)[index]; + String pathParam = ((String[]) pathParamValues)[index]; + return encoded ? pathParam : Encode.decodePath(pathParam); } if (index > 1) { throw new IndexOutOfBoundsException(); } - return (String) pathParamValues; + String pathParam = (String) pathParamValues; + return encoded ? pathParam : Encode.decodePath(pathParam); } public ResteasyReactiveRequestContext setPathParamValue(int index, String value) { @@ -926,18 +928,11 @@ public String getPathParameter(String name, boolean encoded) { Integer index = target.getPathParameterIndexes().get(name); String value; if (index != null) { - value = getPathParam(index); - } else { - // Check previous resources if the path is not defined in the current target - value = getResourceLocatorPathParam(name); - } - - // It's possible to inject a path param that's not defined, return null in this case - if (encoded && value != null) { - return Encode.encodeQueryParam(value); + return getPathParam(index, encoded); } - return value; + // Check previous resources if the path is not defined in the current target + return getResourceLocatorPathParam(name, encoded); } @Override @@ -996,8 +991,8 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { public abstract Runnable registerTimer(long millis, Runnable task); - public String getResourceLocatorPathParam(String name) { - return getResourceLocatorPathParam(name, (PreviousResource) getProperty(PreviousResource.PROPERTY_KEY)); + public String getResourceLocatorPathParam(String name, boolean encoded) { + return getResourceLocatorPathParam(name, (PreviousResource) getProperty(PreviousResource.PROPERTY_KEY), encoded); } public FormData getFormData() { @@ -1009,7 +1004,7 @@ public ResteasyReactiveRequestContext setFormData(FormData formData) { return this; } - private String getResourceLocatorPathParam(String name, PreviousResource previousResource) { + private String getResourceLocatorPathParam(String name, PreviousResource previousResource, boolean encoded) { if (previousResource == null) { return null; } @@ -1020,13 +1015,13 @@ private String getResourceLocatorPathParam(String name, PreviousResource previou for (URITemplate.TemplateComponent component : classPath.components) { if (component.name != null) { if (component.name.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } index++; } else if (component.names != null) { for (String nm : component.names) { if (nm.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } } index++; @@ -1036,19 +1031,19 @@ private String getResourceLocatorPathParam(String name, PreviousResource previou for (URITemplate.TemplateComponent component : previousResource.locatorTarget.getPath().components) { if (component.name != null) { if (component.name.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } index++; } else if (component.names != null) { for (String nm : component.names) { if (nm.equals(name)) { - return doGetPathParam(index, previousResource.locatorPathParamValues); + return doGetPathParam(index, previousResource.locatorPathParamValues, encoded); } } index++; } } - return getResourceLocatorPathParam(name, previousResource.prev); + return getResourceLocatorPathParam(name, previousResource.prev, encoded); } public abstract boolean resumeExternalProcessing(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java index 835be0b515340..e478141b7c45c 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java @@ -12,7 +12,7 @@ public LocatableResourcePathParamExtractor(String name) { @Override public Object extractParameter(ResteasyReactiveRequestContext context) { - return context.getResourceLocatorPathParam(name); + return context.getResourceLocatorPathParam(name, false); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java index ccfa99edca5c3..d56f72b856bf5 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/PathParamExtractor.java @@ -1,6 +1,8 @@ package org.jboss.resteasy.reactive.server.core.parameters; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.jboss.resteasy.reactive.common.util.Encode; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; @@ -19,14 +21,14 @@ public PathParamExtractor(int index, boolean encoded, boolean single) { @Override public Object extractParameter(ResteasyReactiveRequestContext context) { - String pathParam = context.getPathParam(index); - if (encoded) { - pathParam = Encode.encodeQueryParam(pathParam); - } + String pathParam = context.getPathParam(index, true); if (single) { - return pathParam; + return encoded ? pathParam : Encode.decodePath(pathParam); } else { - return List.of(pathParam.split("/")); + return encoded + ? List.of(pathParam.split("/")) + : Arrays.stream(pathParam.split("/")).map(Encode::decodePath) + .collect(Collectors.toList()); } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java index 7cbd13cb272a6..987bbf2838113 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/UriInfoImpl.java @@ -152,7 +152,7 @@ public MultivaluedMap getPathParameters(boolean decode) { RuntimeResource target = currentRequest.getTarget(); if (target != null) { // a target can be null if this happens in a filter that runs before the target is set for (Entry pathParam : target.getPathParameterIndexes().entrySet()) { - pathParams.add(pathParam.getKey(), currentRequest.getPathParam(pathParam.getValue())); + pathParams.add(pathParam.getKey(), currentRequest.getPathParam(pathParam.getValue(), false)); } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java index 5d8d5fc931191..7fd7cb535f518 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java @@ -8,8 +8,6 @@ import java.util.Map; import java.util.regex.Matcher; -import org.jboss.resteasy.reactive.common.util.URIDecoder; - public class RequestMapper { private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -111,7 +109,7 @@ private RequestMatch mapFromPathMatcher(String path, PathMatcher.PathMatch Date: Tue, 5 Dec 2023 17:36:12 +0200 Subject: [PATCH 19/29] Fix != expression in @PreAuthorize check Fixes: #37526 (cherry picked from commit 4616d52b43d44b86c373bcfaf3e6d75ce4190ea1) --- .../deployment/SpringSecurityProcessor.java | 8 ++++++- .../deployment/SpringPreAuthorizeTest.java | 10 ++++++++ .../deployment/springapp/SpringComponent.java | 5 ++++ .../interceptor/SpringSecurityRecorder.java | 5 ++-- ...lNameFromParameterObjectSecurityCheck.java | 23 +++++++++++++++---- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java index 70d16090e0090..d5d70eb2fbace 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java @@ -48,6 +48,7 @@ import io.quarkus.spring.security.runtime.interceptor.SpringPreauthorizeInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecuredInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecurityRecorder; +import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterObjectSecurityCheck; import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterSecurityCheck; class SpringSecurityProcessor { @@ -466,13 +467,18 @@ void addSpringPreAuthorizeSecurityCheck(CombinedIndexBuildItem index, propertyName, index.getIndex(), part); + PrincipalNameFromParameterObjectSecurityCheck.CheckType checkType = part.contains("==") + ? PrincipalNameFromParameterObjectSecurityCheck.CheckType.EQ + : PrincipalNameFromParameterObjectSecurityCheck.CheckType.NEQ; + securityChecks.add(springSecurityRecorder.principalNameFromParameterObjectSecurityCheck( parameterNameAndIndex.getIndex(), stringPropertyAccessorData.getMatchingParameterClassInfo().name().toString(), StringPropertyAccessorGenerator .getAccessorClassName( stringPropertyAccessorData.getMatchingParameterClassInfo().name()), - stringPropertyAccessorData.getMatchingParameterFieldInfo().name())); + stringPropertyAccessorData.getMatchingParameterFieldInfo().name(), + checkType)); } } else if (part.matches(SpringSecurityProcessorUtil.BASIC_BEAN_METHOD_INVOCATION_REGEX)) { diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java index eaab4280b3647..c7dc3deff1f77 100644 --- a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java @@ -116,6 +116,16 @@ public void testPrincipalNameFromObject() { assertSuccess(() -> springComponent.principalNameFromObject(new Person("user")), "user", USER); } + @Test + public void testPrincipalNameFromObjectIsNot() { + assertFailureFor(() -> springComponent.principalNameFromObjectIsNot(new Person("whatever")), + UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> springComponent.principalNameFromObjectIsNot(new Person("whatever")), "whatever", USER); + assertFailureFor(() -> springComponent.principalNameFromObjectIsNot(new Person("user")), ForbiddenException.class, + USER); + } + @Test public void testNotSecured() { assertSuccess(() -> springComponent.notSecured(), "notSecured", ANONYMOUS); diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java index 0d86e8406fcf3..68a8af6552c6d 100644 --- a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java @@ -37,6 +37,11 @@ public String principalNameFromObject(Person person) { return person.getName(); } + @PreAuthorize("#person.name != authentication.principal.username") + public String principalNameFromObjectIsNot(Person person) { + return person.getName(); + } + public String notSecured() { return "notSecured"; } diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java index 8101bdd0991b6..591c08d8bd892 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringSecurityRecorder.java @@ -70,8 +70,9 @@ public SecurityCheck fromGeneratedClass(String generatedClassName) { } public SecurityCheck principalNameFromParameterObjectSecurityCheck(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) { + String stringPropertyAccessorClass, String propertyName, + PrincipalNameFromParameterObjectSecurityCheck.CheckType checkType) { return PrincipalNameFromParameterObjectSecurityCheck.of(index, expectedParameterClass, stringPropertyAccessorClass, - propertyName); + propertyName, checkType); } } diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java index c34e97b6d5f7b..63da575e48187 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java @@ -23,22 +23,24 @@ public class PrincipalNameFromParameterObjectSecurityCheck implements SecurityCh private final Class expectedParameterClass; private final Class stringPropertyAccessorClass; private final String propertyName; + private final CheckType checkType; private PrincipalNameFromParameterObjectSecurityCheck(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) throws ClassNotFoundException { + String stringPropertyAccessorClass, String propertyName, CheckType checkType) throws ClassNotFoundException { this.index = index; this.expectedParameterClass = Class.forName(expectedParameterClass, false, Thread.currentThread().getContextClassLoader()); this.stringPropertyAccessorClass = (Class) Class.forName(stringPropertyAccessorClass, false, Thread.currentThread().getContextClassLoader()); this.propertyName = propertyName; + this.checkType = checkType; } public static PrincipalNameFromParameterObjectSecurityCheck of(int index, String expectedParameterClass, - String stringPropertyAccessorClass, String propertyName) { + String stringPropertyAccessorClass, String propertyName, CheckType checkType) { try { return new PrincipalNameFromParameterObjectSecurityCheck(index, expectedParameterClass, stringPropertyAccessorClass, - propertyName); + propertyName, checkType); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } @@ -70,8 +72,14 @@ private void doApply(SecurityIdentity identity, Object[] parameters, String clas } String name = identity.getPrincipal().getName(); - if (!name.equals(parameterValueStr)) { - throw new ForbiddenException(); + if (checkType == CheckType.EQ) { + if (!name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } + } else if (checkType == CheckType.NEQ) { + if (name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } } } @@ -84,4 +92,9 @@ private IllegalStateException genericNotApplicableException(String className, St "PrincipalNameFromParameterObjectSecurityCheck with index " + index + " cannot be applied to '" + className + "#" + methodName + "'"); } + + public enum CheckType { + EQ, + NEQ + } } From ce86db855d8db9188c9b9badbdc02eb2264abaca Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 6 Dec 2023 10:02:05 +0100 Subject: [PATCH 20/29] Make docs/sync-web-site.sh recoverable (cherry picked from commit 763de410a20496b3669ffa9821cacafd158e4c6a) --- docs/sync-web-site.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index 4d0c834a5a3e0..e7056a41fa186 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -32,6 +32,7 @@ fi if [ -z $TARGET_DIR ]; then TARGET_DIR=target/web-site + rm -rf ${TARGET_DIR} GIT_OPTIONS="" if [[ "$QUARKUS_WEB_SITE_PUSH" != "true" ]]; then GIT_OPTIONS="--depth=1" From f522a8f35b2681de7c6700261f081b6399c3e494 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 7 Dec 2023 14:43:08 +0100 Subject: [PATCH 21/29] Support using commas to add and remove extensions with CLI Actually, it was already supported when creating projects, just not when adding/removing extensions. Fixes #37564 (cherry picked from commit 09d2c52bcde6959d2a201f0f945451973099f459) --- .../io/quarkus/cli/ProjectExtensionsAdd.java | 2 +- .../quarkus/cli/ProjectExtensionsRemove.java | 2 +- .../test/java/io/quarkus/cli/CliDriver.java | 50 ++++++++++++++++- .../io/quarkus/cli/CliProjectMavenTest.java | 2 + .../src/main/resources/fake-catalog.json | 56 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java index 3b1266ecdae52..4d3e1572d771c 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsAdd.java @@ -17,7 +17,7 @@ public class ProjectExtensionsAdd extends BaseBuildCommand implements Callable extensions; @Override diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java index adf82223bccfc..2affcc921bf42 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsRemove.java @@ -17,7 +17,7 @@ public class ProjectExtensionsRemove extends BaseBuildCommand implements Callabl @CommandLine.Mixin RunModeOption runMode; - @CommandLine.Parameters(arity = "1", paramLabel = "EXTENSION", description = "Extension(s) to remove from this project.") + @CommandLine.Parameters(arity = "1", paramLabel = "EXTENSION", description = "Extension(s) to remove from this project.", split = ",") Set extensions; @Override diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java index 6bbbc63a26463..60af7b5aabb94 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java @@ -319,7 +319,7 @@ public static Result invokeExtensionRemoveQute(Path projectRoot, Path file) thro } public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) throws Exception { - // add the qute extension + // add amazon-lambda-http and jackson extensions Result result = execute(projectRoot, "extension", "add", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -345,7 +345,7 @@ public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) thr } public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) throws Exception { - // add the qute extension + // remove amazon-lambda-http and jackson extensions Result result = execute(projectRoot, "extension", "remove", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -370,6 +370,52 @@ public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) return result; } + public static Result invokeExtensionAddMultipleCommas(Path projectRoot, Path file) throws Exception { + Result result = execute(projectRoot, "extension", "add", + "quarkus-resteasy-reactive-jsonb,quarkus-resteasy-reactive-jackson", "-e", "-B", "--verbose"); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code. Result:\n" + result); + + result = invokeValidateExtensionList(projectRoot); + Assertions.assertTrue(result.stdout.contains("quarkus-qute"), + "Expected quarkus-qute to be in the list of extensions. Result:\n" + result); + Assertions.assertTrue(result.stdout.contains("quarkus-resteasy-reactive-jsonb"), + "Expected quarkus-resteasy-reactive-jsonb to be in the list of extensions. Result:\n" + result); + Assertions.assertTrue(result.stdout.contains("quarkus-resteasy-reactive-jackson"), + "Expected quarkus-resteasy-reactive-jackson to be in the list of extensions. Result:\n" + result); + + String content = CliDriver.readFileAsString(file); + Assertions.assertTrue(content.contains("quarkus-qute"), + "quarkus-qute should still be listed as a dependency. Result:\n" + content); + Assertions.assertTrue(content.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should be listed as a dependency. Result:\n" + content); + Assertions.assertTrue(content.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should be listed as a dependency. Result:\n" + content); + + return result; + } + + public static Result invokeExtensionRemoveMultipleCommas(Path projectRoot, Path file) throws Exception { + Result result = execute(projectRoot, "extension", "remove", + "quarkus-resteasy-reactive-jsonb,quarkus-resteasy-reactive-jackson", "-e", "-B", "--verbose"); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code. Result:\n" + result); + + result = invokeValidateExtensionList(projectRoot); + Assertions.assertFalse(result.stdout.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should not be in the list of extensions. Result:\n" + result); + Assertions.assertFalse(result.stdout.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should not be in the list of extensions. Result:\n" + result); + + String content = CliDriver.readFileAsString(file); + Assertions.assertFalse(content.contains("quarkus-resteasy-reactive-jsonb"), + "quarkus-resteasy-reactive-jsonb should not be listed as a dependency. Result:\n" + content); + Assertions.assertFalse(content.contains("quarkus-resteasy-reactive-jackson"), + "quarkus-resteasy-reactive-jackson should not be listed as a dependency. Result:\n" + content); + + return result; + } + public static Result invokeExtensionListInstallable(Path projectRoot) throws Exception { Result result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java index 4d74100b21eaa..1ceddb4909bf9 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java @@ -131,8 +131,10 @@ public void testExtensionList() throws Exception { CliDriver.invokeExtensionAddRedundantQute(project); CliDriver.invokeExtensionListInstallable(project); CliDriver.invokeExtensionAddMultiple(project, pom); + CliDriver.invokeExtensionAddMultipleCommas(project, pom); CliDriver.invokeExtensionRemoveQute(project, pom); CliDriver.invokeExtensionRemoveMultiple(project, pom); + CliDriver.invokeExtensionRemoveMultipleCommas(project, pom); CliDriver.invokeExtensionListInstallableSearch(project); CliDriver.invokeExtensionListFormatting(project); diff --git a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json index d5a2a64e17411..e0ffdfe8e81e6 100644 --- a/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json +++ b/independent-projects/tools/devtools-testing/src/main/resources/fake-catalog.json @@ -75,6 +75,62 @@ "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" ] }, + { + "name" : "RESTEasy Reactive Jackson", + "description" : "Jackson serialization support for RESTEasy Reactive. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it", + "metadata" : { + "codestart" : { + "name" : "resteasy-reactive", + "kind" : "core", + "languages" : [ "java", "kotlin", "scala" ], + "artifact" : "io.quarkus:quarkus-project-core-extension-codestarts::jar:999-FAKE" + }, + "minimum-java-version" : "11", + "status" : "stable", + "config" : [ "quarkus.resteasy-reactive.", "quarkus.jackson." ], + "built-with-quarkus-core" : "999-FAKE", + "scm-url" : "https://github.com/quarkus-release/release", + "short-name" : "resteasy-reactive-jackson", + "capabilities" : { + "provides" : [ "io.quarkus.rest.jackson", "io.quarkus.resteasy.reactive.json.jackson" ] + }, + "categories" : [ "web", "reactive" ], + "extension-dependencies" : [ "io.quarkus:quarkus-resteasy-reactive", "io.quarkus:quarkus-resteasy-reactive-common", "io.quarkus:quarkus-mutiny", "io.quarkus:quarkus-smallrye-context-propagation", "io.quarkus:quarkus-vertx", "io.quarkus:quarkus-netty", "io.quarkus:quarkus-vertx-http", "io.quarkus:quarkus-core", "io.quarkus:quarkus-jsonp", "io.quarkus:quarkus-virtual-threads", "io.quarkus:quarkus-arc", "io.quarkus:quarkus-resteasy-reactive-jackson-common", "io.quarkus:quarkus-jackson" ], + "keywords" : [ "rest-jackson", "quarkus-resteasy-reactive-json", "jaxrs-json", "rest", "jaxrs", "json", "jackson", "jakarta-rest" ] + }, + "artifact" : "io.quarkus:quarkus-resteasy-reactive-jackson::jar:999-FAKE", + "origins": [ + "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" + ] + }, + { + "name" : "RESTEasy Reactive JSON-B", + "description" : "JSON-B serialization support for RESTEasy Reactive. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.", + "metadata" : { + "codestart" : { + "name" : "resteasy-reactive", + "kind" : "core", + "languages" : [ "java", "kotlin", "scala" ], + "artifact" : "io.quarkus:quarkus-project-core-extension-codestarts::jar:999-FAKE" + }, + "minimum-java-version" : "11", + "status" : "stable", + "config" : [ "quarkus.resteasy-reactive." ], + "built-with-quarkus-core" : "999-FAKE", + "scm-url" : "https://github.com/quarkus-release/release", + "short-name" : "resteasy-reactive-jsonb", + "capabilities" : { + "provides" : [ "io.quarkus.rest.jsonb", "io.quarkus.resteasy.reactive.json.jsonb" ] + }, + "categories" : [ "web", "reactive" ], + "extension-dependencies" : [ "io.quarkus:quarkus-resteasy-reactive", "io.quarkus:quarkus-resteasy-reactive-common", "io.quarkus:quarkus-mutiny", "io.quarkus:quarkus-smallrye-context-propagation", "io.quarkus:quarkus-vertx", "io.quarkus:quarkus-netty", "io.quarkus:quarkus-vertx-http", "io.quarkus:quarkus-core", "io.quarkus:quarkus-jsonp", "io.quarkus:quarkus-virtual-threads", "io.quarkus:quarkus-arc", "io.quarkus:quarkus-resteasy-reactive-jsonb-common", "io.quarkus:quarkus-jsonb" ], + "keywords" : [ "rest-jsonb", "resteasy-reactive-json", "jaxrs-json", "rest", "jaxrs", "json", "jsonb", "jakarta-rest" ] + }, + "artifact" : "io.quarkus:quarkus-resteasy-reactive-jsonb::jar:999-FAKE", + "origins": [ + "io.quarkus:quarkus-fake-bom:999-FAKE:json:999-FAKE" + ] + }, { "name": "YAML Configuration", "description": "Use YAML to configure your Quarkus application", From 0d97d00391fe7c6d3ad840777be513fced290bbd Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 13 Nov 2023 11:27:49 +0200 Subject: [PATCH 22/29] Reg. methods of RESTeasy reactive param. containers for reflection Fixes https://github.com/quarkusio/quarkus/issues/36986 (cherry picked from commit a69b1247d79005a45decf71a1950c7cce78fe969) --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 0cbc6e7668f68..d65c0b290aa17 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -286,7 +286,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, reflectiveClassBuildItemBuildProducer.produce(ReflectiveClassBuildItem .builder(scannedParameterContainers.stream().map(name -> name.toString()).collect(Collectors.toSet()) .toArray(new String[0])) - .fields().build()); + .methods().fields().build()); if (resourceScanningResultBuildItem.isEmpty() || resourceScanningResultBuildItem.get().getResult().getClientInterfaces().isEmpty()) { From 28eac006fa12e25e7da67b10d7a00cfc0fc0de00 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 13 Nov 2023 11:35:05 +0200 Subject: [PATCH 23/29] Refactor: Simplify class names array creation (cherry picked from commit 94cf51115110b4f7d4449c8c8105d9e16d88db5c) --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index d65c0b290aa17..22d8633adeb51 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -44,7 +44,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; -import java.util.stream.Collectors; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.RuntimeType; @@ -284,8 +283,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, scannedParameterContainers.addAll(parameterContainersBuildItem.getClassNames()); } reflectiveClassBuildItemBuildProducer.produce(ReflectiveClassBuildItem - .builder(scannedParameterContainers.stream().map(name -> name.toString()).collect(Collectors.toSet()) - .toArray(new String[0])) + .builder(scannedParameterContainers.stream().map(DotName::toString).distinct().toArray(String[]::new)) .methods().fields().build()); if (resourceScanningResultBuildItem.isEmpty() From dcbb238e11dafbd30c327784e3dba08dc8887700 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 12 Dec 2023 09:53:46 +0100 Subject: [PATCH 24/29] Use standard URL when updating the website (cherry picked from commit 75946f91ff5ec42734cc3b884345b5976cd5fb36) --- docs/sync-web-site.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sync-web-site.sh b/docs/sync-web-site.sh index e7056a41fa186..83dcbc41f5a78 100755 --- a/docs/sync-web-site.sh +++ b/docs/sync-web-site.sh @@ -38,7 +38,7 @@ if [ -z $TARGET_DIR ]; then GIT_OPTIONS="--depth=1" fi if [ -n "${RELEASE_GITHUB_TOKEN}" ]; then - git clone -b develop --single-branch $GIT_OPTIONS https://${RELEASE_GITHUB_TOKEN}:@github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} + git clone -b develop --single-branch $GIT_OPTIONS https://github.com/quarkusio/quarkusio.github.io.git ${TARGET_DIR} else git clone -b develop --single-branch $GIT_OPTIONS git@github.com:quarkusio/quarkusio.github.io.git ${TARGET_DIR} fi From 102ec7eb8f12ae8715e35d14fc5e999af2e9d7e9 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 2 Jan 2024 11:20:26 +0000 Subject: [PATCH 25/29] Fix Create the Maven project section in security-oidc-bearer-token-authentication-tutorial.adoc (cherry picked from commit 5545a2b081be6cd476341ea3d2a2f6b36b3f5003) --- ...-bearer-token-authentication-tutorial.adoc | 40 ++++++------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc index 8921ec8696429..063261c048e36 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication-tutorial.adoc @@ -59,51 +59,35 @@ The solution is located in the `security-openid-connect-quickstart` link:{quicks You can either create a new Maven project with the `oidc` extension or you can add the extension to an existing Maven project. Complete one of the following commands: -* To create a new Maven project, use the following command: -+ -==== +To create a new Maven project, use the following command: + :create-app-artifact-id: security-openid-connect-quickstart :create-app-extensions: oidc,resteasy-reactive-jackson include::{includes}/devtools/create-app.adoc[] -==== -This command generates a Maven project, importing the `oidc` extension -which is an implementation of OIDC for Quarkus. -* If you already have your Quarkus project configured, you can add the `oidc` extension -to your project by running the following command in your project base directory: -+ -==== +If you already have your Quarkus project configured, you can add the `oidc` extension to your project by running the following command in your project base directory: + :add-extension-extensions: oidc include::{includes}/devtools/extension-add.adoc[] -==== -The following configuration gets added to your build file: -* Using Maven (pom.xml): -+ -==== --- +This will add the following to your build file: + [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml ---- - io.quarkus - quarkus-oidc + io.quarkus + quarkus-oidc ---- --- -==== -+ -* Using Gradle (build.gradle): -+ -==== --- + [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle ---- implementation("io.quarkus:quarkus-oidc") ---- --- -==== -=== Write the application +== Write the application . Implement the `/api/users/me` endpoint as shown in the following example, which is a regular Jakarta REST resource: + From d50f48d89786806a80eb320f374495a1cdac2f43 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 2 Jan 2024 22:26:01 +0000 Subject: [PATCH 26/29] Do not expand config properties for Gradle Workers (cherry picked from commit 1aa4604aeeb6ad4f9a2c8de9067c7c742531bb35) --- .../io/quarkus/gradle/tasks/QuarkusBuildTask.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index 42a2ea257b487..51c468443586b 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -27,6 +27,7 @@ import io.quarkus.gradle.QuarkusPlugin; import io.quarkus.gradle.tasks.worker.BuildWorker; import io.quarkus.maven.dependency.GACTV; +import io.smallrye.config.Expressions; /** * Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildCacheableAppParts}, {@link QuarkusBuild} tasks @@ -205,12 +206,14 @@ void generateBuild() { ApplicationModel appModel = resolveAppModelForBuild(); Map configMap = new HashMap<>(); - for (Map.Entry entry : extension().buildEffectiveConfiguration(appModel.getAppArtifact()).configMap() - .entrySet()) { - if (entry.getKey().startsWith("quarkus.")) { - configMap.put(entry.getKey(), entry.getValue()); + EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); + Expressions.withoutExpansion(() -> { + for (Map.Entry entry : effectiveConfig.configMap().entrySet()) { + if (entry.getKey().startsWith("quarkus.")) { + configMap.put(entry.getKey(), effectiveConfig.config().getRawValue(entry.getKey())); + } } - } + }); getLogger().info("Starting Quarkus application build for package type {}", packageType); From a6c5b05b9234cb0035fa7f5d9001d6e8442d2d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Dec 2023 14:32:12 +0100 Subject: [PATCH 27/29] Test access to fields of @Immutable embeddables (cherry picked from commit 79e3b19ec13360b305fa6941516559e325faa3dd) --- .../ImmutableEmbeddableFieldAccessTest.java | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java new file mode 100644 index 0000000000000..123a97aa63ef5 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/applicationfieldaccess/ImmutableEmbeddableFieldAccessTest.java @@ -0,0 +1,193 @@ +package io.quarkus.hibernate.orm.applicationfieldaccess; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Immutable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Checks that access to record fields or record getters by the application works correctly. + */ +public class ImmutableEmbeddableFieldAccessTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(MyEntity.class) + .addClasses(MyImmutableEmbeddableWithFieldAccess.class) + .addClasses(MyImmutableEmbeddableWithAccessors.class) + .addClass(AccessDelegate.class)) + .withConfigurationResource("application.properties"); + + @Inject + EntityManager em; + + @Test + public void immutableEmbeddableWithoutAdditionalGetters_field() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithFieldAccess(); + embedded.value = value; + entity.embeddedWithoutAccessors = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithoutAccessors == null ? null : entity.embeddedWithoutAccessors.value; + } + }); + } + + @Test + public void immutableEmbeddableWithAdditionalGetters_field() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithAccessors(); + // Assuming this is changed only once on initialization, + // which is the only way the @Immutable annotation would make sense. + embedded.value = value; + entity.embeddedWithFieldAccess = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithFieldAccess == null ? null : entity.embeddedWithFieldAccess.value; + } + }); + } + + @Test + public void immutableEmbeddableWithAccessors() { + doTestFieldAccess(new AccessDelegate() { + @Override + public void setValue(MyEntity entity, Long value) { + var embedded = new MyImmutableEmbeddableWithAccessors(); + // Assuming this is changed only once on initialization, + // which is the only way the @Immutable annotation would make sense. + embedded.setValue(value); + entity.embeddedWithFieldAccess = embedded; + } + + @Override + public Long getValue(MyEntity entity) { + return entity.embeddedWithFieldAccess == null ? null : entity.embeddedWithFieldAccess.getValue(); + } + }); + } + + // Ideally we'd make this a @ParameterizedTest and pass the access delegate as parameter, + // but we cannot do that due to JUnit using a different classloader than the test. + private void doTestFieldAccess(AccessDelegate delegate) { + Long id = QuarkusTransaction.disallowingExisting().call(() -> { + var entity = new MyEntity(); + em.persist(entity); + return entity.id; + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.find(MyEntity.class, id); + assertThat(delegate.getValue(entity)) + .as("Loaded value before update") + .isNull(); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.getReference(MyEntity.class, id); + // Since field access is replaced with accessor calls, + // we expect this change to be detected by dirty tracking and persisted. + delegate.setValue(entity, 42L); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.find(MyEntity.class, id); + // We're working on an initialized entity. + assertThat(entity) + .as("find() should return uninitialized entity") + .returns(true, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + assertThat(delegate.getValue(entity)) + .as("Loaded value after update") + .isEqualTo(42L); + }); + + QuarkusTransaction.disallowingExisting().run(() -> { + var entity = em.getReference(MyEntity.class, id); + // We're working on an uninitialized entity. + assertThat(entity) + .as("getReference() should return uninitialized entity") + .returns(false, Hibernate::isInitialized); + // The above should have persisted a value that passes the assertion. + assertThat(delegate.getValue(entity)) + .as("Lazily loaded value after update") + .isEqualTo(42L); + // Accessing the value should trigger initialization of the entity. + assertThat(entity) + .as("Getting the value should initialize the entity") + .returns(true, Hibernate::isInitialized); + }); + } + + @Entity(name = "myentity") + public static class MyEntity { + @Id + @GeneratedValue + public long id; + @Embedded + @AttributeOverride(name = "value", column = @Column(name = "value1")) + public MyImmutableEmbeddableWithAccessors embeddedWithFieldAccess; + @Embedded + @AttributeOverride(name = "value", column = @Column(name = "value2")) + public MyImmutableEmbeddableWithFieldAccess embeddedWithoutAccessors; + } + + @Immutable + @Embeddable + public static class MyImmutableEmbeddableWithFieldAccess { + public Long value; + + public MyImmutableEmbeddableWithFieldAccess() { + } + } + + @Immutable + @Embeddable + public static class MyImmutableEmbeddableWithAccessors { + private Long value; + + // For Hibernate ORM instantiation + protected MyImmutableEmbeddableWithAccessors() { + } + + public Long getValue() { + return value; + } + + // For Hibernate ORM instantiation + protected void setValue(Long value) { + this.value = value; + } + } + + private interface AccessDelegate { + void setValue(MyEntity entity, Long value); + + Long getValue(MyEntity entity); + } +} From 5f266fae463934d7650676b1838f48e081b27147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 1 Dec 2023 14:41:10 +0100 Subject: [PATCH 28/29] Fix Panache bytecode enhancement for @Embeddable records (cherry picked from commit 1b426fc415832a026d0f3806f685f50034bf6359) --- .../deployment/PanacheHibernateCommonResourceProcessor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java b/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java index 93037abea56c9..6127afcc2955b 100644 --- a/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java +++ b/extensions/panache/panache-hibernate-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheHibernateCommonResourceProcessor.java @@ -157,9 +157,12 @@ private EntityModel createEntityModel(ClassInfo classInfo) { // so we need to be careful when we enhance private fields, // because the corresponding `$_hibernate_{read/write}_*()` methods // will only be generated for classes mapped through *annotations*. - boolean willBeEnhancedByHibernateOrm = classInfo.hasAnnotation(DOTNAME_ENTITY) + boolean isManaged = classInfo.hasAnnotation(DOTNAME_ENTITY) || classInfo.hasAnnotation(DOTNAME_MAPPED_SUPERCLASS) || classInfo.hasAnnotation(DOTNAME_EMBEDDABLE); + boolean willBeEnhancedByHibernateOrm = isManaged + // Records are immutable, thus never enhanced + && !classInfo.isRecord(); for (FieldInfo fieldInfo : classInfo.fields()) { String name = fieldInfo.name(); if (!Modifier.isStatic(fieldInfo.flags()) From 2e4648b1fe2d32249349414246b0f3dec6ff4662 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 7 Nov 2023 10:38:44 +0100 Subject: [PATCH 29/29] Avoid @TempDir in RestClientCDIDelegateBuilderTest Unfortunately, using @TempDir on Windows CI is not working very well and this test has been failing from time to time for a few days now. Applying the same trick as in https://github.com/quarkusio/quarkus/pull/31996 --- .../RestClientCDIDelegateBuilderTest.java | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index 7a60af0b2d842..95d36dd17fa4b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -1,9 +1,10 @@ package io.quarkus.rest.client.reactive.runtime; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -16,15 +17,12 @@ import jakarta.ws.rs.client.ClientResponseContext; import jakarta.ws.rs.client.ClientResponseFilter; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; @@ -37,33 +35,45 @@ public class RestClientCDIDelegateBuilderTest { private static final String TRUSTSTORE_PASSWORD = "truststorePassword"; private static final String KEYSTORE_PASSWORD = "keystorePassword"; - @TempDir - static File tempDir; - private static File truststoreFile; - private static File keystoreFile; - private static Config createdConfig; + private static Path truststorePath; + private static Path keystorePath; @BeforeAll public static void beforeAll() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { // prepare keystore and truststore - truststoreFile = new File(tempDir, "truststore.jks"); - keystoreFile = new File(tempDir, "keystore.jks"); + truststorePath = Files.createTempFile("truststore", ".jks"); - KeyStore truststore = KeyStore.getInstance("JKS"); - truststore.load(null, TRUSTSTORE_PASSWORD.toCharArray()); - truststore.store(new FileOutputStream(truststoreFile), TRUSTSTORE_PASSWORD.toCharArray()); + try (OutputStream truststoreOs = Files.newOutputStream(truststorePath)) { + KeyStore truststore = KeyStore.getInstance("JKS"); + truststore.load(null, TRUSTSTORE_PASSWORD.toCharArray()); + truststore.store(truststoreOs, TRUSTSTORE_PASSWORD.toCharArray()); + } + + keystorePath = Files.createTempFile("keystore", ".jks"); - KeyStore keystore = KeyStore.getInstance("JKS"); - keystore.load(null, KEYSTORE_PASSWORD.toCharArray()); - keystore.store(new FileOutputStream(keystoreFile), KEYSTORE_PASSWORD.toCharArray()); + try (OutputStream keystoreOs = Files.newOutputStream(keystorePath)) { + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(null, KEYSTORE_PASSWORD.toCharArray()); + keystore.store(keystoreOs, KEYSTORE_PASSWORD.toCharArray()); + } } - @AfterEach - public void afterEach() { - if (createdConfig != null) { - ConfigProviderResolver.instance().releaseConfig(createdConfig); - createdConfig = null; + @AfterAll + public static void afterAll() { + if (truststorePath != null) { + try { + Files.deleteIfExists(truststorePath); + } catch (IOException e) { + // ignore it + } + } + if (keystorePath != null) { + try { + Files.deleteIfExists(keystorePath); + } catch (IOException e) { + // ignore it + } } } @@ -178,10 +188,10 @@ private static RestClientsConfig createSampleConfigRoot() { .of("io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilderTest$MyResponseFilter2"); configRoot.queryParamStyle = Optional.of(QueryParamStyle.MULTI_PAIRS); - configRoot.trustStore = Optional.of(truststoreFile.getAbsolutePath()); + configRoot.trustStore = Optional.of(truststorePath.toAbsolutePath().toString()); configRoot.trustStorePassword = Optional.of("truststorePassword"); configRoot.trustStoreType = Optional.of("JKS"); - configRoot.keyStore = Optional.of(keystoreFile.getAbsolutePath()); + configRoot.keyStore = Optional.of(keystorePath.toAbsolutePath().toString()); configRoot.keyStorePassword = Optional.of("keystorePassword"); configRoot.keyStoreType = Optional.of("JKS"); @@ -215,10 +225,10 @@ private static RestClientConfig createSampleClientConfig() { .of("io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilderTest$MyResponseFilter1"); clientConfig.queryParamStyle = Optional.of(QueryParamStyle.COMMA_SEPARATED); - clientConfig.trustStore = Optional.of(truststoreFile.getAbsolutePath()); + clientConfig.trustStore = Optional.of(truststorePath.toAbsolutePath().toString()); clientConfig.trustStorePassword = Optional.of("truststorePassword"); clientConfig.trustStoreType = Optional.of("JKS"); - clientConfig.keyStore = Optional.of(keystoreFile.getAbsolutePath()); + clientConfig.keyStore = Optional.of(keystorePath.toAbsolutePath().toString()); clientConfig.keyStorePassword = Optional.of("keystorePassword"); clientConfig.keyStoreType = Optional.of("JKS");