From 96c4d2d41ed714033efa7ffd18a5f0b4343ddd71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 20 Dec 2023 14:50:59 +0100 Subject: [PATCH 01/24] RR client: fix chunking between \n\n in SSEParser #37625 Fixes #37625 (cherry picked from commit 5070e60b439def56b2dbaec27a96f77e3c419c86) --- .../deployment/test/sse/SseParserTest.java | 16 ++++++++++++++++ .../reactive/client/impl/SseParser.java | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) 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 36cca2e23779e..12c47625797e5 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 @@ -108,6 +108,22 @@ public void testParser() { testParser(Arrays.asList("data:f", "oo\n\n"), Collections.singletonList(new InboundSseEventImpl(null, null) .setData("foo"))); + testParser(Arrays.asList("dat", "a:foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data", ":foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data:", "foo\n\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + // chunk at the worst possible place, make sure we don't drop events + testParser(Arrays.asList("data:foo\n", "\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo"))); + testParser(Arrays.asList("data:foo\n", "data:bar\n", "\n"), + Collections.singletonList(new InboundSseEventImpl(null, null) + .setData("foo\nbar"))); // one event in two buffers within a UTF-8 char testParserWithBytes( Arrays.asList(new byte[] { 'd', 'a', 't', 'a', ':', (byte) 0b11000010 }, 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 46bb82858514a..45db7ae4ac6fb 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 @@ -40,7 +40,10 @@ public class SseParser implements Handler { * True if we're at the very beginning of the data stream and could see a BOM */ private boolean firstByte = true; - + /** + * True if we've started to read at least one byte of an event + */ + private boolean startedEvent = false; /** * The event type we're reading. Defaults to "message" and changes with "event" fields */ @@ -95,6 +98,7 @@ public void handle(Buffer event) { while (hasByte()) { boolean lastFirstByte = firstByte; + startedEvent = false; nameBuffer.setLength(0); valueBuffer.setLength(0); commentBuffer.setLength(0); @@ -105,10 +109,19 @@ public void handle(Buffer event) { eventReconnectTime = SseEvent.RECONNECT_NOT_SET; // SSE spec says ID is persistent + boolean needsMoreData = false; int lastEventStart = i; try { parseEvent(); + // if we started an event but did not fire it, it means we lacked a final end-of-line and must + // wait for more data + if (startedEvent) { + needsMoreData = true; + } } catch (NeedsMoreDataException x) { + needsMoreData = true; + } + if (needsMoreData) { // save the remaining bytes for later i = lastEventStart; // be ready to rescan the BOM, but only if we didn't already see it in a previous event @@ -133,8 +146,10 @@ private void parseEvent() { int c = readChar(); firstByte = false; if (c == COLON) { + startedEvent = true; parseComment(); } else if (isNameChar(c)) { + startedEvent = true; parseField(c); } else if (isEofWithSideEffect(c)) { dispatchEvent(); @@ -164,6 +179,8 @@ private void dispatchEvent() { event.setReconnectDelay(eventReconnectTime); event.setMediaType(contentType != null ? MediaType.valueOf(contentType) : null); sseEventSource.fireEvent(event); + // make sure we mark that we are done with this event + startedEvent = false; } private byte peekByte() { From 8e33f892abdf686d75cfe3f5145434195be44610 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 20 Dec 2023 18:12:22 -0600 Subject: [PATCH 02/24] Choose correct level field value and type to avoid non-resolution error Fixes #37598 (cherry picked from commit 9aee1dbd21c3e3dde74ef972cf5bc59b41388ab8) --- .../logging/LoggingResourceProcessor.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index 346e3adf46bd7..a1b3e84a7fd53 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -619,10 +619,23 @@ private static BiFunction generate } private static ResultHandle getLogManagerLevelIntValue(String levelName, BytecodeCreator method) { - final ResultHandle infoLevel = method.readStaticField( - FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class)); + FieldDescriptor fd; + switch (levelName) { + case "FATAL": + case "ERROR": + case "WARN": + case "INFO": + case "DEBUG": + case "TRACE": + fd = FieldDescriptor.of(org.jboss.logmanager.Level.class, levelName, org.jboss.logmanager.Level.class); + break; + default: + fd = FieldDescriptor.of(Level.class, levelName, Level.class); + break; + } + final ResultHandle levelVal = method.readStaticField(fd); return method - .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), infoLevel); + .invokeVirtualMethod(MethodDescriptor.ofMethod(Level.class, "intValue", int.class), levelVal); } private static void generateDefaultLoggingLogger(Level minLevel, ClassOutput output) { From 9bfa40d0c32ef16251a01c27aa2c116938aa0fb2 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 20 Dec 2023 07:50:26 -0600 Subject: [PATCH 03/24] Update `jboss-logmanager` to 3.0.4.Final Fixes #36919. Fixes #22844. (cherry picked from commit 47f0c586356ac6cfaa493a81fc255b946b7f78af) --- bom/application/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d699b3a03fe18..cedfd439909e8 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -164,7 +164,7 @@ 4.0.3 3.2.0 4.2.0 - 3.0.2.Final + 3.0.4.Final 9.22.3 3.0.3 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index ad9f2f590dadf..c4cc6b630e003 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -67,7 +67,7 @@ 1.0.1 2.8 1.2.6 - 3.0.2.Final + 3.0.4.Final 1.1.0.Final 1.7.36 23.1.0 From 4bbd759e9f6c6e90509502f799e68bf9e2daf93c Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 21 Dec 2023 08:39:38 -0600 Subject: [PATCH 04/24] Make sure all testing frameworks use `ExtHandler` This avoids the situation where `printf`-formatted messages are translated to "simple" messages due to #22844. (cherry picked from commit d6e8ae3b5e25baeca9e43e2645033f1044abac03) --- .../io/quarkus/vertx/mdc/InMemoryLogHandler.java | 4 ++-- independent-projects/resteasy-reactive/pom.xml | 13 +++++++++++++ .../resteasy-reactive/server/vertx/pom.xml | 6 ++++++ .../vertx/test/framework/InMemoryLogHandler.java | 5 +++-- .../java/io/quarkus/test/InMemoryLogHandler.java | 4 ++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java index ecc3d790b0529..2db16e1fa057f 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/mdc/InMemoryLogHandler.java @@ -5,12 +5,12 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.ErrorManager; import java.util.logging.Formatter; -import java.util.logging.Handler; import java.util.logging.LogRecord; +import org.jboss.logmanager.ExtHandler; import org.jboss.logmanager.formatters.PatternFormatter; -public class InMemoryLogHandler extends Handler { +public class InMemoryLogHandler extends ExtHandler { private static final PatternFormatter FORMATTER = new PatternFormatter("%X{requestId} ### %s"); private static final List recordList = new CopyOnWriteArrayList<>(); diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 5ce2d22175a19..f91324942e673 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -54,6 +54,7 @@ 3.9.6 3.24.2 3.5.3.Final + 3.0.4.Final 2.1.1 1.7.0 3.1.0 @@ -295,6 +296,18 @@ ${jboss-logging.version} + + org.jboss.logmanager + jboss-logmanager + ${jboss-logmanager.version} + + + * + * + + + + jakarta.annotation jakarta.annotation-api diff --git a/independent-projects/resteasy-reactive/server/vertx/pom.xml b/independent-projects/resteasy-reactive/server/vertx/pom.xml index 4e4ea50e5e650..6149a3d62dc5b 100644 --- a/independent-projects/resteasy-reactive/server/vertx/pom.xml +++ b/independent-projects/resteasy-reactive/server/vertx/pom.xml @@ -100,6 +100,12 @@ jboss-logging + + org.jboss.logmanager + jboss-logmanager + test + + diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java index b1dde96f45bc1..274570a6883c2 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/InMemoryLogHandler.java @@ -3,11 +3,12 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; -public class InMemoryLogHandler extends Handler { +import org.jboss.logmanager.ExtHandler; + +public class InMemoryLogHandler extends ExtHandler { private final Predicate predicate; diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java index 60eeb40d6f5ba..59f41cc339b4e 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/InMemoryLogHandler.java @@ -3,13 +3,13 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; -import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; +import org.jboss.logmanager.ExtHandler; import org.wildfly.common.Assert; -public class InMemoryLogHandler extends Handler { +public class InMemoryLogHandler extends ExtHandler { private final Predicate predicate; From 635aebc9ef0f0b1bc408a5a92c17d58c96f02c32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:02:02 +0000 Subject: [PATCH 05/24] Bump org.bouncycastle:bctls-fips from 1.0.17 to 1.0.18 Bumps org.bouncycastle:bctls-fips from 1.0.17 to 1.0.18. --- updated-dependencies: - dependency-name: org.bouncycastle:bctls-fips dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] (cherry picked from commit 02752964e97f46945c6cb90100591deae2758dda) --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index cedfd439909e8..74f2d63a0a2bf 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -17,7 +17,7 @@ 2.0.1 1.77 1.0.2.4 - 1.0.17 + 1.0.18 5.0.0 3.0.2 3.1.6 From 3390752707a1c4a220d349942fe64c6068fb9354 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 13 Dec 2023 22:55:09 +0000 Subject: [PATCH 06/24] Reset CSRF cookie to minimize a risk of failures due to its expiry (cherry picked from commit 5a95be2b559d2ccb320a3eb24a043ab857269fe1) --- .../reactive/runtime/CsrfReactiveConfig.java | 2 +- .../CsrfRequestResponseReactiveFilter.java | 22 +++++++++++-------- .../io/quarkus/it/csrf/CsrfReactiveTest.java | 12 +++++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java index e525994361f12..6d7717e0a8d3f 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfReactiveConfig.java @@ -34,7 +34,7 @@ public class CsrfReactiveConfig { /** * CSRF cookie max age. */ - @ConfigItem(defaultValue = "10M") + @ConfigItem(defaultValue = "2H") public Duration cookieMaxAge; /** diff --git a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java index b75764772a912..25df381ac7b59 100644 --- a/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java +++ b/extensions/csrf-reactive/runtime/src/main/java/io/quarkus/csrf/reactive/runtime/CsrfRequestResponseReactiveFilter.java @@ -30,6 +30,7 @@ public class CsrfRequestResponseReactiveFilter { */ private static final String CSRF_TOKEN_KEY = "csrf_token"; private static final String CSRF_TOKEN_BYTES_KEY = "csrf_token_bytes"; + private static final String NEW_COOKIE_REQUIRED = "true"; /** * CSRF token verification status. @@ -85,12 +86,14 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi if (requestMethodIsSafe(requestContext)) { // safe HTTP method, tolerate the absence of a token - if (cookieToken == null && isCsrfTokenRequired(routing, config)) { + if (isCsrfTokenRequired(routing, config)) { // Set the CSRF cookie with a randomly generated value byte[] tokenBytes = new byte[config.tokenSize]; secureRandom.nextBytes(tokenBytes); routing.put(CSRF_TOKEN_BYTES_KEY, tokenBytes); routing.put(CSRF_TOKEN_KEY, Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes)); + + routing.put(NEW_COOKIE_REQUIRED, true); } } else if (config.verifyToken) { // unsafe HTTP method, token is required @@ -129,7 +132,6 @@ public void filter(ResteasyReactiveContainerRequestContext requestContext, Routi String csrfTokenFormParam = (String) rrContext.getFormParameter(config.formFieldName, true, false); LOG.debugf("CSRF token found in the form parameter"); verifyCsrfToken(requestContext, routing, config, cookieToken, csrfTokenFormParam); - return; } else if (cookieToken == null) { LOG.debug("CSRF token is not found"); @@ -159,6 +161,8 @@ private void verifyCsrfToken(ResteasyReactiveContainerRequestContext requestCont } else { routing.put(CSRF_TOKEN_KEY, csrfToken); routing.put(CSRF_TOKEN_VERIFIED, true); + // reset the cookie + routing.put(NEW_COOKIE_REQUIRED, true); return; } } @@ -195,9 +199,9 @@ private static Response badClientRequest() { @ServerResponseFilter public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext, RoutingContext routing) { - final CsrfReactiveConfig config = configInstance.get(); - if (requestContext.getMethod().equals("GET") && isCsrfTokenRequired(routing, config) - && getCookieToken(routing, config) == null) { + if (routing.get(NEW_COOKIE_REQUIRED) != null) { + + final CsrfReactiveConfig config = configInstance.get(); String cookieValue = null; if (config.tokenSignatureKey.isPresent()) { @@ -230,7 +234,7 @@ && getCookieToken(routing, config) == null) { * * @return An Optional containing the token, or an empty Optional if the token cookie is not present or is invalid */ - private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) { + private static String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) { Cookie cookie = routing.getCookie(config.cookieName); if (cookie == null) { @@ -241,14 +245,14 @@ private String getCookieToken(RoutingContext routing, CsrfReactiveConfig config) return cookie.getValue(); } - private boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) { + private static boolean isCsrfTokenRequired(RoutingContext routing, CsrfReactiveConfig config) { return config.createTokenPath .map(value -> value.contains(routing.normalizedPath())).orElse(true); } - private void createCookie(String csrfToken, RoutingContext routing, CsrfReactiveConfig config) { + private static void createCookie(String cookieTokenValue, RoutingContext routing, CsrfReactiveConfig config) { - ServerCookie cookie = new CookieImpl(config.cookieName, csrfToken); + ServerCookie cookie = new CookieImpl(config.cookieName, cookieTokenValue); cookie.setHttpOnly(config.cookieHttpOnly); cookie.setSecure(config.cookieForceSecure || routing.request().isSSL()); cookie.setMaxAge(config.cookieMaxAge.toSeconds()); diff --git a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java index 770d36441e300..a8a937e1906a8 100644 --- a/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java +++ b/integration-tests/csrf-reactive/src/test/java/io/quarkus/it/csrf/CsrfReactiveTest.java @@ -41,6 +41,8 @@ public void testCsrfTokenInForm() throws Exception { try (final WebClient webClient = createWebClient()) { webClient.addRequestHeader("Authorization", basicAuth("alice", "alice")); HtmlPage htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm"); + htmlPage = webClient.getPage("http://localhost:8081/service/csrfTokenForm"); + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("CSRF Token Form Test", htmlPage.getTitleText()); @@ -51,11 +53,19 @@ public void testCsrfTokenInForm() throws Exception { assertNotNull(webClient.getCookieManager().getCookie("csrftoken")); TextPage textPage = loginForm.getInputByName("submit").click(); - + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); + // This request which returns String is not CSRF protected textPage = webClient.getPage("http://localhost:8081/service/hello"); assertEquals("hello", textPage.getContent()); + // therefore no Set-Cookie header is expected + assertNull(textPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); + + // Repeat a form submission + textPage = loginForm.getInputByName("submit").click(); + assertNotNull(htmlPage.getWebResponse().getResponseHeaderValue("Set-Cookie")); + assertEquals("alice:true:tokenHeaderIsSet=false", textPage.getContent()); webClient.getCookieManager().clearCookies(); } From ce5ec522d9579e24693ad21b73025e67c18eff24 Mon Sep 17 00:00:00 2001 From: Welton Rodrigo Torres Nascimento Date: Mon, 25 Dec 2023 15:15:41 -0300 Subject: [PATCH 07/24] Update getting-started-testing.adoc fix typo (cherry picked from commit b4baa1a0ff53c3b926fbcc690ded138d1d2cfd39) --- docs/src/main/asciidoc/getting-started-testing.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 31639cfb3af85..7b3537015ebaa 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1088,7 +1088,7 @@ or starting a mock HTTP server using https://wiremock.org/[Wiremock] (an example === Altering the test class -When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject the something into the test class, the `inject` methods can be used. +When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject something into the test class, the `inject` methods can be used. If for example you have a test like the following: [source,java] From cb9e118f9197b40800fae704a367177f6455ffbe Mon Sep 17 00:00:00 2001 From: Jakub Scholz Date: Thu, 28 Dec 2023 17:06:56 +0100 Subject: [PATCH 08/24] Update Strimzi container images in docs Signed-off-by: Jakub Scholz Signed-off-by: Jakub Scholz (cherry picked from commit bb89fe365ebc822b3b4de78858bfde5208087236) --- docs/src/main/asciidoc/kafka-dev-services.adoc | 2 +- .../src/main/asciidoc/kafka-reactive-getting-started.adoc | 4 ++-- docs/src/main/asciidoc/kafka-schema-registry-avro.adoc | 8 ++++---- docs/src/main/asciidoc/kafka-streams.adoc | 4 ++-- docs/src/main/asciidoc/kafka.adoc | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/main/asciidoc/kafka-dev-services.adoc b/docs/src/main/asciidoc/kafka-dev-services.adoc index a94da965a3a21..2c0a35c674c8c 100644 --- a/docs/src/main/asciidoc/kafka-dev-services.adoc +++ b/docs/src/main/asciidoc/kafka-dev-services.adoc @@ -82,7 +82,7 @@ For Strimzi, you can select any image with a Kafka version which has Kraft suppo [source, properties] ---- -quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.100.0-kafka-3.1.0 +quarkus.kafka.devservices.image-name=quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0 ---- == Configuring Kafka topics diff --git a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc index cb4c541a3c9c1..bce3607373361 100644 --- a/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc +++ b/docs/src/main/asciidoc/kafka-reactive-getting-started.adoc @@ -407,7 +407,7 @@ version: '3.5' services: zookeeper: - image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -420,7 +420,7 @@ services: - kafka-quickstart-network kafka: - image: quay.io/strimzi/kafka:0.23.0-kafka-2.8.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}" diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc index ca1b4c787a759..224a81fbe682e 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc @@ -324,7 +324,7 @@ version: '2' services: zookeeper: - image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -335,7 +335,7 @@ services: LOG_DIR: /tmp/logs kafka: - image: quay.io/strimzi/kafka:0.22.1-kafka-2.7.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT}" @@ -545,7 +545,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio io.strimzi strimzi-test-container - 0.22.1 + 0.105.0 test @@ -559,7 +559,7 @@ If we couldn't use Dev Services and wanted to start a Kafka broker and Apicurio [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- -testImplementation("io.strimzi:strimzi-test-container:0.22.1") { +testImplementation("io.strimzi:strimzi-test-container:0.105.0") { exclude group: "org.apache.logging.log4j", module: "log4j-core" } ---- diff --git a/docs/src/main/asciidoc/kafka-streams.adoc b/docs/src/main/asciidoc/kafka-streams.adoc index e5cc5fa70d8fa..28f940e4a9e3a 100644 --- a/docs/src/main/asciidoc/kafka-streams.adoc +++ b/docs/src/main/asciidoc/kafka-streams.adoc @@ -499,7 +499,7 @@ version: '3.5' services: zookeeper: - image: strimzi/kafka:0.19.0-kafka-2.5.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/zookeeper-server-start.sh config/zookeeper.properties" @@ -511,7 +511,7 @@ services: networks: - kafkastreams-network kafka: - image: strimzi/kafka:0.19.0-kafka-2.5.0 + image: quay.io/strimzi/kafka:0.39.0-kafka-3.6.1 command: [ "sh", "-c", "bin/kafka-server-start.sh config/server.properties --override listeners=$${KAFKA_LISTENERS} --override advertised.listeners=$${KAFKA_ADVERTISED_LISTENERS} --override zookeeper.connect=$${KAFKA_ZOOKEEPER_CONNECT} --override num.partitions=$${KAFKA_NUM_PARTITIONS}" diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index b559acfa4d85d..f672fbcb4d37c 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -2268,7 +2268,7 @@ The configuration of the created Kafka broker can be customized using `@Resource [source,java] ---- @QuarkusTestResource(value = KafkaCompanionResource.class, initArgs = { - @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi/kafka:0.28.0-kafka-3.0.0"), // Image name + @ResourceArg(name = "strimzi.kafka.image", value = "quay.io/strimzi-test-container/test-container:0.105.0-kafka-3.6.0"), // Image name @ResourceArg(name = "kafka.port", value = "9092"), // Fixed port for kafka, by default it will be exposed on a random port @ResourceArg(name = "kraft", value = "true"), // Enable Kraft mode @ResourceArg(name = "num.partitions", value = "3"), // Other custom broker configurations From a9bcc134d77d5d7f9e9b45c9122a7fd69abe5328 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 2 Jan 2024 22:26:01 +0000 Subject: [PATCH 09/24] 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 13c97cfd722a1..88196dbba173a 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 @@ -28,6 +28,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 @@ -207,12 +208,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 27fc3e505a6f487369b72502d5e2a0011089ed8b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 21 Dec 2023 16:20:42 +0100 Subject: [PATCH 10/24] Upgrade to Kotlin 1.9.22 (cherry picked from commit 1d648ece44517e97f513c0eac93fff30097ce480) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- .../src/test/java/io/quarkus/gradle/QuarkusPluginTest.java | 2 +- devtools/gradle/gradle/libs.versions.toml | 2 +- independent-projects/arc/pom.xml | 2 +- .../conditional-dependencies-kotlin/build.gradle.kts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 74f2d63a0a2bf..9156846eb8e85 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -157,7 +157,7 @@ 2.14.0 2.2.0 1.0.0 - 1.9.21 + 1.9.22 1.7.3 0.27.0 1.6.2 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ae8c28c5ea334..5124c1c267c1e 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -20,7 +20,7 @@ 3.11.0 - 1.9.21 + 1.9.22 1.9.10 2.13.8 4.8.1 diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 862ad0a62abb6..74f976e2b4b7a 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -105,7 +105,7 @@ public void shouldReturnMultipleOutputSourceDirectories() { @Test public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException { - var kotlinVersion = System.getProperty("kotlin_version", "1.9.21"); + var kotlinVersion = System.getProperty("kotlin_version", "1.9.22"); var settingFile = testProjectDir.resolve("settings.gradle.kts"); var mppProjectDir = testProjectDir.resolve("mpp"); var quarkusProjectDir = testProjectDir.resolve("quarkus"); diff --git a/devtools/gradle/gradle/libs.versions.toml b/devtools/gradle/gradle/libs.versions.toml index 5bb10bc0b84fc..a8f4fe2d5dfa3 100644 --- a/devtools/gradle/gradle/libs.versions.toml +++ b/devtools/gradle/gradle/libs.versions.toml @@ -2,7 +2,7 @@ plugin-publish = "1.2.1" # updating Kotlin here makes QuarkusPluginTest > shouldNotFailOnProjectDependenciesWithoutMain(Path) fail -kotlin = "1.9.21" +kotlin = "1.9.22" smallrye-config = "3.4.4" junit5 = "5.10.1" diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 5cf3128c08d0c..9e02257fdec6c 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -55,7 +55,7 @@ 3.24.2 5.10.1 - 1.9.21 + 1.9.22 1.7.3 5.7.0 diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts index 5e3808937ce3e..5e4ee3b8fe2e4 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies-kotlin/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.9.21" - kotlin("plugin.allopen") version "1.9.21" + kotlin("jvm") version "1.9.22" + kotlin("plugin.allopen") version "1.9.22" id("io.quarkus") } From 7430ec6cd7be35f0fd4176b8d9ad0954b41374a6 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 12:43:03 +0200 Subject: [PATCH 11/24] Mention exit handler parameter variant of Quarkus.run Fixes: #37929 (cherry picked from commit 4f239039bb297cb07ee063888bf5722d07d9e987) --- docs/src/main/asciidoc/lifecycle.adoc | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/src/main/asciidoc/lifecycle.adoc b/docs/src/main/asciidoc/lifecycle.adoc index 911f10ff02f8b..a528863ac6bf0 100644 --- a/docs/src/main/asciidoc/lifecycle.adoc +++ b/docs/src/main/asciidoc/lifecycle.adoc @@ -111,6 +111,42 @@ public class Main { } ---- +[TIP] +==== +`Quarkus.run` also provides a version that allows the code to handle errors. +For example: + +[source,java] +---- +package com.acme; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; + +@QuarkusMain +public class Main { + public static void main(String... args) { + Quarkus.run(MyApp.class, + (exitCode, exception) -> { + // do whatever + }, + args); + } + + public static class MyApp implements QuarkusApplication { + + @Override + public int run(String... args) throws Exception { + System.out.println("Do startup logic here"); + Quarkus.waitForExit(); + return 0; + } + } +} +---- +==== + === Injecting the command line arguments It is possible to inject the arguments that were passed in on the command line: From 577969527f8cc0628370922426615930d697195b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 10:20:23 +0200 Subject: [PATCH 12/24] Register JsonSubTypes.Type values for native mode Fixes: #37942 (cherry picked from commit a110cea7bb88d0b934f7717d9cfa8fe7b95f004c) --- .../jackson/deployment/JacksonProcessor.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index dc7b382a921a4..d120cea847a9e 100644 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -25,6 +25,7 @@ import org.jboss.jandex.Type; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.SimpleObjectIdResolver; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; @@ -79,7 +80,7 @@ public class JacksonProcessor { private static final DotName JSON_AUTO_DETECT = DotName.createSimple(JsonAutoDetect.class.getName()); private static final DotName JSON_TYPE_ID_RESOLVER = DotName.createSimple(JsonTypeIdResolver.class.getName()); - + private static final DotName JSON_SUBTYPES = DotName.createSimple(JsonSubTypes.class.getName()); private static final DotName JSON_CREATOR = DotName.createSimple("com.fasterxml.jackson.annotation.JsonCreator"); private static final DotName JSON_NAMING = DotName.createSimple("com.fasterxml.jackson.databind.annotation.JsonNaming"); @@ -98,6 +99,7 @@ public class JacksonProcessor { // this list can probably be enriched with more modules private static final List MODULES_NAMES_TO_AUTO_REGISTER = Arrays.asList(TIME_MODULE, JDK8_MODULE, PARAMETER_NAMES_MODULE); + private static final String[] EMPTY_STRING = new String[0]; @Inject CombinedIndexBuildItem combinedIndexBuildItem; @@ -266,6 +268,25 @@ void register( } } + // register @JsonSubTypes.Type values for reflection + Set subTypeTypesNames = new HashSet<>(); + for (AnnotationInstance subTypeInstance : index.getAnnotations(JSON_SUBTYPES)) { + AnnotationValue subTypeValue = subTypeInstance.value(); + if (subTypeValue != null) { + for (AnnotationInstance subTypeTypeInstance : subTypeValue.asNestedArray()) { + AnnotationValue subTypeTypeValue = subTypeTypeInstance.value(); + if (subTypeTypeValue != null) { + subTypeTypesNames.add(subTypeTypeValue.asClass().name().toString()); + } + } + + } + } + if (!subTypeTypesNames.isEmpty()) { + reflectiveClass.produce(ReflectiveClassBuildItem.builder(subTypeTypesNames.toArray(EMPTY_STRING)) + .methods().fields().build()); + } + // this needs to be registered manually since the runtime module is not indexed by Jandex additionalBeans.produce(new AdditionalBeanBuildItem(ObjectMapperProducer.class)); } From 08c3e9a4a643b1de3bf4383c13920ab328e34035 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 13:03:28 +0200 Subject: [PATCH 13/24] Add native test for verifying JsonSubTypes.Type handling (cherry picked from commit d7c765ca12e88ce02111efade1644d16e00cca4b) --- .../it/jackson/JsonSubTypesResource.java | 29 +++++++++++++++++++ .../io/quarkus/it/jackson/model/Elephant.java | 20 +++++++++++++ .../io/quarkus/it/jackson/model/Mammal.java | 15 ++++++++++ .../it/jackson/model/MammalFamily.java | 19 ++++++++++++ .../io/quarkus/it/jackson/model/Whale.java | 20 +++++++++++++ .../it/jackson/JsonSubTypesResourceIT.java | 7 +++++ .../it/jackson/JsonSubTypesResourceTest.java | 27 +++++++++++++++++ 7 files changed, 137 insertions(+) create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java create mode 100644 integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java create mode 100644 integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java create mode 100644 integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java new file mode 100644 index 0000000000000..ebffa6018819c --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/JsonSubTypesResource.java @@ -0,0 +1,29 @@ +package io.quarkus.it.jackson; + +import java.util.List; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.it.jackson.model.Elephant; +import io.quarkus.it.jackson.model.MammalFamily; +import io.quarkus.it.jackson.model.Whale; + +@Path("jsonSubTypes") +public class JsonSubTypesResource { + + private final ObjectMapper objectMapper; + + public JsonSubTypesResource(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @GET + public String test() throws JsonProcessingException { + return objectMapper.writeValueAsString(new MammalFamily(List.of( + new Whale(30.0, "white"), new Elephant(10, "africa")))); + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java new file mode 100644 index 0000000000000..4fea8838dc3b7 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Elephant.java @@ -0,0 +1,20 @@ +package io.quarkus.it.jackson.model; + +public class Elephant implements Mammal { + + private final int hornLength; + private final String continent; + + public Elephant(int hornLength, String continent) { + this.hornLength = hornLength; + this.continent = continent; + } + + public int getHornLength() { + return hornLength; + } + + public String getContinent() { + return continent; + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java new file mode 100644 index 0000000000000..dc622346349f2 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Mammal.java @@ -0,0 +1,15 @@ +package io.quarkus.it.jackson.model; + +import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY; +import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo(use = NAME, include = PROPERTY) +@JsonSubTypes({ + @JsonSubTypes.Type(value = Elephant.class, name = "Elephant"), + @JsonSubTypes.Type(value = Whale.class, name = "Whale"), +}) +public interface Mammal { +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java new file mode 100644 index 0000000000000..b35e13d143ddc --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/MammalFamily.java @@ -0,0 +1,19 @@ +package io.quarkus.it.jackson.model; + +import java.util.Collection; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public class MammalFamily { + + private final Collection mammals; + + public MammalFamily(Collection mammals) { + this.mammals = mammals; + } + + public Collection getMammals() { + return mammals; + } +} diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java new file mode 100644 index 0000000000000..de7d22dfe2c64 --- /dev/null +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/Whale.java @@ -0,0 +1,20 @@ +package io.quarkus.it.jackson.model; + +public class Whale implements Mammal { + + private final double swimSpeed; + private final String color; + + public Whale(double swimSpeed, String color) { + this.swimSpeed = swimSpeed; + this.color = color; + } + + public double getSwimSpeed() { + return swimSpeed; + } + + public String getColor() { + return color; + } +} diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java new file mode 100644 index 0000000000000..9d58d36a156f4 --- /dev/null +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.jackson; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class JsonSubTypesResourceIT extends JsonSubTypesResourceTest { +} diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java new file mode 100644 index 0000000000000..fe1979b72db7a --- /dev/null +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/JsonSubTypesResourceTest.java @@ -0,0 +1,27 @@ +package io.quarkus.it.jackson; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import io.restassured.response.Response; + +@QuarkusTest +class JsonSubTypesResourceTest { + + @Test + void test() { + Response response = given() + .accept(ContentType.JSON) + .when() + .get("jsonSubTypes") + .then() + .statusCode(200) + .extract().response(); + assertThat(response.jsonPath().getString("mammals[0].color")).isEqualTo("white"); + assertThat(response.jsonPath().getString("mammals[1].continent")).isEqualTo("africa"); + } +} From 6d2b36e810c02c276ec4e2ed9ba87726f4edc731 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 15:01:22 +0200 Subject: [PATCH 14/24] Fix the status is a couple extensions documentation pages Closes: #37941 (cherry picked from commit 13662011f190c1de6e736000e52360839d3bb82d) --- docs/src/main/asciidoc/cache.adoc | 2 -- docs/src/main/asciidoc/security-oauth2.adoc | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs/src/main/asciidoc/cache.adoc b/docs/src/main/asciidoc/cache.adoc index 2b711ed7ff187..10f397041d653 100644 --- a/docs/src/main/asciidoc/cache.adoc +++ b/docs/src/main/asciidoc/cache.adoc @@ -12,8 +12,6 @@ include::_attributes.adoc[] In this guide, you will learn how to enable application data caching in any CDI managed bean of your Quarkus application. -include::{includes}/extension-status.adoc[] - == Prerequisites include::{includes}/prerequisites.adoc[] diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index 7720edb8bb238..cf216a14c8225 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -22,8 +22,6 @@ This extension provides a light-weight support for using the opaque Bearer Token If the OAuth2 Authentication server provides JWT Bearer Tokens, consider using either xref:security-oidc-bearer-token-authentication.adoc[OIDC Bearer token authentication] or xref:security-jwt.adoc[SmallRye JWT] extensions instead. OpenID Connect extension has to be used if the Quarkus application needs to authenticate the users using OIDC Authorization Code Flow. For more information, see the xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications] guide. -include::{includes}/extension-status.adoc[] - == Solution We recommend that you follow the instructions in the next sections and create the application step by step. From 9c042cf57517eed72586aad9077deafba2f2c5ba Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 3 Jan 2024 12:22:04 +0200 Subject: [PATCH 15/24] Don't fail if config is not a directory Fixes: #37903 (cherry picked from commit b364fe9e0dad4c74b3a735d93cb47d3ff469b6e6) --- .../io/quarkus/runtime/configuration/ConfigDiagnostic.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java index 574ae74cf169e..d073ca201d7f9 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -6,6 +6,7 @@ import java.net.URI; import java.nio.file.DirectoryStream; import java.nio.file.Files; +import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; @@ -186,6 +187,9 @@ public static Set configFiles(Path configFilesLocation) throws IOExcepti for (Path candidate : candidates) { configFiles.add(candidate.toUri().toURL().toString()); } + } catch (NotDirectoryException ignored) { + log.debugf("File %s is not a directory", configFilesLocation.toAbsolutePath()); + return Collections.emptySet(); } return configFiles; } From 23ab911b4a3eb6542ff8ed03f0edb86f74fa4ae2 Mon Sep 17 00:00:00 2001 From: "rob.spoor" Date: Wed, 3 Jan 2024 16:00:38 +0100 Subject: [PATCH 16/24] Set the correct port properties for HTTPS (cherry picked from commit 5bd6a7947a1ac31f6aca711db3952f31eb8ea78e) --- .../http/runtime/PortSystemProperties.java | 38 +++++--- .../it/vertx/RandomTestPortTestCase.java | 86 +++++++++++++++++++ 2 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java index 40fa135373900..02d3a1bf5deb5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PortSystemProperties.java @@ -11,28 +11,38 @@ public class PortSystemProperties { public void set(String subProperty, int actualPort, LaunchMode launchMode) { String portPropertyValue = String.valueOf(actualPort); - //we always set the .port property, even if we are in test mode, so this will always - //reflect the current port String portPropertyName = "quarkus." + subProperty + ".port"; - String prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue); - if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) { - portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue); + String testPropName = "quarkus." + subProperty + ".test-port"; + + set(portPropertyName, testPropName, portPropertyValue, launchMode); + //if subProperty is "https", the correct properties are not quarkus.https.port and quarkus.https.test-port + //but quarkus.http.ssl-port and quarkus.http.test-ssl-port + //the incorrect properties are still set for backward compatibility with code that works around the incorrect + //names + if ("https".equals(subProperty)) { + set("quarkus.http.ssl-port", "quarkus.http.test-ssl-port", portPropertyValue, launchMode); } + } + + private void set(String portPropertyName, String testPropName, String portPropertyValue, LaunchMode launchMode) { + //we always set the .port property, even if we are in test mode, so this will always + //reflect the current port + set(portPropertyName, portPropertyValue); if (launchMode == LaunchMode.TEST) { //we also set the test-port property in a test - String testPropName = "quarkus." + subProperty + ".test-port"; - String prevTestPropPrevValue = System.setProperty(testPropName, portPropertyValue); - if (!Objects.equals(prevTestPropPrevValue, portPropertyValue)) { - portPropertiesToRestore.put(testPropName, prevTestPropPrevValue); - } + set(testPropName, portPropertyValue); } if (launchMode.isDevOrTest()) { // set the profile property as well to make sure we don't have any inconsistencies portPropertyName = "%" + launchMode.getDefaultProfile() + "." + portPropertyName; - prevPortPropertyValue = System.setProperty(portPropertyName, portPropertyValue); - if (!Objects.equals(prevPortPropertyValue, portPropertyValue)) { - portPropertiesToRestore.put(portPropertyName, prevPortPropertyValue); - } + set(portPropertyName, portPropertyValue); + } + } + + private void set(String propertyName, String propertyValue) { + String prevPropertyValue = System.setProperty(propertyName, propertyValue); + if (!Objects.equals(prevPropertyValue, propertyValue)) { + portPropertiesToRestore.put(propertyName, prevPropertyValue); } } diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java new file mode 100644 index 0000000000000..b8275f241d538 --- /dev/null +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/RandomTestPortTestCase.java @@ -0,0 +1,86 @@ +package io.quarkus.it.vertx; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.net.URL; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(RandomTestPortTestCase.TestPortProfile.class) +public class RandomTestPortTestCase { + + @ConfigProperty(name = "quarkus.http.port") + int httpPort; + + @ConfigProperty(name = "quarkus.http.test-port") + int httpTestPort; + + @TestHTTPResource(value = "/some-path") + URL httpTestUrl; + + @ConfigProperty(name = "quarkus.http.ssl-port") + int httpsPort; + + @ConfigProperty(name = "quarkus.http.test-ssl-port") + int httpsTestPort; + + @TestHTTPResource(value = "/some-path", ssl = true) + URL httpsTestUrl; + + @Test + public void testHttpTestPort() { + assertNotEquals(0, httpTestPort); + assertNotEquals(8080, httpTestPort); + assertNotEquals(8081, httpTestPort); + + assertEquals(httpTestPort, httpPort); + assertEquals(httpTestPort, httpTestUrl.getPort()); + } + + @Test + public void testHttpsTestPort() { + assertNotEquals(0, httpsTestPort); + assertNotEquals(8443, httpsTestPort); + assertNotEquals(8444, httpsTestPort); + + assertEquals(httpsTestPort, httpsPort); + assertEquals(httpsTestPort, httpsTestUrl.getPort()); + } + + @Test + public void testLegacyProperties() { + Config config = ConfigProvider.getConfig(); + + testLegacyProperty(config, "quarkus.http.ssl-port", "quarkus.https.port", httpsPort); + testLegacyProperty(config, "quarkus.http.test-ssl-port", "quarkus.https.test-port", httpsTestPort); + testLegacyProperty(config, "%test.quarkus.http.ssl-port", "%test.quarkus.https.port", httpsTestPort); + } + + private void testLegacyProperty(Config config, String correctName, String legacyName, int expectedValue) { + Optional portWithCorrectProperty = config.getOptionalValue(correctName, Integer.class); + Optional portWithLegacyProperty = config.getOptionalValue(legacyName, Integer.class); + + assertEquals(Optional.of(expectedValue), portWithCorrectProperty); + assertEquals(portWithCorrectProperty, portWithLegacyProperty); + } + + public static class TestPortProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.http.test-port", "0", "quarkus.http.test-ssl-port", "0"); + } + } +} From 3af3724314054235fdb9d5894632e377bd8dc1e9 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 15:52:35 +0300 Subject: [PATCH 17/24] Properly take Quarkus HTTP body configuration into account for File body (cherry picked from commit e9a0c7c470307e18058529b04aa42c6c2f14e568) --- .../test/MessageBodyReaderTests.java | 8 ++ .../BuiltInReaderOverrideBuildItem.java | 42 ++++++++ .../deployment/ResteasyReactiveProcessor.java | 18 +++- .../test/multipart/AbstractMultipartTest.java | 6 ++ .../multipart/FileInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/FileInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../MultipartInputBodyHandlerTest.java | 5 - .../test/multipart/MultipartInputTest.java | 5 - .../runtime/QuarkusServerFileBodyHandler.java | 95 ++++++++++++++++++ .../serialisers/FileBodyHandler.java | 16 ++-- .../core/ResteasyReactiveRequestContext.java | 6 ++ .../server/spi/ServerRequestContext.java | 3 + 12 files changed, 362 insertions(+), 18 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index d4e1238aadc60..ab49f42c3381b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,12 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -20,6 +22,7 @@ import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import org.jboss.resteasy.reactive.server.jaxrs.HttpHeadersImpl; import org.jboss.resteasy.reactive.server.spi.ContentType; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; @@ -266,6 +269,11 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { return null; } + @Override + public HttpHeaders getRequestHeaders() { + return new HttpHeadersImpl(Collections.emptyList()); + } + @Override public void abortWith(Response response) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java new file mode 100644 index 0000000000000..13b3ce8eb68da --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/BuiltInReaderOverrideBuildItem.java @@ -0,0 +1,42 @@ +package io.quarkus.resteasy.reactive.server.deployment; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class BuiltInReaderOverrideBuildItem extends MultiBuildItem { + + private final String readerClassName; + private final String overrideClassName; + + public BuiltInReaderOverrideBuildItem(String readerClassName, String overrideClassName) { + this.readerClassName = readerClassName; + this.overrideClassName = overrideClassName; + } + + public String getReaderClassName() { + return readerClassName; + } + + public String getOverrideClassName() { + return overrideClassName; + } + + public static Map toMap(List items) { + if (items.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + for (BuiltInReaderOverrideBuildItem item : items) { + String previousOverride = result.put(item.getReaderClassName(), item.getOverrideClassName()); + if (previousOverride != null) { + throw new IllegalStateException( + "Providing multiple BuiltInReaderOverrideBuildItem for the same readerClassName is not supported"); + } + } + return result; + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 2b018c4247b58..65ef31c761aad 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -110,6 +110,7 @@ import org.jboss.resteasy.reactive.server.processor.scanning.ResponseHeaderMethodScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner; import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter; @@ -165,6 +166,7 @@ import io.quarkus.resteasy.reactive.common.deployment.ServerDefaultProducesHandlerBuildItem; import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -1020,6 +1022,12 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } } + @BuildStep + public void builtInReaderOverrides(BuildProducer producer) { + producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + QuarkusServerFileBodyHandler.class.getName())); + } + @BuildStep @Record(value = ExecutionTime.STATIC_INIT, useIdentityComparisonForParameters = false) public void serverSerializers(ResteasyReactiveRecorder recorder, @@ -1029,6 +1037,7 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, List additionalMessageBodyWriters, List messageBodyReaderOverrideBuildItems, List messageBodyWriterOverrideBuildItems, + List builtInReaderOverrideBuildItems, BuildProducer reflectiveClass, BuildProducer serverSerializersProducer) { @@ -1046,11 +1055,16 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinWriter.writerClass.getName()) .build()); } + Map builtInReaderOverrides = BuiltInReaderOverrideBuildItem.toMap(builtInReaderOverrideBuildItems); for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) { - registerReader(recorder, serialisers, builtinReader.entityClass.getName(), builtinReader.readerClass.getName(), + String effectiveReaderClassName = builtinReader.readerClass.getName(); + if (builtInReaderOverrides.containsKey(effectiveReaderClassName)) { + effectiveReaderClassName = builtInReaderOverrides.get(effectiveReaderClassName); + } + registerReader(recorder, serialisers, builtinReader.entityClass.getName(), effectiveReaderClassName, beanContainerBuildItem.getValue(), builtinReader.mediaType, builtinReader.constraint); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(builtinReader.readerClass.getName()) + reflectiveClass.produce(ReflectiveClassBuildItem.builder(effectiveReaderClassName) .build()); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java index 86348aa16e4b8..9bcb1a8dd1d7c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/AbstractMultipartTest.java @@ -3,6 +3,8 @@ import static org.awaitility.Awaitility.await; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.concurrent.Callable; @@ -40,4 +42,8 @@ public Boolean call() { } }); } + + protected String fileSizeAsStr(File file) throws IOException { + return "" + Files.readAllBytes(file.toPath()).length; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java new file mode 100644 index 0000000000000..a3d43bad36ab3 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..318b8602e9167 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/FileInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class FileInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(File file) throws IOException { + return Files.size(file.toPath()); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java index f75e0a416573d..9b371cb02996e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputBodyHandlerTest.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; @@ -164,8 +163,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).length; - } - } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java index 3b3bc76ad6b54..e29c3791fda69 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartInputTest.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Supplier; @@ -245,8 +244,4 @@ private String filePath(File file) { return file.toPath().toAbsolutePath().toString(); } - private String fileSizeAsStr(File file) throws IOException { - return "" + Files.readAllBytes(file.toPath()).length; - } - } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java new file mode 100644 index 0000000000000..01e6b53b33b35 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -0,0 +1,95 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerFileBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerFileBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + private Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java index 82d2045179024..1f502c66555a2 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/providers/serialisers/FileBodyHandler.java @@ -21,8 +21,8 @@ import org.jboss.resteasy.reactive.common.headers.HeaderUtil; public class FileBodyHandler implements MessageBodyReader, MessageBodyWriter { - protected static final String PREFIX = "pfx"; - protected static final String SUFFIX = "sfx"; + public static final String PREFIX = "pfx"; + public static final String SUFFIX = "sfx"; @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { @@ -33,16 +33,20 @@ public boolean isReadable(Class type, Type genericType, Annotation[] annotati public File readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { - File downloadedFile = Files.createTempFile(PREFIX, SUFFIX).toFile(); + return doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); + } + + public static File doRead(MultivaluedMap httpHeaders, InputStream entityStream, + File file) throws IOException { if (HeaderUtil.isContentLengthZero(httpHeaders)) { - return downloadedFile; + return file; } - try (OutputStream output = new BufferedOutputStream(new FileOutputStream(downloadedFile))) { + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { entityStream.transferTo(output); } - return downloadedFile; + return file; } public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { 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 2248a8f7763ac..4fd59f33828db 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 @@ -19,6 +19,7 @@ import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.Request; @@ -148,6 +149,11 @@ public ResteasyReactiveRequestContext(Deployment deployment, @Override public abstract ServerHttpResponse serverResponse(); + @Override + public HttpHeaders getRequestHeaders() { + return getHttpHeaders(); + } + public Deployment getDeployment() { return deployment; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java index 50fba8210e0da..4825de1472c11 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerRequestContext.java @@ -3,6 +3,7 @@ import java.io.InputStream; import java.io.OutputStream; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -22,5 +23,7 @@ public interface ServerRequestContext extends ResteasyReactiveCallbackContext { ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo(); + HttpHeaders getRequestHeaders(); + void abortWith(Response response); } From d8bc51aa4327a6ac810732a31a1893e10917ef8d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 31 Aug 2023 16:04:51 +0300 Subject: [PATCH 18/24] Add support for Path as a JAX-RS method body type (cherry picked from commit 96135286ba942f45fdf57af3093660720814cea2) --- .../test/MessageBodyReaderTests.java | 2 +- .../deployment/ResteasyReactiveProcessor.java | 10 +- .../multipart/PathInputWithDeleteTest.java | 80 ++++++++++++++++ .../multipart/PathInputWithoutDeleteTest.java | 96 +++++++++++++++++++ .../runtime/QuarkusServerFileBodyHandler.java | 42 +------- .../runtime/QuarkusServerPathBodyHandler.java | 96 +++++++++++++++++++ 6 files changed, 282 insertions(+), 44 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java index ab49f42c3381b..68bd47c204deb 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import jakarta.ws.rs.core.HttpHeaders; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -17,6 +16,7 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.container.ConnectionCallback; +import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 65ef31c761aad..26a848224c7ed 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -12,6 +12,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -167,6 +168,7 @@ import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.server.EndpointDisabled; import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerFileBodyHandler; +import io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveInitialiser; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRuntimeRecorder; @@ -1023,9 +1025,13 @@ private static String determineHandledGenericTypeOfProviderInterface(Class pr } @BuildStep - public void builtInReaderOverrides(BuildProducer producer) { - producer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), + public void fileHandling(BuildProducer overrideProducer, + BuildProducer readerProducer) { + overrideProducer.produce(new BuiltInReaderOverrideBuildItem(ServerFileBodyHandler.class.getName(), QuarkusServerFileBodyHandler.class.getName())); + readerProducer.produce( + new MessageBodyReaderBuildItem(QuarkusServerPathBodyHandler.class.getName(), Path.class.getName(), List.of( + MediaType.WILDCARD), RuntimeType.SERVER, true, Priorities.USER)); } @BuildStep diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java new file mode 100644 index 0000000000000..b03d853f2fcab --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithDeleteTest.java @@ -0,0 +1,80 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + "quarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + awaitUploadDirectoryToEmpty(uploadDir); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + awaitUploadDirectoryToEmpty(uploadDir); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java new file mode 100644 index 0000000000000..08b7f7181da6b --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/PathInputWithoutDeleteTest.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class PathInputWithoutDeleteTest extends AbstractMultipartTest { + + private static final java.nio.file.Path uploadDir = Paths.get("file-uploads"); + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class) + .addAsResource(new StringAsset( + // keep the files around so we can assert the outcome + "quarkus.http.body.delete-uploaded-files-on-end=false\nquarkus.http.body.uploads-directory=" + + uploadDir.toString() + "\n"), + "application.properties"); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File HTML_FILE2 = new File("./src/test/resources/test2.html"); + + @BeforeEach + public void assertEmptyUploads() { + Assertions.assertTrue(isDirectoryEmpty(uploadDir)); + } + + @AfterEach + public void clearDirectory() { + clearDirectory(uploadDir); + } + + @Test + public void test() throws IOException { + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(1, uploadDir.toFile().listFiles().length); + + RestAssured.given() + .contentType("application/octet-stream") + .body(HTML_FILE2) + .when() + .post("/test") + .then() + .statusCode(200) + .body(equalTo(fileSizeAsStr(HTML_FILE2))); + + // ensure that the 3 uploaded files where created on disk + Assertions.assertEquals(2, uploadDir.toFile().listFiles().length); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/octet-stream") + public long size(java.nio.file.Path file) throws IOException { + return Files.size(file); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java index 01e6b53b33b35..e8c8effc7300a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerFileBodyHandler.java @@ -1,28 +1,24 @@ package io.quarkus.resteasy.reactive.server.runtime; +import static io.quarkus.resteasy.reactive.server.runtime.QuarkusServerPathBodyHandler.createFile; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.nio.file.Paths; import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.container.CompletionCallback; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; -import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; @@ -56,40 +52,4 @@ public File readFrom(Class type, Type genericType, Annotation[] annotation // however this should never be called in a real world scenario return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()); } - - private Path createFile(ServerRequestContext context) throws IOException { - RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() - .getRuntimeConfiguration().body(); - boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); - String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); - Path uploadDirectory = Paths.get(uploadsDirectoryStr); - try { - Files.createDirectories(uploadDirectory); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); - if (deleteUploadedFilesOnEnd) { - context.registerCompletionCallback(new CompletionCallback() { - @Override - public void onComplete(Throwable throwable) { - ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { - @Override - public void run() { - if (Files.exists(file)) { - try { - Files.delete(file); - } catch (NoSuchFileException e) { // ignore - } catch (IOException e) { - log.error("Cannot remove uploaded file " + file, e); - } - } - } - }); - } - }); - } - return file; - } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java new file mode 100644 index 0000000000000..e6dcb8ff7dfa9 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/QuarkusServerPathBodyHandler.java @@ -0,0 +1,96 @@ +package io.quarkus.resteasy.reactive.server.runtime; + +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.PREFIX; +import static org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler.SUFFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.CompletionCallback; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.common.providers.serialisers.FileBodyHandler; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; + +public class QuarkusServerPathBodyHandler implements ServerMessageBodyReader { + + private static final Logger log = Logger.getLogger(QuarkusServerPathBodyHandler.class); + + @Override + public boolean isReadable(Class type, Type genericType, ResteasyReactiveResourceInfo lazyMethod, + MediaType mediaType) { + return Path.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) + throws WebApplicationException, IOException { + Path file = createFile(context); + return FileBodyHandler.doRead(context.getRequestHeaders().getRequestHeaders(), context.getInputStream(), file.toFile()) + .toPath(); + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return File.class.equals(type); + } + + @Override + public Path readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + // unfortunately we don't do much here to avoid the file leak + // however this should never be called in a real world scenario + return FileBodyHandler.doRead(httpHeaders, entityStream, Files.createTempFile(PREFIX, SUFFIX).toFile()).toPath(); + } + + static Path createFile(ServerRequestContext context) throws IOException { + RuntimeConfiguration.Body runtimeBodyConfiguration = ResteasyReactiveRecorder.getCurrentDeployment() + .getRuntimeConfiguration().body(); + boolean deleteUploadedFilesOnEnd = runtimeBodyConfiguration.deleteUploadedFilesOnEnd(); + String uploadsDirectoryStr = runtimeBodyConfiguration.uploadsDirectory(); + Path uploadDirectory = Paths.get(uploadsDirectoryStr); + try { + Files.createDirectories(uploadDirectory); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + Path file = Files.createTempFile(uploadDirectory, PREFIX, SUFFIX); + if (deleteUploadedFilesOnEnd) { + context.registerCompletionCallback(new CompletionCallback() { + @Override + public void onComplete(Throwable throwable) { + ResteasyReactiveRecorder.EXECUTOR_SUPPLIER.get().execute(new Runnable() { + @Override + public void run() { + if (Files.exists(file)) { + try { + Files.delete(file); + } catch (NoSuchFileException e) { // ignore + } catch (IOException e) { + log.error("Cannot remove uploaded file " + file, e); + } + } + } + }); + } + }); + } + return file; + } +} From f2f4762f3528caf506175a8a66e9b2f4639e6141 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Jan 2024 09:08:40 +0200 Subject: [PATCH 19/24] Don't want about missing JSON when returning String Closes: #38044 (cherry picked from commit d53358a8efca70de449c4c5c51184818d7586ebc) --- .../resteasy/reactive/common/processor/EndpointIndexer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 0f4dca20e286b..b8dedcde357e2 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -1623,6 +1623,9 @@ protected void warnAboutMissingJsonProviderIfNeeded(ResourceMethod method, Metho DefaultProducesHandler jsonDefaultProducersHandler, DefaultProducesHandler.Context context) { if (hasJson(method) || (hasNoTypesDefined(method) && isDefaultJson(jsonDefaultProducersHandler, context))) { + if (STRING.toString().equals(method.getSimpleReturnType())) { // when returning string, we assume that the method implementation is actually handling to conversion + return; + } boolean appProvidedJsonReaderExists = appProvidedJsonProviderExists(getSerializerScanningResult().getReaders()); boolean appProvidedJsonWriterExists = appProvidedJsonProviderExists(getSerializerScanningResult().getWriters()); if (!appProvidedJsonReaderExists || !appProvidedJsonWriterExists) { From fffa654a60b5b9e3e2b7924426cc50a62b06a6e6 Mon Sep 17 00:00:00 2001 From: Marco Schaub Date: Thu, 4 Jan 2024 15:57:50 +0000 Subject: [PATCH 20/24] Add hint for Scheduled.ApplicationNotRunning skip predicate (cherry picked from commit f37d9bd60eb936b7797cd453d059d89533f58f21) --- docs/src/main/asciidoc/scheduler-reference.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/scheduler-reference.adoc b/docs/src/main/asciidoc/scheduler-reference.adoc index b999361cdb397..3ba552f80c169 100644 --- a/docs/src/main/asciidoc/scheduler-reference.adoc +++ b/docs/src/main/asciidoc/scheduler-reference.adoc @@ -302,6 +302,8 @@ The main idea is to keep the logic to skip the execution outside the scheduled b TIP: A CDI event of type `io.quarkus.scheduler.SkippedExecution` is fired when an execution of a scheduled method is skipped. +TIP: To skip the scheduled executions while the application is starting up/shutting down, you can make use of the `io.quarkus.scheduler.Scheduled.ApplicationNotRunning` skip predicate. + [[non-blocking-methods]] === Non-blocking Methods From f9377cd0f0220ff0005b1d20a01178f6129acd18 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 5 Jan 2024 16:01:26 +0200 Subject: [PATCH 21/24] Add companion classes to Kotlin reflective hierarchy registration Fixes: #37957 (cherry picked from commit e185df85d5ead125ec3487652411ebde7abc40ae) --- .../steps/ReflectiveHierarchyStep.java | 55 ++++++++++++++----- .../reactive/kotlin/CompanionResource.kt | 19 +++++++ .../resteasy/reactive/kotlin/ResponseData.kt | 17 ++++++ .../resteasy/reactive/kotlin/CompanionIT.kt | 5 ++ .../resteasy/reactive/kotlin/CompanionTest.kt | 29 ++++++++++ 5 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt create mode 100644 integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index c3dd57bae5e17..d63b5535faf49 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -1,5 +1,7 @@ package io.quarkus.deployment.steps; +import static io.quarkus.deployment.steps.KotlinUtil.isKotlinClass; + import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; @@ -27,6 +29,8 @@ import org.jboss.jandex.VoidType; import org.jboss.logging.Logger; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; @@ -51,7 +55,7 @@ public ReflectiveHierarchyIgnoreWarningBuildItem ignoreJavaClassWarnings() { } @BuildStep - public void build(CombinedIndexBuildItem combinedIndexBuildItem, + public void build(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities, List hierarchy, List ignored, List finalFieldsWritablePredicates, @@ -73,7 +77,7 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem, final Deque visits = new ArrayDeque<>(); for (ReflectiveHierarchyBuildItem i : hierarchy) { - addReflectiveHierarchy(combinedIndexBuildItem, + addReflectiveHierarchy(combinedIndexBuildItem, capabilities, i, i.hasSource() ? i.getSource() : i.getType().name().toString(), i.getType(), @@ -128,7 +132,7 @@ private void removeIgnored(Map> unindexedClasses, } private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, - ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type, + Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type, Set processedReflectiveHierarchies, Map> unindexedClasses, Predicate finalFieldsWritable, BuildProducer reflectiveClass, Deque visits) { @@ -142,30 +146,34 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte return; } - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), type.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(), + type.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + subclass.name(), subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); } for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + subclass.name(), subclass.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); } } else if (type instanceof ArrayType) { - visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, + reflectiveHierarchyBuildItem, source, type.asArrayType().constituent(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } else if (type instanceof ParameterizedType) { if (!reflectiveHierarchyBuildItem.getIgnoreTypePredicate().test(type.name())) { - addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(), type.name(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits); @@ -173,14 +181,15 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte final ParameterizedType parameterizedType = (ParameterizedType) type; for (Type typeArgument : parameterizedType.arguments()) { visits.addLast( - () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, typeArgument, + () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + typeArgument, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } } } - private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, + private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, DotName name, @@ -223,7 +232,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem return; } - visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, info.superName(), initialName, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); @@ -237,7 +246,8 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem } final Type fieldType = getFieldType(combinedIndexBuildItem, initialName, info, field); visits.addLast( - () -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, fieldType, + () -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + fieldType, processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } @@ -249,11 +259,30 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem method.returnType().kind() == Kind.VOID) { continue; } - visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, + visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, + reflectiveHierarchyBuildItem, source, method.returnType(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable, reflectiveClass, visits)); } + + // for Kotlin classes, we need to register the nested classes as well because companion classes are very often necessary at runtime + if (capabilities.isPresent(Capability.KOTLIN) && isKotlinClass(info)) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + Class[] declaredClasses = classLoader.loadClass(info.name().toString()).getDeclaredClasses(); + for (Class clazz : declaredClasses) { + DotName dotName = DotName.createSimple(clazz.getName()); + addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, + dotName, dotName, + processedReflectiveHierarchies, unindexedClasses, + finalFieldsWritable, reflectiveClass, visits); + } + } catch (ClassNotFoundException e) { + log.warnf(e, "Failed to load Class %s", info.name().toString()); + } + + } } private static Type getFieldType(CombinedIndexBuildItem combinedIndexBuildItem, DotName initialName, ClassInfo info, diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt new file mode 100644 index 0000000000000..6459e438c013f --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionResource.kt @@ -0,0 +1,19 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType + +@Path("/companion") +class CompanionResource { + @Path("success") + @GET + @Produces(MediaType.APPLICATION_JSON) + fun success() = ResponseData.success() + + @Path("failure") + @GET + @Produces(MediaType.APPLICATION_JSON) + fun failure() = ResponseData.failure("error") +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt new file mode 100644 index 0000000000000..efce43fa20bac --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/main/kotlin/io/quarkus/it/resteasy/reactive/kotlin/ResponseData.kt @@ -0,0 +1,17 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +class ResponseData( + val code: Int = STATUS_CODE.SUCCESS.code, + val msg: String = "", + val data: T? = null, +) { + companion object { + fun success() = ResponseData() + fun failure(msg: String) = ResponseData(code = STATUS_CODE.ERROR.code, msg = msg) + } +} + +enum class STATUS_CODE(val code: Int) { + SUCCESS(200), + ERROR(500) +} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt new file mode 100644 index 0000000000000..75b52c24a0edc --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionIT.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import io.quarkus.test.junit.QuarkusIntegrationTest + +@QuarkusIntegrationTest class CompanionIT : CompanionTest() {} diff --git a/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt new file mode 100644 index 0000000000000..1f088fffed227 --- /dev/null +++ b/integration-tests/resteasy-reactive-kotlin/standard/src/test/kotlin/io/quarkus/it/resteasy/reactive/kotlin/CompanionTest.kt @@ -0,0 +1,29 @@ +package io.quarkus.it.resteasy.reactive.kotlin + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +@QuarkusTest +class CompanionTest { + + @Test + fun testSuccessResponseData() { + When { get("/companion/success") } Then + { + statusCode(200) + body(containsString("200")) + } + } + + @Test + fun testFailureResponseData() { + When { get("/companion/failure") } Then + { + statusCode(200) + body(containsString("500"), containsString("error")) + } + } +} From 94728244101443a588213ad0ae4b312ecbdde89c Mon Sep 17 00:00:00 2001 From: Ales Justin Date: Mon, 8 Jan 2024 18:31:01 +0100 Subject: [PATCH 22/24] Always set ssl and alpn for non-plain-text with Vert.x gRPC channel (cherry picked from commit bea42972acb01c70e768d7a00e82827f7caf92dc) --- .../java/io/quarkus/grpc/runtime/supports/Channels.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java index 4e66fd21023bb..65831169c0b03 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java @@ -254,14 +254,16 @@ public static Channel createChannel(String name, Set perClientIntercepto options.setHttp2ClearTextUpgrade(false); // this fixes i30379 if (!plainText) { + // always set ssl + alpn for plain-text=false + options.setSsl(true); + options.setUseAlpn(true); + if (config.ssl.trustStore.isPresent()) { Optional trustStorePath = config.ssl.trustStore; if (trustStorePath.isPresent()) { PemTrustOptions to = new PemTrustOptions(); to.addCertValue(bufferFor(trustStorePath.get(), "trust store")); options.setTrustOptions(to); - options.setSsl(true); - options.setUseAlpn(true); } Optional certificatePath = config.ssl.certificate; Optional keyPath = config.ssl.key; @@ -270,8 +272,6 @@ public static Channel createChannel(String name, Set perClientIntercepto cko.setCertValue(bufferFor(certificatePath.get(), "certificate")); cko.setKeyValue(bufferFor(keyPath.get(), "key")); options.setKeyCertOptions(cko); - options.setSsl(true); - options.setUseAlpn(true); } } } From 842b3fe91fbb980f8c6229f6ca566dfe7d473585 Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Thu, 14 Dec 2023 19:30:28 +0100 Subject: [PATCH 23/24] Stork path param resolution fix: use raw path and avoid double encoding when creating new URI Related to #37713 refactor: clean up a few commented lines fix: use raw path and avoid double encoding, adapt tests accordingly (cherry picked from commit ce206d5e43f5260ce71bc792ac5dd576bd4ff2f5) --- .../client/reactive/stork/HelloClient.java | 13 +++++++ .../client/reactive/stork/HelloResource.java | 18 ++++++++++ .../reactive/stork/PassThroughResource.java | 13 +++++++ .../reactive/stork/StorkDevModeTest.java | 21 +++++++++++ .../reactive/stork/StorkIntegrationTest.java | 35 ++++++++++++------- .../StorkResponseTimeLoadBalancerTest.java | 6 ++-- .../stork/StorkWithPathIntegrationTest.java | 32 +++++++++++------ .../client/impl/StorkClientRequestFilter.java | 10 +++--- .../it/rest/client/reactive/stork/Client.java | 8 +++++ .../reactive/stork/ClientCallingResource.java | 10 ++++++ .../reactive/stork/FastWiremockServer.java | 3 ++ .../stork/RestClientReactiveStorkTest.java | 13 +++++++ .../reactive/stork/SlowWiremockServer.java | 3 ++ 13 files changed, 154 insertions(+), 31 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java index 664dea477ef7d..baa2e8d3771d5 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloClient.java @@ -1,7 +1,11 @@ package io.quarkus.rest.client.reactive.stork; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -10,4 +14,13 @@ public interface HelloClient { @GET String hello(); + + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Path("/") + String echo(String name); + + @GET + @Path("/{name}") + public String helloWithPathParam(@PathParam("name") String name); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java index e9966a8d8eac6..1a544e2ab878e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/HelloResource.java @@ -1,7 +1,13 @@ package io.quarkus.rest.client.reactive.stork; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; @Path("/hello") public class HelloResource { @@ -12,4 +18,16 @@ public class HelloResource { public String hello() { return HELLO_WORLD; } + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public String invoke(@PathParam("name") String name) { + return "Hello, " + name; + } + + @POST + public String echo(String name, @Context Request request) { + return "hello, " + name; + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java index 129b7aece4cda..51f11c1b539ca 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/PassThroughResource.java @@ -4,6 +4,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -22,6 +23,18 @@ public String invokeClient() { return client.hello(); } + @Path("/v2/{name}") + @GET + public String invokeClientWithPathParamContainingSlash(@PathParam("name") String name) { + return client.helloWithPathParam(name + "/" + name); + } + + @Path("/{name}") + @GET + public String invokeClientWithPathParam(@PathParam("name") String name) { + return client.helloWithPathParam(name); + } + @Path("/cdi") @GET public String invokeCdiClient() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java index f30d13b937008..5a12b520c497b 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkDevModeTest.java @@ -67,4 +67,25 @@ void shouldModifyStorkSettings() { .body(equalTo(WIREMOCK_RESPONSE)); // @formatter:on } + + @Test + void shouldSayHelloNameWithSlash() { + when() + .get("/helper/v2/stork") + .then() + .statusCode(200) + // The response contains an encoded `/` + .body(equalTo("Hello, stork/stork")); + + } + + @Test + void shouldSayHelloNameWithBlank() { + when() + .get("/helper/smallrye stork") + .then() + .statusCode(200) + // The response contains an encoded blank espace + .body(equalTo("Hello, smallrye stork")); + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java index cb22c1393db59..639ae39cd8fac 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkIntegrationTest.java @@ -15,8 +15,6 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; import io.smallrye.stork.api.NoSuchServiceDefinitionException; @@ -24,45 +22,58 @@ public class StorkIntegrationTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-application.properties"); @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldDetermineUrlViaStork() { String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello")) - .build(HelloClient2.class) + .build(HelloClient.class) .echo("black and white bird"); assertThat(greeting).isEqualTo("hello, black and white bird"); + + greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service/hello")) + .build(HelloClient.class) + .helloWithPathParam("black and white bird"); + assertThat(greeting).isEqualTo("Hello, black and white bird"); } @Test void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException { - String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + String greeting = ClientBuilder.newClient().target("stork://hello-service/hello").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(new URI("stork://hello-service/hello")).request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/hello")).request() .get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); + + greeting = ClientBuilder.newClient().target("stork://hello-service/hello").path("big bird").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test void shouldDetermineUrlViaStorkCDI() { String greeting = client.echo("big bird"); assertThat(greeting).isEqualTo("hello, big bird"); + + greeting = client.helloWithPathParam("big bird"); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test @Timeout(20) void shouldFailOnUnknownService() { - HelloClient2 client2 = RestClientBuilder.newBuilder() + HelloClient client = RestClientBuilder.newBuilder() .baseUri(URI.create("stork://nonexistent-service")) - .build(HelloClient2.class); - assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); + .build(HelloClient.class); + assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java index 507ca9eb31b1a..9dc52a8d0d271 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkResponseTimeLoadBalancerTest.java @@ -16,8 +16,6 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; public class StorkResponseTimeLoadBalancerTest { @@ -28,7 +26,7 @@ public class StorkResponseTimeLoadBalancerTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-stat-lb.properties"); @BeforeAll @@ -46,7 +44,7 @@ public static void shutDown() { } @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldUseFasterService() { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java index 26ba43279cbae..26ac15b363f45 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/stork/StorkWithPathIntegrationTest.java @@ -15,8 +15,6 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.rest.client.reactive.HelloClient2; -import io.quarkus.rest.client.reactive.HelloResource; import io.quarkus.test.QuarkusUnitTest; import io.smallrye.stork.api.NoSuchServiceDefinitionException; @@ -24,45 +22,57 @@ public class StorkWithPathIntegrationTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloClient2.class, HelloResource.class)) + .addClasses(HelloClient.class, HelloResource.class)) .withConfigurationResource("stork-application-with-path.properties"); @RestClient - HelloClient2 client; + HelloClient client; @Test void shouldDetermineUrlViaStork() { String greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service")) - .build(HelloClient2.class) + .build(HelloClient.class) .echo("black and white bird"); assertThat(greeting).isEqualTo("hello, black and white bird"); + + greeting = RestClientBuilder.newBuilder().baseUri(URI.create("stork://hello-service")) + .build(HelloClient.class) + .helloWithPathParam("black and white bird"); + assertThat(greeting).isEqualTo("Hello, black and white bird"); } @Test void shouldDetermineUrlViaStorkWhenUsingTarget() throws URISyntaxException { String greeting = ClientBuilder.newClient().target("stork://hello-service").request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(new URI("stork://hello-service")).request().get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); greeting = ClientBuilder.newClient().target(UriBuilder.fromUri("stork://hello-service/")).request() .get(String.class); - assertThat(greeting).isEqualTo("Hello"); + assertThat(greeting).isEqualTo("Hello, World!"); + + greeting = ClientBuilder.newClient().target("stork://hello-service/").path("big bird").request() + .get(String.class); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test void shouldDetermineUrlViaStorkCDI() { String greeting = client.echo("big bird"); assertThat(greeting).isEqualTo("hello, big bird"); + + greeting = client.helloWithPathParam("big bird"); + assertThat(greeting).isEqualTo("Hello, big bird"); } @Test @Timeout(20) void shouldFailOnUnknownService() { - HelloClient2 client2 = RestClientBuilder.newBuilder() + HelloClient client = RestClientBuilder.newBuilder() .baseUri(URI.create("stork://nonexistent-service")) - .build(HelloClient2.class); - assertThatThrownBy(() -> client2.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); + .build(HelloClient.class); + assertThatThrownBy(() -> client.echo("foo")).isInstanceOf(NoSuchServiceDefinitionException.class); } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java index 7083d96803940..60990009a9d88 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/StorkClientRequestFilter.java @@ -7,6 +7,7 @@ import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.ext.Provider; import org.jboss.logging.Logger; @@ -62,7 +63,7 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { } // Service instance can also contain an optional path. Optional path = instance.getPath(); - String actualPath = uri.getPath(); + String actualPath = uri.getRawPath(); if (path.isPresent()) { var p = path.get(); if (!p.startsWith("/")) { @@ -79,11 +80,12 @@ public void filter(ResteasyReactiveClientRequestContext requestContext) { } } } - + //To avoid the path double encoding we create uri with path=null and set the path after URI newUri = new URI(scheme, uri.getUserInfo(), host, port, - actualPath, uri.getQuery(), uri.getFragment()); - requestContext.setUri(newUri); + null, uri.getQuery(), uri.getFragment()); + URI build = UriBuilder.fromUri(newUri).path(actualPath).build(); + requestContext.setUri(build); if (measureTime && instance.gatherStatistics()) { requestContext.setCallStatsCollector(instance); } diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java index 3c3bcea3042bc..ebf44dc314680 100644 --- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java +++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/Client.java @@ -2,6 +2,9 @@ import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -11,4 +14,9 @@ public interface Client { @GET @Consumes(MediaType.TEXT_PLAIN) String echo(String name); + + @GET + @Path("/v2/{name}") + @Produces(MediaType.TEXT_PLAIN) + String invoke(@PathParam("name") String name); } diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java index 2e3a307174dc8..782165a5c3895 100644 --- a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/ClientCallingResource.java @@ -3,6 +3,9 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -17,4 +20,11 @@ public class ClientCallingResource { public String passThrough() { return client.echo("World!"); } + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public String invoke(@PathParam("name") String name) { + return client.invoke(name + "/" + name); + } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java index ba55cdc0f30f0..a6b277681a970 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/FastWiremockServer.java @@ -1,6 +1,7 @@ package io.quarkus.it.rest.reactive.stork; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; import java.util.Map; @@ -25,6 +26,8 @@ int httpsPort() { protected Map initWireMock(WireMockServer server) { server.stubFor(WireMock.get("/hello") .willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200))); + server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}")) + .willReturn(aResponse().withBody(FAST_RESPONSE).withStatus(200))); return Map.of("fast-service", "localhost:8443"); } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java index 5adb6924ee71b..884d5ffbbfc0f 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java @@ -54,4 +54,17 @@ void shouldUseFasterService() { // after hitting the slow endpoint, we should only use the fast one: assertThat(responses).containsOnly(FAST_RESPONSE, FAST_RESPONSE, FAST_RESPONSE); } + + @Test + void shouldUseV2Service() { + Set responses = new HashSet<>(); + + for (int i = 0; i < 2; i++) { + Response response = when().get("/client/quarkus"); + response.then().statusCode(200); + } + + responses.clear(); + + } } diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java index 7dbc7f74b9b9d..3b1a051f345a6 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/SlowWiremockServer.java @@ -1,6 +1,7 @@ package io.quarkus.it.rest.reactive.stork; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; import java.util.Map; @@ -26,6 +27,8 @@ protected Map initWireMock(WireMockServer server) { server.stubFor(WireMock.get("/hello") .willReturn(aResponse().withFixedDelay(1000) .withBody(SLOW_RESPONSE).withStatus(200))); + server.stubFor(WireMock.get(urlPathTemplate("/hello/v2/{name}")) + .willReturn(aResponse().withFixedDelay(1000).withBody(SLOW_RESPONSE).withStatus(200))); return Map.of("slow-service", "localhost:8444"); } } From 315a25498bae4ca4cfdfd2cab932e48d59f07a00 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 8 Jan 2024 10:56:31 +0200 Subject: [PATCH 24/24] Make Picocli version providers unremovable classes (cherry picked from commit 8161071196200925c2be70c0664a6f8898c7e905) --- .../picocli/deployment/PicocliProcessor.java | 13 ++++++++++ .../it/picocli/EntryWithVersionCommand.java | 9 +++++++ .../quarkus/it/picocli/VersionProvider.java | 22 ++++++++++++++++ .../io/quarkus/it/picocli/TestVersion.java | 25 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java create mode 100644 integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java create mode 100644 integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java diff --git a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java index b2ed32c390f8b..18c452474e5f7 100644 --- a/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java +++ b/extensions/picocli/deployment/src/main/java/io/quarkus/picocli/deployment/PicocliProcessor.java @@ -12,6 +12,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.AutoAddScopeBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Feature; @@ -76,6 +77,7 @@ IndexDependencyBuildItem picocliIndexDependency() { void picocliRunner(ApplicationIndexBuildItem applicationIndex, CombinedIndexBuildItem combinedIndex, BuildProducer additionalBean, + BuildProducer unremovableBean, BuildProducer quarkusApplicationClass, BuildProducer annotationsTransformer) { IndexView index = combinedIndex.getIndex(); @@ -99,6 +101,17 @@ void picocliRunner(ApplicationIndexBuildItem applicationIndex, additionalBean.produce(AdditionalBeanBuildItem.unremovableOf(DefaultPicocliCommandLineFactory.class)); quarkusApplicationClass.produce(new QuarkusApplicationClassBuildItem(PicocliRunner.class)); } + + // Make all classes that can be instantiated by IFactory unremovable + unremovableBean.produce(UnremovableBeanBuildItem.beanTypes(CommandLine.ITypeConverter.class, + CommandLine.IVersionProvider.class, + CommandLine.IModelTransformer.class, + CommandLine.IModelTransformer.class, + CommandLine.IDefaultValueProvider.class, + CommandLine.IParameterConsumer.class, + CommandLine.IParameterPreprocessor.class, + CommandLine.INegatableOptionTransformer.class, + CommandLine.IHelpFactory.class)); } private List classesAnnotatedWith(IndexView indexView, String annotationClassName) { diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java new file mode 100644 index 0000000000000..c2121fe991b46 --- /dev/null +++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/EntryWithVersionCommand.java @@ -0,0 +1,9 @@ +package io.quarkus.it.picocli; + +import io.quarkus.picocli.runtime.annotations.TopCommand; +import picocli.CommandLine; + +@TopCommand +@CommandLine.Command(mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) +public class EntryWithVersionCommand { +} diff --git a/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java new file mode 100644 index 0000000000000..e06d7c92156f7 --- /dev/null +++ b/integration-tests/picocli/src/main/java/io/quarkus/it/picocli/VersionProvider.java @@ -0,0 +1,22 @@ +package io.quarkus.it.picocli; + +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import picocli.CommandLine; + +@Singleton +public class VersionProvider implements CommandLine.IVersionProvider { + + private final String version; + + public VersionProvider(@ConfigProperty(name = "some.version", defaultValue = "0.0.1") String version) { + this.version = version; + } + + @Override + public String[] getVersion() throws Exception { + return new String[] { version }; + } +} diff --git a/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java new file mode 100644 index 0000000000000..1218095d3de49 --- /dev/null +++ b/integration-tests/picocli/src/test/java/io/quarkus/it/picocli/TestVersion.java @@ -0,0 +1,25 @@ +package io.quarkus.it.picocli; + +import static io.quarkus.it.picocli.TestUtils.createConfig; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusProdModeTest; + +public class TestVersion { + + @RegisterExtension + static final QuarkusProdModeTest config = createConfig("version-app", EntryWithVersionCommand.class, + VersionProvider.class) + .overrideConfigKey("some.version", "1.1") + .setCommandLineParameters("--version"); + + @Test + public void simpleTest() { + Assertions.assertThat(config.getStartupConsoleOutput()).containsOnlyOnce("1.1"); + Assertions.assertThat(config.getExitCode()).isZero(); + } + +}