From 6e88f4e05910c44c233f55c7f68856c575c802df Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 31 Aug 2021 09:47:43 +1000 Subject: [PATCH 01/60] Update Dev UI with the correct state when resuming Fixes #19777 (cherry picked from commit 25ab92601ba13d2fe083d48578028e69d4ec2bc8) --- ...ntinuousTestingWebSocketTestListener.java} | 40 ++++++++++++------- .../devmode/tests/TestsProcessor.java | 4 +- 2 files changed, 28 insertions(+), 16 deletions(-) rename extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/{ContinuousTestingWebSocketListener.java => ContinuousTestingWebSocketTestListener.java} (56%) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketListener.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketTestListener.java similarity index 56% rename from extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketListener.java rename to extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketTestListener.java index 703dfe9514f44..2d83492a8186b 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketListener.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ContinuousTestingWebSocketTestListener.java @@ -11,7 +11,9 @@ import io.quarkus.deployment.dev.testing.TestRunResults; import io.quarkus.dev.testing.ContinuousTestingWebsocketListener; -public class ContinuousTestingWebSocketListener implements TestListener { +public class ContinuousTestingWebSocketTestListener implements TestListener { + + private volatile ContinuousTestingWebsocketListener.State lastState; @Override public void listenerRegistered(TestController testController) { @@ -20,9 +22,18 @@ public void listenerRegistered(TestController testController) { @Override public void testsEnabled() { - ContinuousTestingWebsocketListener - .setLastState( - new ContinuousTestingWebsocketListener.State(true, true, 0L, 0L, 0L, 0L, false, false, false, true)); + if (lastState == null) { + ContinuousTestingWebsocketListener + .setLastState(new ContinuousTestingWebsocketListener.State(true, false, + 0, 0, 0, 0, + ContinuousTestingWebsocketListener.getLastState().isBrokenOnly, + ContinuousTestingWebsocketListener.getLastState().isTestOutput, + ContinuousTestingWebsocketListener.getLastState().isInstrumentationBasedReload, + ContinuousTestingWebsocketListener.getLastState().isLiveReload)); + } else { + ContinuousTestingWebsocketListener + .setLastState(lastState); + } } @Override @@ -46,17 +57,18 @@ public void testComplete(TestResult result) { @Override public void runComplete(TestRunResults testRunResults) { + lastState = new ContinuousTestingWebsocketListener.State(true, false, + testRunResults.getPassedCount() + + testRunResults.getFailedCount() + + testRunResults.getSkippedCount(), + testRunResults.getPassedCount(), + testRunResults.getFailedCount(), testRunResults.getSkippedCount(), + ContinuousTestingWebsocketListener.getLastState().isBrokenOnly, + ContinuousTestingWebsocketListener.getLastState().isTestOutput, + ContinuousTestingWebsocketListener.getLastState().isInstrumentationBasedReload, + ContinuousTestingWebsocketListener.getLastState().isLiveReload); ContinuousTestingWebsocketListener.setLastState( - new ContinuousTestingWebsocketListener.State(true, false, - testRunResults.getPassedCount() + - testRunResults.getFailedCount() + - testRunResults.getSkippedCount(), - testRunResults.getPassedCount(), - testRunResults.getFailedCount(), testRunResults.getSkippedCount(), - ContinuousTestingWebsocketListener.getLastState().isBrokenOnly, - ContinuousTestingWebsocketListener.getLastState().isTestOutput, - ContinuousTestingWebsocketListener.getLastState().isInstrumentationBasedReload, - ContinuousTestingWebsocketListener.getLastState().isLiveReload)); + lastState); } @Override diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java index fe0a98105eb8b..6bc93507d3b80 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java @@ -23,7 +23,7 @@ import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.devmode.console.ContinuousTestingWebSocketListener; +import io.quarkus.vertx.http.deployment.devmode.console.ContinuousTestingWebSocketTestListener; import io.quarkus.vertx.http.runtime.devmode.DevConsoleRecorder; import io.quarkus.vertx.http.runtime.devmode.Json; import io.vertx.core.Handler; @@ -61,7 +61,7 @@ public void setupTestRoutes( .route("dev/test") .handler(recorder.continousTestHandler(shutdownContextBuildItem)) .build()); - testListenerBuildItemBuildProducer.produce(new TestListenerBuildItem(new ContinuousTestingWebSocketListener())); + testListenerBuildItemBuildProducer.produce(new TestListenerBuildItem(new ContinuousTestingWebSocketTestListener())); } } From 3439612d7e5f625e85d864152a4198fc8d304002 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 31 Aug 2021 19:53:29 +0200 Subject: [PATCH 02/60] Update Vert.x version to 4.1.3 and Mutiny bindings to 2.13.0 (cherry picked from commit 2d9e9dc35b0074035df6093a8e7c05b3fb861f03) --- bom/application/pom.xml | 6 +++--- independent-projects/resteasy-reactive/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index c101beff0e49f..905e66a6bca47 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -54,7 +54,7 @@ 1.2.0 1.0.13 2.6.0 - 2.12.0 + 2.13.0 3.9.1 1.2.1 1.3.5 @@ -110,7 +110,7 @@ 1.16.1.Final 1.8.7.Final 3.4.2.Final - 4.1.2 + 4.1.3 4.5.13 4.4.14 4.1.4 @@ -132,7 +132,7 @@ 12.1.7.Final 4.4.1.Final 2.9.2 - 4.1.65.Final + 4.1.67.Final 1.0.3 3.4.2.Final 1.0.0 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 3067ad3b99707..3f610491faa3b 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -52,7 +52,7 @@ 1.1.6 1.0.0 1.6.0 - 4.1.2 + 4.1.3 4.4.0 1.0.0.Final 2.0.0.Final From ea5d1948168c4fc9b1b4cccb84163e3bfd088d09 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 17 Aug 2021 09:36:14 +1000 Subject: [PATCH 03/60] Add some substitutions for Brotli and Zstd (cherry picked from commit fb4c1cbb92ad588bd48cec3d07a223ddfec73a91) --- extensions/vertx-http/runtime/pom.xml | 5 + .../HttpContentCompressorSubstitutions.java | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/graal/HttpContentCompressorSubstitutions.java diff --git a/extensions/vertx-http/runtime/pom.xml b/extensions/vertx-http/runtime/pom.xml index a1f7c667077d8..fabcf3c34282d 100644 --- a/extensions/vertx-http/runtime/pom.xml +++ b/extensions/vertx-http/runtime/pom.xml @@ -53,6 +53,11 @@ + + org.graalvm.nativeimage + svm + provided + org.junit.jupiter diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/graal/HttpContentCompressorSubstitutions.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/graal/HttpContentCompressorSubstitutions.java new file mode 100644 index 0000000000000..37dac442fbcf1 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/graal/HttpContentCompressorSubstitutions.java @@ -0,0 +1,115 @@ +package io.quarkus.vertx.http.runtime.graal; + +import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; +import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; +import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; +import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; + +import java.util.function.BooleanSupplier; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.compression.ZlibWrapper; +import io.netty.handler.codec.http2.CompressorHttp2ConnectionEncoder; +import io.netty.handler.codec.http2.Http2Exception; + +public class HttpContentCompressorSubstitutions { + + @TargetClass(className = "io.netty.handler.codec.compression.ZstdEncoder", onlyWith = IsZstdAbsent.class) + public static final class ZstdEncoderFactorySubstitution { + + @Substitute + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + throw new UnsupportedOperationException(); + } + + @Substitute + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) { + throw new UnsupportedOperationException(); + } + + @Substitute + public void flush(final ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } + } + + @TargetClass(className = "io.netty.handler.codec.compression.BrotliEncoder", onlyWith = IsBrotliAbsent.class) + public static final class BrEncoderFactorySubstitution { + + @Substitute + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + throw new UnsupportedOperationException(); + } + + @Substitute + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) { + throw new UnsupportedOperationException(); + } + } + + @TargetClass(CompressorHttp2ConnectionEncoder.class) + public static final class CompressorHttp2ConnectionSubstitute { + + @Substitute + protected EmbeddedChannel newContentCompressor(ChannelHandlerContext ctx, CharSequence contentEncoding) + throws Http2Exception { + if (GZIP.contentEqualsIgnoreCase(contentEncoding) || X_GZIP.contentEqualsIgnoreCase(contentEncoding)) { + return newCompressionChannel(ctx, ZlibWrapper.GZIP); + } + if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { + return newCompressionChannel(ctx, ZlibWrapper.ZLIB); + } + // 'identity' or unsupported + return null; + } + + @Alias + private EmbeddedChannel newCompressionChannel(final ChannelHandlerContext ctx, ZlibWrapper wrapper) { + throw new UnsupportedOperationException(); + } + } + + public static class IsZstdAbsent implements BooleanSupplier { + + private boolean zstdAbsent; + + public IsZstdAbsent() { + try { + Class.forName("com.github.luben.zstd.Zstd"); + zstdAbsent = false; + } catch (ClassNotFoundException e) { + zstdAbsent = true; + } + } + + @Override + public boolean getAsBoolean() { + return zstdAbsent; + } + } + + public static class IsBrotliAbsent implements BooleanSupplier { + + private boolean brotliAbsent; + + public IsBrotliAbsent() { + try { + Class.forName("com.aayushatharva.brotli4j.encoder.Encoder"); + brotliAbsent = false; + } catch (ClassNotFoundException e) { + brotliAbsent = true; + } + } + + @Override + public boolean getAsBoolean() { + return brotliAbsent; + } + } +} From 061fce664b9475f723cda532a795ac0674a348b7 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 1 Sep 2021 09:28:47 +1000 Subject: [PATCH 04/60] Fix some tests the broke due to default change The max attribute size changed in vert.x (cherry picked from commit a3d5b90cc5fbe17ae79864c0f3868369715d8d46) --- ...anDefaultFormAttributeMultipartFormInputTest.java | 8 +++++++- .../TooLargeFormAttributeMultipartFormInputTest.java | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/LargerThanDefaultFormAttributeMultipartFormInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/LargerThanDefaultFormAttributeMultipartFormInputTest.java index 5489e49172b3f..225943678fa69 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/LargerThanDefaultFormAttributeMultipartFormInputTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/LargerThanDefaultFormAttributeMultipartFormInputTest.java @@ -39,7 +39,7 @@ public JavaArchive get() { return ShrinkWrap.create(JavaArchive.class) .addClasses(Resource.class, Data.class) .addAsResource(new StringAsset( - "quarkus.http.limits.max-form-attribute-size=4K"), + "quarkus.http.limits.max-form-attribute-size=120K"), "application.properties"); } }); @@ -49,6 +49,12 @@ public JavaArchive get() { @Test public void test() throws IOException { String fileContents = new String(Files.readAllBytes(FILE.toPath()), StandardCharsets.UTF_8); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; ++i) { + sb.append(fileContents); + } + fileContents = sb.toString(); + Assertions.assertTrue(fileContents.length() > HttpServerOptions.DEFAULT_MAX_FORM_ATTRIBUTE_SIZE); given() .multiPart("text", fileContents) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/TooLargeFormAttributeMultipartFormInputTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/TooLargeFormAttributeMultipartFormInputTest.java index 6699beab8c705..ef3d755319168 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/TooLargeFormAttributeMultipartFormInputTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/TooLargeFormAttributeMultipartFormInputTest.java @@ -66,9 +66,13 @@ public void clearDirectory() { @Test public void test() throws IOException { - String formAttrSourceFileContents = new String(Files.readAllBytes(FORM_ATTR_SOURCE_FILE.toPath()), - StandardCharsets.UTF_8); - Assertions.assertTrue(formAttrSourceFileContents.length() > HttpServerOptions.DEFAULT_MAX_FORM_ATTRIBUTE_SIZE); + String fileContents = new String(Files.readAllBytes(FORM_ATTR_SOURCE_FILE.toPath()), StandardCharsets.UTF_8); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 10; ++i) { + sb.append(fileContents); + } + fileContents = sb.toString(); + Assertions.assertTrue(fileContents.length() > HttpServerOptions.DEFAULT_MAX_FORM_ATTRIBUTE_SIZE); given() .multiPart("active", "true") .multiPart("num", "25") @@ -76,7 +80,7 @@ public void test() throws IOException { .multiPart("htmlFile", HTML_FILE, "text/html") .multiPart("xmlFile", XML_FILE, "text/xml") .multiPart("txtFile", TXT_FILE, "text/plain") - .multiPart("name", formAttrSourceFileContents) + .multiPart("name", fileContents) .accept("text/plain") .when() .post("/test") From 1375c0d2b841ee91843036c965d045260ada2526 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Tue, 31 Aug 2021 21:11:43 +0200 Subject: [PATCH 05/60] Fix NoSuchElementException on `@Before/AfterAll` with TestInfo Fixes #19800 (cherry picked from commit 6ac8ad3884bfee5915c42f14b0bb7bfff8d30baf) --- .../it/main/QuarkusTestCallbacksTestCase.java | 21 +++++++++++++++++++ .../test/junit/QuarkusTestExtension.java | 6 ++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java index af121205b21c2..80983c0f53bc0 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestCallbacksTestCase.java @@ -3,6 +3,7 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -11,7 +12,9 @@ import java.lang.annotation.Target; import java.lang.reflect.Method; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -27,6 +30,22 @@ @QuarkusTest public class QuarkusTestCallbacksTestCase { + @BeforeAll + static void beforeAllWithTestInfo(TestInfo testInfo) { + checkBeforeOrAfterAllTestInfo(testInfo); + } + + @AfterAll + static void afterAllWithTestInfo(TestInfo testInfo) { + checkBeforeOrAfterAllTestInfo(testInfo); + } + + private static void checkBeforeOrAfterAllTestInfo(TestInfo testInfo) { + assertNotNull(testInfo); + assertEquals(QuarkusTestCallbacksTestCase.class, testInfo.getTestClass().get()); + assertFalse(testInfo.getTestMethod().isPresent()); + } + @BeforeEach void beforeEachWithTestInfo(TestInfo testInfo) throws NoSuchMethodException { checkBeforeOrAfterEachTestInfo(testInfo, "beforeEachWithTestInfo"); @@ -43,6 +62,7 @@ private void checkBeforeOrAfterEachTestInfo(TestInfo testInfo, String unexpected assertNotEquals(testMethodName, QuarkusTestCallbacksTestCase.class.getDeclaredMethod(unexpectedMethodName, TestInfo.class)); assertTrue(testMethodName.startsWith("test")); + assertEquals(QuarkusTestCallbacksTestCase.class, testInfo.getTestClass().get()); } @Test @@ -66,6 +86,7 @@ public void testInfoTestCase(TestInfo testInfo) throws NoSuchMethodException { Method testMethod = testInfo.getTestMethod().get(); assertEquals(testMethod, QuarkusTestCallbacksTestCase.class.getDeclaredMethod("testInfoTestCase", TestInfo.class)); assertEquals(1, testMethod.getAnnotationsByType(TestAnnotation.class).length); + assertEquals(QuarkusTestCallbacksTestCase.class, testInfo.getTestClass().get()); } @Target({ METHOD }) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index a6175dce4e58e..5e91160ff6ff6 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -1038,9 +1038,11 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation cloneRequired = false; } else if (TestInfo.class.isAssignableFrom(theclass)) { TestInfo info = (TestInfo) arg; - Method newTestMethod = determineTCCLExtensionMethod(info.getTestMethod().get(), testClassFromTCCL); + Method newTestMethod = info.getTestMethod().isPresent() + ? determineTCCLExtensionMethod(info.getTestMethod().get(), testClassFromTCCL) + : null; replacement = new TestInfoImpl(info.getDisplayName(), info.getTags(), Optional.of(testClassFromTCCL), - Optional.of(newTestMethod)); + Optional.ofNullable(newTestMethod)); } else if (clonePattern.matcher(className).matches()) { cloneRequired = true; } else { From 947c53da6a49235b2259311fe8134cfaf467c9fa Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Tue, 31 Aug 2021 15:16:11 -0400 Subject: [PATCH 06/60] CLI: docs and help for -P (cherry picked from commit f78e00ace9d9696cdebd499817a7cb70023ccd55) --- .../quarkus/cli/common/TargetQuarkusVersionGroup.java | 11 +++++++++-- docs/src/main/asciidoc/cli-tooling.adoc | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java index 5e3b2a1254a62..7444fd2418a98 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java @@ -8,6 +8,8 @@ import picocli.CommandLine.Model.CommandSpec; public class TargetQuarkusVersionGroup { + final static String FULL_EXAMPLE = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID + ":" + + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + ":2.2.0.Final"; StreamCoords streamCoords = null; String validStream = null; @@ -18,7 +20,7 @@ public class TargetQuarkusVersionGroup { CommandSpec spec; @CommandLine.Option(paramLabel = "platformKey:streamId", names = { "-S", - "--stream" }, description = "A target stream, for example:%n io.quarkus.platform:999-SNAPSHOT%n io.quarkus.platform:2.0") + "--stream" }, description = "A target stream, for example:%n io.quarkus.platform:2.0") void setStream(String stream) { stream = stream.trim(); if (!stream.isEmpty()) { @@ -34,7 +36,12 @@ void setStream(String stream) { } @CommandLine.Option(paramLabel = "groupId:artifactId:version", names = { "-P", - "--platform-bom" }, description = "A specific Quarkus platform BOM, for example:%n io.quarkus:quarkus-bom:2.0.0.Final") + "--platform-bom" }, description = "A specific Quarkus platform BOM, for example:%n" + + " " + FULL_EXAMPLE + "%n" + + " io.quarkus::999-SNAPSHOT" + + " 2.2.0.Final%n" + + "Default groupId: " + ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID + "%n" + + "Default artifactId: " + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + "%n") void setPlatformBom(String bom) { bom = bom.replaceFirst("^::", "").trim(); if (!bom.isEmpty()) { diff --git a/docs/src/main/asciidoc/cli-tooling.adoc b/docs/src/main/asciidoc/cli-tooling.adoc index e2a39ca359959..c6ef10ea64b00 100644 --- a/docs/src/main/asciidoc/cli-tooling.adoc +++ b/docs/src/main/asciidoc/cli-tooling.adoc @@ -173,12 +173,12 @@ Both `quarkus create` and `quarkus extension list` allow you to explicitly speci 1. Specify a specific Platform Release BOM + -A https://quarkus.io/guides/platform#quarkus-platform-bom[Quarkus Platform release BOM] is identified by `groupId:artifactId:version` (GAV) coordinates. When specifying a platform release BOM, you may use empty segments to fallback to default values (groupId: `io.quarkus`, artifactId: `quarkus-bom`, version: cli version). If you specify only one segment (no `:`), it is assumed to be a version. +A https://quarkus.io/guides/platform#quarkus-platform-bom[Quarkus Platform release BOM] is identified by `groupId:artifactId:version` (GAV) coordinates. When specifying a platform release BOM, you may use empty segments to fallback to default values (shown with `quarkus create app --help`). If you specify only one segment (no `:`), it is assumed to be a version. + For example: + -- Given the `2.0.0.Final` version of the CLI, specifying `-P :quarkus-bom:` is equivalent to `-P io.quarkus.platform:quarkus-bom:2.0.0.Final`. -- Specifying `-P 999-SNAPSHOT` is equivalent to `-P io.quarkus:quarkus-bom:999-SNAPSHOT` +- With the `2.0.0.Final` version of the CLI, specifying `-P :quarkus-bom:` is equivalent to `-P io.quarkus:quarkus-bom:2.0.0.Final`. Specifying `-P 999-SNAPSHOT` is equivalent to `-P io.quarkus:quarkus-bom:999-SNAPSHOT`. +- With the `2.1.0.Final` version of the CLI, `io.quarkus.platform` is the default group id. Specifying `-P :quarkus-bom:` is equivalent to `-P io.quarkus.platform:quarkus-bom:2.1.0.Final`. Note that you need to specify the group id to work with a snapshot, e.g `-P io.quarkus::999-SNAPSHOT` is equivalent to `-P io.quarkus:quarkus-bom:999-SNAPSHOT`. + Note: default values are subject to change. Using the `--dry-run` option will show you the computed value. From 3644bceffc29da75797fd211a94f3d046ea2944f Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 31 Aug 2021 11:59:36 +0200 Subject: [PATCH 07/60] fix: K8s Service can be re-applied Signed-off-by: Marc Nuri (cherry picked from commit 7142e7913f0f476889b7353432d339ca14b2ad12) --- .../deployment/KubernetesDeployer.java | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java index c358fb29ea50d..362b63ae06357 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java @@ -11,11 +11,12 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; -import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -25,6 +26,7 @@ import io.dekorate.utils.Serialization; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.openshift.api.model.Route; @@ -42,7 +44,6 @@ import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.kubernetes.client.deployment.KubernetesClientErrorHandler; import io.quarkus.kubernetes.client.spi.KubernetesClientBuildItem; -import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; public class KubernetesDeployer { @@ -60,7 +61,7 @@ public void selectDeploymentTarget(ContainerImageInfoBuildItem containerImageInf Optional activeContainerImageCapability = ContainerImageCapabilitiesUtil .getActiveContainerImageCapability(capabilities); - if (!activeContainerImageCapability.isPresent()) { + if (activeContainerImageCapability.isEmpty()) { // we can't thrown an exception here, because it could prevent the Kubernetes resources from being generated return; } @@ -85,10 +86,10 @@ public void deploy(KubernetesClientBuildItem kubernetesClient, return; } - if (!selectedDeploymentTarget.isPresent()) { + if (selectedDeploymentTarget.isEmpty()) { - if (!ContainerImageCapabilitiesUtil - .getActiveContainerImageCapability(capabilities).isPresent()) { + if (ContainerImageCapabilitiesUtil + .getActiveContainerImageCapability(capabilities).isEmpty()) { throw new RuntimeException( "A Kubernetes deployment was requested but no extension was found to build a container image. Consider adding one of following extensions: " + CONTAINER_IMAGE_EXTENSIONS_STR + "."); @@ -158,11 +159,6 @@ private DeploymentTargetEntry determineDeploymentTarget( return selectedTarget; } - private Optional getOptionalDeploymentTarget( - List deploymentTargets, String name) { - return deploymentTargets.stream().filter(d -> name.equals(d.getName())).findFirst(); - } - private DeploymentResultBuildItem deploy(DeploymentTargetEntry deploymentTarget, KubernetesClient client, Path outputDir, OpenshiftConfig openshiftConfig, ApplicationInfoBuildItem applicationInfo) { @@ -174,12 +170,13 @@ private DeploymentResultBuildItem deploy(DeploymentTargetEntry deploymentTarget, try (FileInputStream fis = new FileInputStream(manifest)) { KubernetesList list = Serialization.unmarshalAsList(fis); - distinct(list.getItems()).forEach(i -> { - if (KNATIVE.equals(deploymentTarget.getName().toLowerCase())) { - client.resource(i).inNamespace(namespace).deletingExisting().createOrReplace(); - } else { - client.resource(i).inNamespace(namespace).createOrReplace(); + list.getItems().stream().filter(distinctByResourceKey()).forEach(i -> { + final var r = client.resource(i).inNamespace(namespace); + if (shouldDeleteExisting(deploymentTarget, i)) { + r.delete(); + r.waitUntilCondition(Objects::isNull, 10, TimeUnit.SECONDS); } + r.createOrReplace(); log.info("Applied: " + i.getKind() + " " + i.getMetadata().getName() + "."); }); @@ -221,13 +218,15 @@ private void printExposeInformation(KubernetesClient client, KubernetesList list } } - public static Predicate distictByResourceKey() { + private static boolean shouldDeleteExisting(DeploymentTargetEntry deploymentTarget, HasMetadata resource) { + return KNATIVE.equalsIgnoreCase(deploymentTarget.getName()) + || resource instanceof Service + || (Objects.equals("v1", resource.getApiVersion()) && Objects.equals("Service", resource.getKind())); + } + + private static Predicate distinctByResourceKey() { Map seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(t.getApiVersion() + "/" + t.getKind() + ":" + t.getMetadata().getName(), Boolean.TRUE) == null; } - - private static Collection distinct(Collection resources) { - return resources.stream().filter(distictByResourceKey()).collect(Collectors.toList()); - } } From 4570ea5a511ba5e6987cc1f2187f8c3d2d2a0b0b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 30 Aug 2021 19:07:03 +0100 Subject: [PATCH 08/60] Update OIDC TokenStateManager to return Uni (cherry picked from commit e4d2bcfe681928e87e7309bb3a5c7c9c6d35ef7a) --- .../security-openid-connect-multitenancy.adoc | 34 +- ...ity-openid-connect-web-authentication.adoc | 53 +++ .../oidc/test/CodeFlowDevModeTestCase.java | 10 + .../oidc/test/CustomTokenStateManager.java | 19 +- .../quarkus/oidc/test/ProtectedResource.java | 6 + .../resources/application-dev-mode.properties | 1 + .../io/quarkus/oidc/TenantConfigResolver.java | 10 +- .../io/quarkus/oidc/TokenStateManager.java | 125 ++++++- .../runtime/CodeAuthenticationMechanism.java | 344 ++++++++++++------ .../runtime/DefaultTokenStateManager.java | 16 +- 10 files changed, 470 insertions(+), 148 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc index 0fa38d53ae5df..614f14aa451af 100644 --- a/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc @@ -318,7 +318,9 @@ This interface allows you to dynamically create tenant configurations at runtime package io.quarkus.it.keycloak; import javax.enterprise.context.ApplicationScoped; +import java.util.function.Supplier; +import io.smallrye.mutiny.Uni; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TenantConfigResolver; import io.vertx.ext.web.RoutingContext; @@ -327,7 +329,7 @@ import io.vertx.ext.web.RoutingContext; public class CustomTenantConfigResolver implements TenantConfigResolver { @Override - public OidcTenantConfig resolve(RoutingContext context) { + public Uni resolve(RoutingContext context, TenantConfigResolver.TenantConfigRequestContext requestContext) { String path = context.request().path(); String[] parts = path.split("/"); @@ -337,24 +339,30 @@ public class CustomTenantConfigResolver implements TenantConfigResolver { } if ("tenant-c".equals(parts[1])) { - OidcTenantConfig config = new OidcTenantConfig(); + // Do 'return requestContext.runBlocking(createTenantConfig());' + // if a blocking call is required to create a tenant config + return Uni.createFromItem(createTenantConfig()); + } + + // resolve to default tenant configuration + return null; + } - config.setTenantId("tenant-c"); - config.setAuthServerUrl("http://localhost:8180/auth/realms/tenant-c"); - config.setClientId("multi-tenant-client"); - OidcTenantConfig.Credentials credentials = new OidcTenantConfig.Credentials(); + private Supplier createTenantConfig() { + final OidcTenantConfig config = new OidcTenantConfig(); - credentials.setSecret("my-secret"); + config.setTenantId("tenant-c"); + config.setAuthServerUrl("http://localhost:8180/auth/realms/tenant-c"); + config.setClientId("multi-tenant-client"); + OidcTenantConfig.Credentials credentials = new OidcTenantConfig.Credentials(); - config.setCredentials(credentials); + credentials.setSecret("my-secret"); - // any other setting support by the quarkus-oidc extension + config.setCredentials(credentials); - return config; - } + // any other setting support by the quarkus-oidc extension - // resolve to default tenant configuration - return null; + return () -> config; } } ---- diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index 0a6f47b01151c..62671a68ace68 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -469,6 +469,59 @@ In such cases, you can use `quarkus.oidc.token-state-manager.split-tokens=true` Register your own `io.quarkus.oidc.TokenStateManager' implementation as an `@ApplicationScoped` CDI bean if you need to customize the way the tokens are associated with the session cookie. For example, you may want to keep the tokens in a database and have only a database pointer stored in a session cookie. Note though that it may present some challenges in making the tokens available across multiple microservices nodes. +Here is a simple example: + +[source, java] +---- +package io.quarkus.oidc.test; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.arc.AlternativePriority; +import io.quarkus.oidc.AuthorizationCodeTokens; +import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.TokenStateManager; +import io.quarkus.oidc.runtime.DefaultTokenStateManager; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +@ApplicationScoped +@AlternativePriority(1) +public class CustomTokenStateManager implements TokenStateManager { + + @Inject + DefaultTokenStateManager tokenStateManager; + + @Override + public Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens sessionContent, TokenStateManager.CreateTokenStateRequestContext requestContext) { + return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext) + .map(t -> (t + "|custom")); + } + + @Override + public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, + String tokenState, TokenStateManager.GetTokensRequestContext requestContext) { + if (!tokenState.endsWith("|custom")) { + throw new IllegalStateException(); + } + String defaultState = tokenState.substring(0, tokenState.length() - 7); + return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState, requestContext); + } + + @Override + public Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + TokenStateManager.DeleteTokensRequestContext requestContext) { + if (!tokenState.endsWith("|custom")) { + throw new IllegalStateException(); + } + String defaultState = tokenState.substring(0, tokenState.length() - 7); + return tokenStateManager.deleteTokens(routingContext, oidcConfig, defaultState, requestContext); + } +} +---- + == Listening to important authentication events One can register `@ApplicationScoped` bean which will observe important OIDC authentication events. The listener will be updated when a user has logged in for the first time or re-authenticated, as well as when the session has been refreshed. More events may be reported in the future. For example: diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java index 1d55d0df736ee..ed0704e02aae7 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java @@ -1,10 +1,12 @@ package io.quarkus.oidc.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import java.net.URI; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -14,6 +16,8 @@ import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; @@ -87,6 +91,12 @@ public void testAccessAndRefreshTokenInjectionDevMode() throws IOException, Inte assertEquals("custom", page.getWebClient().getCookieManager().getCookie("q_session").getValue().split("\\|")[3]); + webClient.getOptions().setRedirectEnabled(false); + WebResponse webResponse = webClient + .loadWebResponse(new WebRequest(URI.create("http://localhost:8080/protected/logout").toURL())); + assertEquals(302, webResponse.getStatusCode()); + assertNull(webClient.getCookieManager().getCookie("q_session")); + webClient.getCookieManager().clearCookies(); } } diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTokenStateManager.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTokenStateManager.java index 220d89e1ff0ed..4a57c25770a12 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTokenStateManager.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTokenStateManager.java @@ -8,6 +8,7 @@ import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TokenStateManager; import io.quarkus.oidc.runtime.DefaultTokenStateManager; +import io.smallrye.mutiny.Uni; import io.vertx.ext.web.RoutingContext; @ApplicationScoped @@ -18,25 +19,29 @@ public class CustomTokenStateManager implements TokenStateManager { DefaultTokenStateManager tokenStateManager; @Override - public String createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, - AuthorizationCodeTokens sessionContent) { - return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent) + "|custom"; + public Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens sessionContent, TokenStateManager.CreateTokenStateRequestContext requestContext) { + return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext) + .map(t -> (t + "|custom")); } @Override - public AuthorizationCodeTokens getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, - String tokenState) { + public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, + String tokenState, TokenStateManager.GetTokensRequestContext requestContext) { if (!tokenState.endsWith("|custom")) { throw new IllegalStateException(); } String defaultState = tokenState.substring(0, tokenState.length() - 7); - return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState); + return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState, requestContext); } @Override - public void deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState) { + public Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + TokenStateManager.DeleteTokensRequestContext requestContext) { if (!tokenState.endsWith("|custom")) { throw new IllegalStateException(); } + String defaultState = tokenState.substring(0, tokenState.length() - 7); + return tokenStateManager.deleteTokens(routingContext, oidcConfig, defaultState, requestContext); } } diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResource.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResource.java index fa87a328bd5c8..9c1c73e03987b 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResource.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/ProtectedResource.java @@ -28,4 +28,10 @@ public String getName() { public String getTenantName(@PathParam("id") String tenantId) { return tenantId + ":" + idToken.getName(); } + + @GET + @Path("logout") + public void logout() { + throw new RuntimeException("Logout must be handled by CodeAuthenticationMechanism"); + } } diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties index 15b1c51e72f52..ee7c873dd6e01 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties @@ -5,6 +5,7 @@ quarkus.oidc.client-id=client-dev quarkus.oidc.credentials.client-secret.provider.name=vault-secret-provider quarkus.oidc.credentials.client-secret.provider.key=secret-from-vault quarkus.oidc.application-type=web-app +quarkus.oidc.logout.path=/protected/logout quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java index c0f9143cef2f9..59fa463895eed 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java @@ -21,10 +21,12 @@ public interface TenantConfigResolver { * * @param context the routing context * @return the tenant configuration. If {@code null}, indicates that the default configuration/tenant should be chosen + * + * @deprecated Use {@link #resolve(RoutingContext, TenantConfigRequestContext))} instead. */ @Deprecated default OidcTenantConfig resolve(RoutingContext context) { - throw new UnsupportedOperationException("resolve not implemented"); + throw new UnsupportedOperationException("resolve is not implemented"); } /** @@ -39,10 +41,10 @@ default Uni resolve(RoutingContext routingContext, TenantConfi } /** - * A context object that can be used to run blocking tasks + * A context object that can be used to run blocking tasks. *

- * Blocking config providers should used this context object to run blocking tasks, to prevent excessive and - * unnecessary delegation to thread pools + * Blocking {@code TenantConfigResolver} providers should use this context object to run blocking tasks, to prevent + * excessive and unnecessary delegation to thread pools. */ interface TenantConfigRequestContext { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenStateManager.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenStateManager.java index 94cf60539cc73..ecda06f10e225 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenStateManager.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenStateManager.java @@ -1,5 +1,8 @@ package io.quarkus.oidc; +import java.util.function.Supplier; + +import io.smallrye.mutiny.Uni; import io.vertx.ext.web.RoutingContext; /** @@ -13,9 +16,125 @@ */ public interface TokenStateManager { - String createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, AuthorizationCodeTokens tokens); + /** + * Convert the authorization code flow tokens into a token state. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the authorization code flow tokens + * + * @return the token state + * + * @deprecated Use + * {@link #createTokenState(RoutingContext, OidcTenantConfig, AuthorizationCodeTokens, CreateTokenStateRequestContext)} + * + */ + @Deprecated + default String createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens tokens) { + throw new UnsupportedOperationException("createTokenState is not implemented"); + } + + /** + * Convert the authorization code flow tokens into a token state. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the authorization code flow tokens + * @param requestContext the request context + * + * @return the token state + */ + default Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens tokens, CreateTokenStateRequestContext requestContext) { + return Uni.createFrom().item(createTokenState(routingContext, oidcConfig, tokens)); + } + + /** + * Convert the token state into the authorization code flow tokens. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the token state + * + * @return the authorization code flow tokens + * + * @deprecated Use {@link #getTokens(RoutingContext, OidcTenantConfig, String, GetTokensRequestContext)} instead. + */ + @Deprecated + default AuthorizationCodeTokens getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState) { + throw new UnsupportedOperationException("getTokens is not implemented"); + } + + /** + * Convert the token state into the authorization code flow tokens. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the token state + * @param requestContext the request context + * + * @return the authorization code flow tokens + */ + default Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, + String tokenState, GetTokensRequestContext requestContext) { + return Uni.createFrom().item(getTokens(routingContext, oidcConfig, tokenState)); + } + + /** + * Delete the token state. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the token state + * + * @deprecated Use {@link #deleteTokens(RoutingContext, OidcTenantConfig, String, DeleteTokensRequestContext)} instead + */ + @Deprecated + default void deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState) { + throw new UnsupportedOperationException("deleteTokens is not implemented"); + } + + /** + * Delete the token state. + * + * @param routingContext the request context + * @param oidcConfig the tenant configuration + * @param tokens the token state + */ + default Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + DeleteTokensRequestContext requestContext) { + deleteTokens(routingContext, oidcConfig, tokenState); + return Uni.createFrom().voidItem(); + } + + /** + * A context object that can be used to create a token state by running a blocking task. + *

+ * Blocking providers should use this context to prevent excessive and unnecessary delegation to thread pools. + */ + interface CreateTokenStateRequestContext { + + Uni runBlocking(Supplier function); + } + + /** + * A context object that can be used to convert the token state to the tokens by running a blocking task. + *

+ * Blocking providers should use this context to prevent excessive and unnecessary delegation to thread pools. + */ + interface GetTokensRequestContext { + + Uni runBlocking(Supplier function); + } - AuthorizationCodeTokens getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState); + /** + * A context object that can be used to delete the token state by running a blocking task. + *

+ * Blocking providers should use this context to prevent excessive and unnecessary delegation to thread pools. + */ + interface DeleteTokensRequestContext { - void deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState); + Uni runBlocking(Supplier function); + } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index cb4bb8ccd7701..8e6e5e656fa49 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -10,7 +10,9 @@ import java.util.Optional; import java.util.UUID; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Pattern; import org.jboss.logging.Logger; @@ -23,8 +25,11 @@ import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.OidcTenantConfig.Authentication; import io.quarkus.oidc.SecurityEvent; +import io.quarkus.oidc.TokenStateManager; import io.quarkus.oidc.common.runtime.OidcCommonUtils; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.runtime.BlockingOperationControl; +import io.quarkus.runtime.ExecutorRecorder; import io.quarkus.security.AuthenticationCompletionException; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.AuthenticationRedirectException; @@ -32,6 +37,7 @@ import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.ChallengeData; import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.subscription.UniEmitter; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.impl.CookieImpl; @@ -47,12 +53,17 @@ public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMecha static final Pattern COOKIE_PATTERN = Pattern.compile("\\" + COOKIE_DELIM); static final String SESSION_COOKIE_NAME = "q_session"; static final String SESSION_MAX_AGE_PARAM = "session-max-age"; + static final Uni VOID_UNI = Uni.createFrom().voidItem(); private static final Logger LOG = Logger.getLogger(CodeAuthenticationMechanism.class); private static final String STATE_COOKIE_NAME = "q_auth"; private static final String POST_LOGOUT_COOKIE_NAME = "q_post_logout"; + private final CreateTokenStateRequestContext createTokenStateRequestContext = new CreateTokenStateRequestContext(); + private final GetTokensRequestContext getTokenStateRequestContext = new GetTokensRequestContext(); + private final DeleteTokensRequestContext deleteTokensRequestContext = new DeleteTokensRequestContext(); + public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { return resolver.resolveConfig(context).chain(new Function>() { @@ -96,51 +107,60 @@ private Uni reAuthenticate(Cookie sessionCookie, IdentityProviderManager identityProviderManager, TenantConfigContext configContext) { - AuthorizationCodeTokens session = resolver.getTokenStateManager().getTokens(context, configContext.oidcConfig, - sessionCookie.getValue()); - - context.put(OidcConstants.ACCESS_TOKEN_VALUE, session.getAccessToken()); - context.put(AuthorizationCodeTokens.class.getName(), session); - return authenticate(identityProviderManager, context, new IdTokenCredential(session.getIdToken(), context)) - .map(new Function() { - @Override - public SecurityIdentity apply(SecurityIdentity identity) { - if (isLogout(context, configContext)) { - fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity); - throw redirectToLogoutEndpoint(context, configContext, session.getIdToken()); - } - return identity; - } - }).onFailure().recoverWithUni(new Function>() { + return resolver.getTokenStateManager().getTokens(context, configContext.oidcConfig, + sessionCookie.getValue(), getTokenStateRequestContext) + .chain(new Function>() { @Override - public Uni apply(Throwable t) { - if (t instanceof AuthenticationRedirectException) { - throw (AuthenticationRedirectException) t; - } + public Uni apply(AuthorizationCodeTokens session) { + context.put(OidcConstants.ACCESS_TOKEN_VALUE, session.getAccessToken()); + context.put(AuthorizationCodeTokens.class.getName(), session); + return authenticate(identityProviderManager, context, + new IdTokenCredential(session.getIdToken(), context)) + .call(new Function>() { + @Override + public Uni apply(SecurityIdentity identity) { + if (isLogout(context, configContext)) { + fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity); + return buildLogoutRedirectUriUni(context, configContext, + session.getIdToken()); + } + return VOID_UNI; + } + }).onFailure() + .recoverWithUni(new Function>() { + @Override + public Uni apply(Throwable t) { + if (t instanceof AuthenticationRedirectException) { + throw (AuthenticationRedirectException) t; + } - if (!(t instanceof TokenAutoRefreshException)) { - boolean expired = (t.getCause() instanceof InvalidJwtException) - && ((InvalidJwtException) t.getCause()).hasErrorCode(ErrorCodes.EXPIRED); + if (!(t instanceof TokenAutoRefreshException)) { + boolean expired = (t.getCause() instanceof InvalidJwtException) + && ((InvalidJwtException) t.getCause()) + .hasErrorCode(ErrorCodes.EXPIRED); - if (!expired) { - LOG.debugf("Authentication failure: %s", t.getCause()); - throw new AuthenticationCompletionException(t.getCause()); - } - if (!configContext.oidcConfig.token.refreshExpired) { - LOG.debug("Token has expired, token refresh is not allowed"); - throw new AuthenticationCompletionException(t.getCause()); - } - LOG.debug("Token has expired, trying to refresh it"); - return refreshSecurityIdentity(configContext, session.getRefreshToken(), context, - identityProviderManager, false, null); - } else { - return refreshSecurityIdentity(configContext, session.getRefreshToken(), context, - identityProviderManager, true, - ((TokenAutoRefreshException) t).getSecurityIdentity()); - } + if (!expired) { + LOG.debugf("Authentication failure: %s", t.getCause()); + throw new AuthenticationCompletionException(t.getCause()); + } + if (!configContext.oidcConfig.token.refreshExpired) { + LOG.debug("Token has expired, token refresh is not allowed"); + throw new AuthenticationCompletionException(t.getCause()); + } + LOG.debug("Token has expired, trying to refresh it"); + return refreshSecurityIdentity(configContext, session.getRefreshToken(), + context, + identityProviderManager, false, null); + } else { + return refreshSecurityIdentity(configContext, session.getRefreshToken(), + context, + identityProviderManager, true, + ((TokenAutoRefreshException) t).getSecurityIdentity()); + } + } + }); } }); - } private boolean isJavaScript(RoutingContext context) { @@ -168,54 +188,63 @@ public Uni apply(TenantConfigContext tenantContext) { } public Uni getChallengeInternal(RoutingContext context, TenantConfigContext configContext) { - removeCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)); - - if (!shouldAutoRedirect(configContext, context)) { - // If the client (usually an SPA) wants to handle the redirect manually, then - // return status code 499 and WWW-Authenticate header with the 'OIDC' value. - return Uni.createFrom().item(new ChallengeData(499, "WWW-Authenticate", "OIDC")); - } - - StringBuilder codeFlowParams = new StringBuilder(); - - // response_type - codeFlowParams.append(OidcConstants.CODE_FLOW_RESPONSE_TYPE).append(EQ).append(OidcConstants.CODE_FLOW_CODE); - - // client_id - codeFlowParams.append(AMP).append(OidcConstants.CLIENT_ID).append(EQ) - .append(OidcCommonUtils.urlEncode(configContext.oidcConfig.clientId.get())); - - // scope - List scopes = new ArrayList<>(); - scopes.add("openid"); - configContext.oidcConfig.getAuthentication().scopes.ifPresent(scopes::addAll); - codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ) - .append(OidcCommonUtils.urlEncode(String.join(" ", scopes))); - - // redirect_uri - String redirectPath = getRedirectPath(configContext, context); - String redirectUriParam = buildUri(context, isForceHttps(configContext), redirectPath); - LOG.debugf("Authentication request redirect_uri parameter: %s", redirectUriParam); + return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)) + .chain(new Function>() { - codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_REDIRECT_URI).append(EQ) - .append(OidcCommonUtils.urlEncode(redirectUriParam)); + @Override + public Uni apply(Void t) { + if (!shouldAutoRedirect(configContext, context)) { + // If the client (usually an SPA) wants to handle the redirect manually, then + // return status code 499 and WWW-Authenticate header with the 'OIDC' value. + return Uni.createFrom().item(new ChallengeData(499, "WWW-Authenticate", "OIDC")); + } - // state - codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_STATE).append(EQ) - .append(generateCodeFlowState(context, configContext, redirectPath)); + StringBuilder codeFlowParams = new StringBuilder(); + + // response_type + codeFlowParams.append(OidcConstants.CODE_FLOW_RESPONSE_TYPE).append(EQ) + .append(OidcConstants.CODE_FLOW_CODE); + + // client_id + codeFlowParams.append(AMP).append(OidcConstants.CLIENT_ID).append(EQ) + .append(OidcCommonUtils.urlEncode(configContext.oidcConfig.clientId.get())); + + // scope + List scopes = new ArrayList<>(); + scopes.add("openid"); + configContext.oidcConfig.getAuthentication().scopes.ifPresent(scopes::addAll); + codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ) + .append(OidcCommonUtils.urlEncode(String.join(" ", scopes))); + + // redirect_uri + String redirectPath = getRedirectPath(configContext, context); + String redirectUriParam = buildUri(context, isForceHttps(configContext), redirectPath); + LOG.debugf("Authentication request redirect_uri parameter: %s", redirectUriParam); + + codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_REDIRECT_URI).append(EQ) + .append(OidcCommonUtils.urlEncode(redirectUriParam)); + + // state + codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_STATE).append(EQ) + .append(generateCodeFlowState(context, configContext, redirectPath)); + + // extra redirect parameters, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequests + if (configContext.oidcConfig.authentication.getExtraParams() != null) { + for (Map.Entry entry : configContext.oidcConfig.authentication.getExtraParams() + .entrySet()) { + codeFlowParams.append(AMP).append(entry.getKey()).append(EQ) + .append(OidcCommonUtils.urlEncode(entry.getValue())); + } + } - // extra redirect parameters, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequests - if (configContext.oidcConfig.authentication.getExtraParams() != null) { - for (Map.Entry entry : configContext.oidcConfig.authentication.getExtraParams().entrySet()) { - codeFlowParams.append(AMP).append(entry.getKey()).append(EQ) - .append(OidcCommonUtils.urlEncode(entry.getValue())); - } - } + String authorizationURL = configContext.provider.getMetadata().getAuthorizationUri() + "?" + + codeFlowParams.toString(); - String authorizationURL = configContext.provider.getMetadata().getAuthorizationUri() + "?" + codeFlowParams.toString(); + return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.FOUND.code(), HttpHeaders.LOCATION, + authorizationURL)); + } - return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.FOUND.code(), HttpHeaders.LOCATION, - authorizationURL)); + }); } private Uni performCodeFlow(IdentityProviderManager identityProviderManager, @@ -278,12 +307,16 @@ public Uni apply(final AuthorizationCodeTokens tokens, final T return authenticate(identityProviderManager, context, new IdTokenCredential(tokens.getIdToken(), context)) + .call(new Function>() { + @Override + public Uni apply(SecurityIdentity identity) { + return processSuccessfulAuthentication(context, configContext, + tokens, identity); + } + }) .map(new Function() { @Override public SecurityIdentity apply(SecurityIdentity identity) { - processSuccessfulAuthentication(context, configContext, - tokens, identity); - boolean removeRedirectParams = configContext.oidcConfig.authentication .isRemoveRedirectParameters(); if (removeRedirectParams || finalUserPath != null @@ -325,31 +358,48 @@ public Throwable apply(Throwable tInner) { } - private void processSuccessfulAuthentication(RoutingContext context, + private Uni processSuccessfulAuthentication(RoutingContext context, TenantConfigContext configContext, AuthorizationCodeTokens tokens, SecurityIdentity securityIdentity) { - removeCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)); + return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)) + .chain(new Function>() { - JsonObject idToken = OidcUtils.decodeJwtContent(tokens.getIdToken()); + @Override + public Uni apply(Void t) { + JsonObject idToken = OidcUtils.decodeJwtContent(tokens.getIdToken()); - if (!idToken.containsKey("exp") || !idToken.containsKey("iat")) { - LOG.debug("ID Token is required to contain 'exp' and 'iat' claims"); - throw new AuthenticationCompletionException(); - } - long maxAge = idToken.getLong("exp") - idToken.getLong("iat"); - if (configContext.oidcConfig.token.lifespanGrace.isPresent()) { - maxAge += configContext.oidcConfig.token.lifespanGrace.getAsInt(); - } - if (configContext.oidcConfig.token.refreshExpired) { - maxAge += configContext.oidcConfig.authentication.sessionAgeExtension.getSeconds(); - } - context.put(SESSION_MAX_AGE_PARAM, maxAge); - String cookieValue = resolver.getTokenStateManager() - .createTokenState(context, configContext.oidcConfig, tokens); - createCookie(context, configContext.oidcConfig, getSessionCookieName(configContext.oidcConfig), cookieValue, maxAge); + if (!idToken.containsKey("exp") || !idToken.containsKey("iat")) { + LOG.debug("ID Token is required to contain 'exp' and 'iat' claims"); + throw new AuthenticationCompletionException(); + } + long maxAge = idToken.getLong("exp") - idToken.getLong("iat"); + if (configContext.oidcConfig.token.lifespanGrace.isPresent()) { + maxAge += configContext.oidcConfig.token.lifespanGrace.getAsInt(); + } + if (configContext.oidcConfig.token.refreshExpired) { + maxAge += configContext.oidcConfig.authentication.sessionAgeExtension.getSeconds(); + } + final long sessionMaxAge = maxAge; + context.put(SESSION_MAX_AGE_PARAM, maxAge); + return resolver.getTokenStateManager() + .createTokenState(context, configContext.oidcConfig, tokens, createTokenStateRequestContext) + .map(new Function() { + + @Override + public Void apply(String cookieValue) { + createCookie(context, configContext.oidcConfig, + getSessionCookieName(configContext.oidcConfig), + cookieValue, sessionMaxAge); + fireEvent(SecurityEvent.Type.OIDC_LOGIN, securityIdentity); + return null; + } + + }); + } + + }); - fireEvent(SecurityEvent.Type.OIDC_LOGIN, securityIdentity); } private void fireEvent(SecurityEvent.Type eventType, SecurityIdentity securityIdentity) { @@ -441,14 +491,24 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho .toString(); } - private void removeCookie(RoutingContext context, TenantConfigContext configContext, String cookieName) { + private Uni removeSessionCookie(RoutingContext context, TenantConfigContext configContext, String cookieName) { + String cookieValue = removeCookie(context, configContext, cookieName); + if (cookieValue != null) { + return resolver.getTokenStateManager().deleteTokens(context, configContext.oidcConfig, cookieValue, + deleteTokensRequestContext); + } else { + return VOID_UNI; + } + } + + private String removeCookie(RoutingContext context, TenantConfigContext configContext, String cookieName) { ServerCookie cookie = (ServerCookie) context.cookieMap().get(cookieName); + String cookieValue = null; if (cookie != null) { - if (SESSION_COOKIE_NAME.equals(cookieName)) { - resolver.getTokenStateManager().deleteTokens(context, configContext.oidcConfig, cookie.getValue()); - } + cookieValue = cookie.getValue(); removeCookie(context, cookie, configContext.oidcConfig); } + return cookieValue; } static void removeCookie(RoutingContext context, ServerCookie cookie, OidcTenantConfig oidcConfig) { @@ -500,12 +560,17 @@ public Uni apply(final AuthorizationCodeTokens tokens, final T return authenticate(identityProviderManager, context, new IdTokenCredential(tokens.getIdToken(), context)) - .map(new Function() { + .call(new Function>() { @Override - public SecurityIdentity apply(SecurityIdentity identity) { + public Uni apply(SecurityIdentity identity) { // after a successful refresh, rebuild the identity and update the cookie - processSuccessfulAuthentication(context, configContext, + return processSuccessfulAuthentication(context, configContext, tokens, identity); + } + }) + .map(new Function() { + @Override + public SecurityIdentity apply(SecurityIdentity identity) { fireEvent(autoRefresh ? SecurityEvent.Type.OIDC_SESSION_REFRESHED : SecurityEvent.Type.OIDC_SESSION_EXPIRED_AND_REFRESHED, identity); @@ -555,10 +620,15 @@ private boolean isForceHttps(TenantConfigContext configContext) { return configContext.oidcConfig.authentication.forceRedirectHttpsScheme; } - private AuthenticationRedirectException redirectToLogoutEndpoint(RoutingContext context, TenantConfigContext configContext, + private Uni buildLogoutRedirectUriUni(RoutingContext context, TenantConfigContext configContext, String idToken) { - removeCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)); - return new AuthenticationRedirectException(buildLogoutRedirectUri(configContext, idToken, context)); + return removeSessionCookie(context, configContext, getSessionCookieName(configContext.oidcConfig)) + .map(new Function() { + @Override + public Void apply(Void t) { + throw new AuthenticationRedirectException(buildLogoutRedirectUri(configContext, idToken, context)); + } + }); } private static String getStateCookieName(TenantConfigContext configContext) { @@ -580,4 +650,48 @@ static String getCookieSuffix(String tenantId) { return !"Default".equals(tenantId) ? "_" + tenantId : ""; } + private static class CreateTokenStateRequestContext extends BlockingTaskRunner + implements TokenStateManager.CreateTokenStateRequestContext { + } + + private static class GetTokensRequestContext extends BlockingTaskRunner + implements TokenStateManager.GetTokensRequestContext { + } + + private static class DeleteTokensRequestContext extends BlockingTaskRunner + implements TokenStateManager.DeleteTokensRequestContext { + } + + private static class BlockingTaskRunner { + public Uni runBlocking(Supplier function) { + return Uni.createFrom().deferred(new Supplier>() { + @Override + public Uni get() { + if (BlockingOperationControl.isBlockingAllowed()) { + try { + return Uni.createFrom().item(function.get()); + } catch (Throwable t) { + return Uni.createFrom().failure(t); + } + } else { + return Uni.createFrom().emitter(new Consumer>() { + @Override + public void accept(UniEmitter uniEmitter) { + ExecutorRecorder.getCurrent().execute(new Runnable() { + @Override + public void run() { + try { + uniEmitter.complete(function.get()); + } catch (Throwable t) { + uniEmitter.fail(t); + } + } + }); + } + }); + } + } + }); + } + } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java index 2b0a951258c24..b5f05b044882d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java @@ -5,6 +5,7 @@ import io.quarkus.oidc.AuthorizationCodeTokens; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.TokenStateManager; +import io.smallrye.mutiny.Uni; import io.vertx.core.http.Cookie; import io.vertx.core.http.impl.ServerCookie; import io.vertx.ext.web.RoutingContext; @@ -16,8 +17,8 @@ public class DefaultTokenStateManager implements TokenStateManager { private static final String SESSION_RT_COOKIE_NAME = CodeAuthenticationMechanism.SESSION_COOKIE_NAME + "_rt"; @Override - public String createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, - AuthorizationCodeTokens tokens) { + public Uni createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig, + AuthorizationCodeTokens tokens, TokenStateManager.CreateTokenStateRequestContext requestContext) { StringBuilder sb = new StringBuilder(); sb.append(tokens.getIdToken()); if (oidcConfig.tokenStateManager.strategy == OidcTenantConfig.TokenStateManager.Strategy.KEEP_ALL_TOKENS) { @@ -56,11 +57,12 @@ public String createTokenState(RoutingContext routingContext, OidcTenantConfig o } } } - return sb.toString(); + return Uni.createFrom().item(sb.toString()); } @Override - public AuthorizationCodeTokens getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState) { + public Uni getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + TokenStateManager.GetTokensRequestContext requestContext) { String[] tokens = CodeAuthenticationMechanism.COOKIE_PATTERN.split(tokenState); String idToken = tokens[0]; @@ -91,17 +93,19 @@ public AuthorizationCodeTokens getTokens(RoutingContext routingContext, OidcTena } } - return new AuthorizationCodeTokens(idToken, accessToken, refreshToken); + return Uni.createFrom().item(new AuthorizationCodeTokens(idToken, accessToken, refreshToken)); } @Override - public void deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState) { + public Uni deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState, + TokenStateManager.DeleteTokensRequestContext requestContext) { if (oidcConfig.tokenStateManager.splitTokens) { CodeAuthenticationMechanism.removeCookie(routingContext, getAccessTokenCookie(routingContext, oidcConfig), oidcConfig); CodeAuthenticationMechanism.removeCookie(routingContext, getRefreshTokenCookie(routingContext, oidcConfig), oidcConfig); } + return CodeAuthenticationMechanism.VOID_UNI; } private static ServerCookie getAccessTokenCookie(RoutingContext routingContext, OidcTenantConfig oidcConfig) { From 0289be4aa6d98b55f23389d0e2ace2b41e4a4ac4 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 1 Sep 2021 18:02:27 +1000 Subject: [PATCH 09/60] Make sure KC Dev UI page persists after reload (cherry picked from commit 03647998696e6a7e202b7e3a07c0ecb2883b5f1e) --- .../devservices/keycloak/KeycloakDevServicesProcessor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index b1a1274ddbc5d..cc02328f820fb 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -80,6 +80,7 @@ public class KeycloakDevServicesProcessor { private static volatile String capturedKeycloakUrl; private static volatile FileTime capturedRealmFileLastModifiedDate; private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); + private static volatile KeycloakDevServicesConfigBuildItem existingDevServiceConfig; @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { IsEnabled.class, GlobalDevServicesConfig.Enabled.class }) public KeycloakDevServicesConfigBuildItem startKeycloakContainer( @@ -108,7 +109,7 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( } } if (!restartRequired) { - return null; + return existingDevServiceConfig; } for (Closeable closeable : closeables) { try { @@ -120,6 +121,7 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer( closeables = null; capturedDevServicesConfiguration = null; capturedKeycloakUrl = null; + existingDevServiceConfig = null; } capturedDevServicesConfiguration = currentDevServicesConfiguration; @@ -199,7 +201,8 @@ private KeycloakDevServicesConfigBuildItem prepareConfiguration(boolean createRe configProperties.put(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret); configProperties.put(OIDC_USERS, users); - return new KeycloakDevServicesConfigBuildItem(configProperties); + existingDevServiceConfig = new KeycloakDevServicesConfigBuildItem(configProperties); + return existingDevServiceConfig; } private StartResult startContainer(boolean useSharedContainer) { From 8feabfcbb0c95c4a0fc00fea07ca5dad2ebeeb81 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Tue, 31 Aug 2021 21:49:53 +0200 Subject: [PATCH 10/60] Do not propagate java compiler argument in kotlin compilation provider (cherry picked from commit d0d2d57c686ea156c03eb7ea865ebb6be70ed6d2) --- .../java/io/quarkus/gradle/QuarkusPlugin.java | 3 ++- .../io/quarkus/gradle/tasks/QuarkusDev.java | 8 +++++- .../build.gradle | 26 +++++++++++++++++++ .../gradle.properties | 4 +++ .../settings.gradle | 18 +++++++++++++ .../main/kotlin/org/acme/GreetingResource.kt | 16 ++++++++++++ ...sicKotlinApplicationModuleDevModeTest.java | 26 +++++++++++++++++++ 7 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle create mode 100644 integration-tests/gradle/src/main/resources/basic-kotlin-application-project/gradle.properties create mode 100644 integration-tests/gradle/src/main/resources/basic-kotlin-application-project/settings.gradle create mode 100644 integration-tests/gradle/src/main/resources/basic-kotlin-application-project/src/main/kotlin/org/acme/GreetingResource.kt create mode 100644 integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 0130082fdd675..fb1b6b8618a33 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -121,7 +121,7 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { Task quarkusBuild = tasks.create(QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class); quarkusBuild.dependsOn(quarkusGenerateCode); - Task quarkusDev = tasks.create(QUARKUS_DEV_TASK_NAME, QuarkusDev.class); + QuarkusDev quarkusDev = tasks.create(QUARKUS_DEV_TASK_NAME, QuarkusDev.class); Task quarkusRemoteDev = tasks.create(QUARKUS_REMOTE_DEV_TASK_NAME, QuarkusRemoteDev.class); Task quarkusTest = tasks.create(QUARKUS_TEST_TASK_NAME, QuarkusTest.class); tasks.create(QUARKUS_TEST_CONFIG_TASK_NAME, QuarkusTestConfig.class); @@ -233,6 +233,7 @@ public void execute(Task test) { }); project.getPlugins().withId("org.jetbrains.kotlin.jvm", plugin -> { + quarkusDev.shouldPropagateJavaCompilerArgs(false); tasks.getByName("compileKotlin").dependsOn(quarkusGenerateCode); tasks.getByName("compileTestKotlin").dependsOn(quarkusGenerateCodeTests); }); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 3c050bb07db45..f28c8bca01aa5 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -74,6 +74,8 @@ public class QuarkusDev extends QuarkusTask { private List compilerArgs = new LinkedList<>(); + private boolean shouldPropagateJavaCompilerArgs = true; + @Inject public QuarkusDev() { super("Development mode: enables hot deployment with background compilation"); @@ -303,7 +305,7 @@ private QuarkusDevModeLauncher newLauncher() throws Exception { builder.targetJavaVersion(javaPluginConvention.getTargetCompatibility().toString()); } - if (getCompilerArgs().isEmpty()) { + if (getCompilerArgs().isEmpty() && shouldPropagateJavaCompilerArgs) { getJavaCompileTask() .map(compileTask -> compileTask.getOptions().getCompilerArgs()) .ifPresent(builder::compilerOptions); @@ -571,4 +573,8 @@ private void addToClassPaths(GradleDevModeLauncher.Builder classPathManifest, Fi classPathManifest.classpathEntry(file); } } + + public void shouldPropagateJavaCompilerArgs(boolean shouldPropagateJavaCompilerArgs) { + this.shouldPropagateJavaCompilerArgs = shouldPropagateJavaCompilerArgs; + } } diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle new file mode 100644 index 0000000000000..6a3efd26bb298 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'io.quarkus' +} + +repositories { + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } + mavenCentral() +} + +dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'io.quarkus:quarkus-resteasy' + implementation 'io.quarkus:quarkus-kotlin' +} + +compileJava { + options.compilerArgs << '-parameters' +} diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/gradle.properties b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/gradle.properties new file mode 100644 index 0000000000000..15d78eaf882e5 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/gradle.properties @@ -0,0 +1,4 @@ +kotlinVersion=1.5.30 + +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformGroupId=io.quarkus diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/settings.gradle b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/settings.gradle new file mode 100644 index 0000000000000..fb99dc799e1e2 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}" + } +} +rootProject.name='code-with-quarkus' diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/src/main/kotlin/org/acme/GreetingResource.kt b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/src/main/kotlin/org/acme/GreetingResource.kt new file mode 100644 index 0000000000000..7634ba1421904 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/src/main/kotlin/org/acme/GreetingResource.kt @@ -0,0 +1,16 @@ +package org.acme + +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Path("/hello") +class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + fun hello(): String { + return "hello" + } +} \ No newline at end of file diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java new file mode 100644 index 0000000000000..a98c32842b24b --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/devmode/BasicKotlinApplicationModuleDevModeTest.java @@ -0,0 +1,26 @@ +package io.quarkus.gradle.devmode; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; + +import com.google.common.collect.ImmutableMap; + +public class BasicKotlinApplicationModuleDevModeTest extends QuarkusDevGradleTestBase { + + @Override + protected String projectDirectoryName() { + return "basic-kotlin-application-project"; + } + + @Override + protected void testDevMode() throws Exception { + assertThat(getHttpResponse("/hello")).contains("hello"); + + final String uuid = UUID.randomUUID().toString(); + replace("src/main/kotlin/org/acme/GreetingResource.kt", + ImmutableMap.of("return \"hello\"", "return \"" + uuid + "\"")); + + assertUpdatedResponseContains("/hello", uuid); + } +} From b0a36a01026226f6026b31b7f88f0be70afabfff Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Tue, 31 Aug 2021 09:18:32 +0200 Subject: [PATCH 11/60] Set default jvm-target in kotlin sample project (cherry picked from commit a77eadb930cb39d3e07a0d009e6a04ba2667246d) --- .../multi-module-kotlin-project/web/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integration-tests/gradle/src/main/resources/multi-module-kotlin-project/web/build.gradle.kts b/integration-tests/gradle/src/main/resources/multi-module-kotlin-project/web/build.gradle.kts index dd4627b9f7240..55afb0a79cdbb 100644 --- a/integration-tests/gradle/src/main/resources/multi-module-kotlin-project/web/build.gradle.kts +++ b/integration-tests/gradle/src/main/resources/multi-module-kotlin-project/web/build.gradle.kts @@ -3,6 +3,12 @@ plugins { kotlin("jvm") } +// This is a fix as kotlin 1.5.30 does not support Java 17 yet +if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { + tasks.quarkusDev { + compilerArgs = listOf("-jvm-target", "16") + } +} dependencies { implementation(project(":port")) implementation(project(":domain")) From 29d59d4e8bb7588c000e071c0c1c8d5350de3f41 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 1 Sep 2021 11:32:03 +0300 Subject: [PATCH 12/60] Fix @NoCache without fields handling in RESTEasy Reactive Fixes: #19822 (cherry picked from commit 07db37f530aa9450874f2e96b31505ab3e39e622) --- .../test/cache/NoCacheOnMethodsTest.java | 40 +++++++++++++------ .../scanning/CacheControlScanner.java | 10 ++--- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/cache/NoCacheOnMethodsTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/cache/NoCacheOnMethodsTest.java index 00662d7c38287..1925a3855d852 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/cache/NoCacheOnMethodsTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/cache/NoCacheOnMethodsTest.java @@ -30,37 +30,53 @@ public JavaArchive get() { }); @Test - public void testWith() { - RestAssured.get("/test/with") + public void testWithFields() { + RestAssured.get("/test/withFields") .then() .statusCode(200) - .body(equalTo("with")) + .body(equalTo("withFields")) .header("Cache-Control", "no-cache=\"f1\", no-cache=\"f2\""); } @Test - public void testWithout() { - RestAssured.get("/test/without") + public void testWithoutFields() { + RestAssured.get("/test/withoutFields") .then() .statusCode(200) - .body(equalTo("without")) + .body(equalTo("withoutFields")) + .header("Cache-Control", "no-cache"); + } + + @Test + public void testWithoutAnnotation() { + RestAssured.get("/test/withoutAnnotation") + .then() + .statusCode(200) + .body(equalTo("withoutAnnotation")) .header("Cache-Control", nullValue()); } @Path("test") public static class ResourceWithNoCache { - @Path("with") + @Path("withFields") @GET @NoCache(fields = { "f1", "f2" }) - public String with() { - return "with"; + public String withFields() { + return "withFields"; + } + + @Path("withoutFields") + @GET + @NoCache + public String withoutFields() { + return "withoutFields"; } - @Path("without") + @Path("withoutAnnotation") @GET - public String without() { - return "without"; + public String withoutAnnotation() { + return "withoutAnnotation"; } } } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java index 5b5ef46790639..6464523f41e22 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/CacheControlScanner.java @@ -61,18 +61,18 @@ private ExtendedCacheControl noCacheToCacheControl(AnnotationInstance noCacheIns if (noCacheInstance == null) { return null; } + ExtendedCacheControl cacheControl = new ExtendedCacheControl(); + cacheControl.setNoCache(true); + cacheControl.setNoTransform(false); AnnotationValue fieldsValue = noCacheInstance.value("fields"); if (fieldsValue != null) { String[] fields = fieldsValue.asStringArray(); if ((fields != null) && (fields.length > 0)) { - ExtendedCacheControl cacheControl = new ExtendedCacheControl(); - cacheControl.setNoCache(true); - cacheControl.setNoTransform(false); cacheControl.getNoCacheFields().addAll(Arrays.asList(fields)); - return cacheControl; + } } - return null; + return cacheControl; } private ExtendedCacheControl cacheToCacheControl(AnnotationInstance cacheInstance) { From 72805e647d04e2f52ad074de9b28342c2fe207b2 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 1 Sep 2021 11:57:07 +0300 Subject: [PATCH 13/60] Mark quarkus-container-image-openshift as stable (cherry picked from commit 12a882fd3a8ebbe136360d20511037336b585a6e) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 5f6b78609781c..9c1ee15c7c8bb 100644 --- a/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,4 +8,4 @@ metadata: - "image" categories: - "cloud" - status: "preview" \ No newline at end of file + status: "stable" From efd47f8a81bce08d1662e569ba678d3fbcc615d3 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 1 Sep 2021 11:31:09 -0300 Subject: [PATCH 14/60] Marking Amazon extensions as stable (cherry picked from commit bac9ee06145d3b1595f1a38eddfcb465820015a1) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/amazon-alexa/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-alexa/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 94df9bef06f78..609bf3df1dd9c 100644 --- a/extensions/amazon-alexa/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-alexa/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,4 +8,4 @@ metadata: categories: - "cloud" guide: "https://quarkus.io/guides/amazon-lambda" - status: "preview" + status: "stable" diff --git a/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml index d26b340b6991f..295b2b80f286c 100644 --- a/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,4 +11,4 @@ metadata: categories: - "cloud" guide: "https://quarkus.io/guides/amazon-lambda-http" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 8caf7277ca0b6..3a755c8ed49a0 100644 --- a/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,4 +11,4 @@ metadata: categories: - "cloud" guide: "https://quarkus.io/guides/amazon-lambda-http" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml index b68cb8deb0e6a..64721449014f4 100644 --- a/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -10,4 +10,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm" categories: - "cloud" - status: "preview" \ No newline at end of file + status: "stable" \ No newline at end of file diff --git a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 4e66c6d21a76a..2c4356bfb26d4 100644 --- a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,7 +8,7 @@ metadata: categories: - "cloud" guide: "https://quarkus.io/guides/amazon-lambda" - status: "preview" + status: "stable" codestart: name: "amazon-lambda" kind: "example" diff --git a/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 4ea5a1d78f630..86c58a06796c5 100644 --- a/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,5 +8,5 @@ metadata: - "amazon" categories: - "data" - status: "preview" + status: "stable" unlisted: true \ No newline at end of file diff --git a/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 16a93626af4f8..17da2de4d91bc 100644 --- a/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -10,4 +10,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-dynamodb" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml index b23a03f1897e5..b7c67762ea2f0 100644 --- a/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-iam" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 37f5135cba20b..64694f803b24d 100644 --- a/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-kms" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 837a6866e3f99..cf0a788472a63 100644 --- a/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-s3" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml index ea0cb367cd76e..5eb9296cd7521 100644 --- a/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-ses" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 507c0dc7dd321..62842e0b8d177 100644 --- a/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-sns" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml index c033c7456bb2d..a2f8ad1d26be9 100644 --- a/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-sqs" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 8e077230c651f..77b04c7bebcb6 100644 --- a/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amazon-ssm" categories: - "data" - status: "preview" \ No newline at end of file + status: "stable" From 7ddf5fe343064950eabd8d3636f89b737e5fcea5 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 1 Sep 2021 18:45:20 +0200 Subject: [PATCH 15/60] Dev UI Test result screen make sure dark background use light test (Fix #19374) Signed-off-by:Phillip Kruger (cherry picked from commit c83c608d31c719968549162bb3ba81fbbf8f126f) --- .../io.quarkus.quarkus-vertx-http/tests.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/tests.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/tests.html index f5e8c77ca9141..bf9981b60745f 100644 --- a/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/tests.html +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/tests.html @@ -87,7 +87,7 @@


-
+
{#for throwable in r.problems}
{throwable.message}
@@ -100,7 +100,7 @@
{#if !r.logOutput.empty}
-
+
{#for log in r.logOutput} {log.raw}
@@ -124,7 +124,7 @@
{#if !r.logOutput.empty}
-
+
{#for log in r.logOutput} {log.raw}
@@ -149,7 +149,7 @@
{#if !r.logOutput.empty}
-
+
{#for log in r.logOutput} {log.raw}
@@ -191,7 +191,7 @@

-
+
{#for log in r.logOutput} {log.raw}
@@ -214,7 +214,7 @@

-
+
{#for log in r.logOutput} {log.raw}
@@ -256,7 +256,7 @@

-
+
{#for log in r.logOutput} {log.raw}
From 8d9d36a3999e7bd190fa88f9d06cf80e5fcc06ac Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 1 Sep 2021 15:14:01 +0300 Subject: [PATCH 16/60] Provide actionable error message when RESTEasy Reactive can't pick Provider constructor Relates to: #19750 Co-authored-by: George Gastaldi (cherry picked from commit 12ca7ebfbf4d170f6775447cb2658db39c7c10f1) --- .../common/runtime/ArcBeanFactory.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcBeanFactory.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcBeanFactory.java index 0808558c0ca57..c44b8bb45458c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcBeanFactory.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ArcBeanFactory.java @@ -22,17 +22,28 @@ public String toString() { @Override public BeanInstance createInstance() { - BeanContainer.Instance instance = factory.create(); - return new BeanInstance() { - @Override - public T getInstance() { - return instance.get(); + BeanContainer.Instance instance; + try { + instance = factory.create(); + return new BeanInstance() { + @Override + public T getInstance() { + return instance.get(); + } + + @Override + public void close() { + instance.close(); + } + }; + } catch (Exception e) { + if (factory.getClass().getName().contains("DefaultInstanceFactory")) { + throw new IllegalArgumentException( + "Unable to create class '" + targetClassName + + "'. To fix the problem, make sure this class is a CDI bean.", + e); } - - @Override - public void close() { - instance.close(); - } - }; + throw e; + } } } From 6e57ce6f2860afec1ef45e18fb6ebaeebd3cd5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Wed, 1 Sep 2021 12:56:32 +0200 Subject: [PATCH 17/60] Rest Client Reactive: Multipart file name set to actual filename for path and file fixes #19805 (cherry picked from commit 55c8f016384ffaf348154498393c98793b5e972c) --- .../JaxrsClientReactiveProcessor.java | 20 +++-- .../MultipartPopulatorGenerator.java | 3 +- .../reactive/MultipartFilenameTest.java | 80 +++++++++++++++++++ 3 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultipartFilenameTest.java diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 815441cbad750..b3335b157c0c4 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -146,6 +146,9 @@ public class JaxrsClientReactiveProcessor { String.class, Object.class); private static final MethodDescriptor MULTIVALUED_MAP_ADD = MethodDescriptor.ofMethod(MultivaluedMap.class, "add", void.class, Object.class, Object.class); + private static final MethodDescriptor PATH_GET_FILENAME = MethodDescriptor.ofMethod(Path.class, "getFileName", + Path.class); + private static final MethodDescriptor OBJECT_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); static final DotName CONTINUATION = DotName.createSimple("kotlin.coroutines.Continuation"); private static final DotName UNI_KT = DotName.createSimple("io.smallrye.mutiny.coroutines.UniKt"); @@ -989,7 +992,7 @@ private ResultHandle createMultipartForm(MethodCreator methodCreator, ResultHand formClass.name() + "." + field.name()); } ResultHandle filePath = methodCreator.invokeVirtualMethod( - MethodDescriptor.ofMethod(File.class, "getPath", String.class), fieldValue); + MethodDescriptor.ofMethod(File.class, "toPath", Path.class), fieldValue); addFile(methodCreator, multipartForm, formParamName, partType, filePath); } else if (is(PATH, fieldClass, index)) { // and so is path @@ -998,9 +1001,7 @@ private ResultHandle createMultipartForm(MethodCreator methodCreator, ResultHand "No @PartType annotation found on multipart form field of type Path: " + formClass.name() + "." + field.name()); } - ResultHandle filePath = methodCreator.invokeInterfaceMethod( - MethodDescriptor.ofMethod(Path.class, "toString", String.class), fieldValue); - addFile(methodCreator, multipartForm, formParamName, partType, filePath); + addFile(methodCreator, multipartForm, formParamName, partType, fieldValue); } else if (is(BUFFER, fieldClass, index)) { // and buffer addBuffer(methodCreator, multipartForm, formParamName, partType, fieldValue, field); @@ -1047,6 +1048,9 @@ private ResultHandle createMultipartForm(MethodCreator methodCreator, ResultHand */ private void addFile(MethodCreator methodCreator, AssignableResultHandle multipartForm, String formParamName, String partType, ResultHandle filePath) { + ResultHandle fileNamePath = methodCreator.invokeInterfaceMethod(PATH_GET_FILENAME, filePath); + ResultHandle fileName = methodCreator.invokeVirtualMethod(OBJECT_TO_STRING, fileNamePath); + ResultHandle pathString = methodCreator.invokeVirtualMethod(OBJECT_TO_STRING, filePath); if (partType.equalsIgnoreCase(MediaType.APPLICATION_OCTET_STREAM)) { methodCreator.assign(multipartForm, // MultipartForm#binaryFileUpload(String name, String filename, String pathname, String mediaType); @@ -1055,8 +1059,8 @@ private void addFile(MethodCreator methodCreator, AssignableResultHandle multipa MethodDescriptor.ofMethod(MultipartForm.class, "binaryFileUpload", MultipartForm.class, String.class, String.class, String.class, String.class), - multipartForm, methodCreator.load(formParamName), methodCreator.load(formParamName), - filePath, methodCreator.load(partType))); + multipartForm, methodCreator.load(formParamName), fileName, + pathString, methodCreator.load(partType))); } else { methodCreator.assign(multipartForm, // MultipartForm#textFileUpload(String name, String filename, String pathname, String mediaType);; @@ -1065,8 +1069,8 @@ private void addFile(MethodCreator methodCreator, AssignableResultHandle multipa MethodDescriptor.ofMethod(MultipartForm.class, "textFileUpload", MultipartForm.class, String.class, String.class, String.class, String.class), - multipartForm, methodCreator.load(formParamName), methodCreator.load(formParamName), - filePath, methodCreator.load(partType))); + multipartForm, methodCreator.load(formParamName), fileName, + pathString, methodCreator.load(partType))); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/MultipartPopulatorGenerator.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/MultipartPopulatorGenerator.java index e02091633f902..b9353e698a05e 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/MultipartPopulatorGenerator.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/MultipartPopulatorGenerator.java @@ -189,7 +189,8 @@ private MultipartPopulatorGenerator() { */ static String generate(ClassInfo multipartClassInfo, ClassOutput classOutput, IndexView index) { if (!multipartClassInfo.hasNoArgsConstructor()) { - throw new IllegalArgumentException("Classes annotated with '@MultipartForm' must contain a no-args constructor"); + throw new IllegalArgumentException("Classes annotated with '@MultipartForm' must contain a no-args constructor. " + + "The constructor is missing on " + multipartClassInfo.name()); } String multipartClassName = multipartClassInfo.name().toString(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultipartFilenameTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultipartFilenameTest.java new file mode 100644 index 0000000000000..89ca96c51be37 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultipartFilenameTest.java @@ -0,0 +1,80 @@ +package io.quarkus.rest.client.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.jboss.resteasy.reactive.MultipartForm; +import org.jboss.resteasy.reactive.PartType; +import org.jboss.resteasy.reactive.multipart.FileUpload; +import org.jboss.shrinkwrap.api.ShrinkWrap; +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.quarkus.test.common.http.TestHTTPResource; + +public class MultipartFilenameTest { + + @TestHTTPResource + URI baseUri; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class, FormData.class, Client.class, ClientForm.class)) + .withConfigurationResource("dependent-test-application.properties"); + + @Test + void shouldPassOriginalFileName() throws IOException { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + + File file = File.createTempFile("MultipartTest", ".txt"); + file.deleteOnExit(); + + ClientForm form = new ClientForm(); + form.file = file; + assertThat(client.postMultipart(form)).isEqualTo(file.getName()); + } + + @Path("/multipart") + @ApplicationScoped + public static class Resource { + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + public String upload(@MultipartForm FormData form) { + return form.myFile.fileName(); + } + } + + public static class FormData { + @FormParam("myFile") + public FileUpload myFile; + + } + + @Path("/multipart") + public interface Client { + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + String postMultipart(@MultipartForm ClientForm clientForm); + + } + + public static class ClientForm { + @FormParam("myFile") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + public File file; + } +} From 00128b64f44e8914f500653ddbbddcf9a9700a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Wed, 1 Sep 2021 22:34:57 +0200 Subject: [PATCH 18/60] Rest Client Reactive: improve error on invalid subresource return type (cherry picked from commit 0b18024b6f10fea094688f37540ad03c90ab5a9f) --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index b3335b157c0c4..e843d538face9 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -505,7 +505,11 @@ A more full example of generated client (with sub-resource) can is at the bottom ClassInfo subResourceInterface = index.getClassByName(returnType.name()); if (!Modifier.isInterface(subResourceInterface.flags())) { throw new IllegalArgumentException( - "Sub resource type is not an interface: " + returnType.name().toString()); + "Client interface method: " + jandexMethod.declaringClass().name() + "#" + jandexMethod + + " has no HTTP method annotation (@GET, @POST, etc) and it's return type: " + + returnType.name().toString() + " is not an interface. " + + "If it's a sub resource method, it has to return an interface. " + + "If it's not, it has to have one of the HTTP method annotations."); } // generate implementation for a method from the jaxrs interface: MethodCreator methodCreator = c.getMethodCreator(method.getName(), method.getSimpleReturnType(), From 47f08ca6a60d24c33ecd643c0bbe849deaa2c822 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 31 Aug 2021 12:50:18 +0200 Subject: [PATCH 19/60] Codestarts - gRPC - add index.html - resolves #19642 (cherry picked from commit 57d2f7cfd67c7690b7aba46b24360c43573fa52d) --- .../main/resources/META-INF/resources/index.entry.qute.html | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html diff --git a/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html new file mode 100644 index 0000000000000..0317faba62263 --- /dev/null +++ b/devtools/project-core-extension-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/grpc-codestart/base/src/main/resources/META-INF/resources/index.entry.qute.html @@ -0,0 +1,3 @@ +{#include index-entry} +{#body}This codestart implements a simple gRPC service that can be tested in the Dev UI (available in dev mode only). +{/include} \ No newline at end of file From e93fbd703f43ffeae802c84f6f92042d12dde00c Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 1 Sep 2021 08:22:36 +0200 Subject: [PATCH 20/60] Update the status of the mutiny, AMQP connector and gRPC extensions to "stable". (cherry picked from commit b1d3c9d961334bd88a61ee91eef59305d5ae3960) --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 5605cd44a5031..e1c04e3a8a33d 100644 --- a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,7 +8,7 @@ metadata: - "web" - "serialization" - "reactive" - status: "experimental" + status: "stable" codestart: name: "grpc" languages: diff --git a/extensions/mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 658ebabc08bea..001d82655819a 100644 --- a/extensions/mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/mutiny/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -10,4 +10,4 @@ metadata: - "Reactor" categories: - "reactive" - status: "preview" \ No newline at end of file + status: "stable" \ No newline at end of file diff --git a/extensions/smallrye-reactive-messaging-amqp/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/smallrye-reactive-messaging-amqp/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 17e0d2833eb84..ad13261008daa 100644 --- a/extensions/smallrye-reactive-messaging-amqp/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/smallrye-reactive-messaging-amqp/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,4 +9,4 @@ metadata: guide: "https://quarkus.io/guides/amqp" categories: - "messaging" - status: "preview" \ No newline at end of file + status: "stable" From be0311779f0713457bab2a36acf718afb61d0989 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 1 Sep 2021 18:47:08 +0300 Subject: [PATCH 21/60] Take @TestSecurity(authorizationEnabled = false) into account for RESTEasy Reactive Relates to: #19834 (cherry picked from commit 07f9e8741d35016df1727ffd834412b8d2bb1b15) --- .../interceptor/check/AuthenticatedCheck.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java index 4b23692988914..ac80cbf0249d9 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java @@ -2,21 +2,47 @@ import java.lang.reflect.Method; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.spi.runtime.AuthorizationController; import io.quarkus.security.spi.runtime.SecurityCheck; public class AuthenticatedCheck implements SecurityCheck { public static final AuthenticatedCheck INSTANCE = new AuthenticatedCheck(); + private volatile AuthorizationController authorizationController; + private AuthenticatedCheck() { } @Override public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + if (isAuthorizationDisabled()) { + return; + } if (identity.isAnonymous()) { throw new UnauthorizedException(); } } + + private boolean isAuthorizationDisabled() { + if (authorizationController != null) { + return !authorizationController.isAuthorizationEnabled(); + } + + ArcContainer container = Arc.container(); + if ((container == null) || !container.isRunning()) { + return false; + } + InstanceHandle instance = container.instance(AuthorizationController.class); + if (instance.isAvailable()) { + authorizationController = instance.get(); + return !instance.get().isAuthorizationEnabled(); + } + return false; + } } From 8af274bf793edad4814d54ff9c5c15df8df2de42 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 1 Sep 2021 20:42:17 +0200 Subject: [PATCH 22/60] Dev UI Keykloak - Make the logout button visible Signed-off-by:Phillip Kruger (cherry picked from commit fab4d7045544a3170916cb316028c036b97ae405) --- .../src/main/resources/dev-templates/provider.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html index 14779285ad747..3ab9b02730c08 100644 --- a/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html +++ b/extensions/oidc/deployment/src/main/resources/dev-templates/provider.html @@ -178,7 +178,7 @@ userName = jsonPayload.preferred_username; } if (userName) { - $('#loggedInUser').append(" Logged in as " + userName); + $('#loggedInUser').append("Logged in as " + userName + " "); } } return "
" + 
@@ -289,8 +289,9 @@
                     Your tokens
                 
From fdf7bf989afa6c9f7e3162068dbcf030b099052d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 1 Sep 2021 19:35:22 +0300 Subject: [PATCH 23/60] Don't fail Hibernate Validator when no RESTEasy Reactive request is in flight Relates to: #19834 (cherry picked from commit ee298ecdfdd258b6af47fe2b8daa0d3f602031c1) --- ...ResteasyReactiveContextLocaleResolver.java | 7 +- .../server/injection/ContextProducers.java | 6 +- .../pom.xml | 182 ++++++++++++++++++ .../HibernateValidatorTestResource.java | 167 ++++++++++++++++ ...ValidatorTestResourceGenericInterface.java | 9 + ...bernateValidatorTestResourceInterface.java | 22 +++ .../validator/custom/MyCustomConstraint.java | 31 +++ .../validator/custom/MyOtherBean.java | 19 ++ .../HibernateValidatorFunctionalityTest.java | 31 +++ integration-tests/pom.xml | 1 + 10 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/pom.xml create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java create mode 100644 integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java index 38366401d34e9..9f4023f718699 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java @@ -18,6 +18,11 @@ public ResteasyReactiveContextLocaleResolver(HttpHeaders headers) { @Override protected HttpHeaders getHeaders() { - return headers; + try { + headers.getLength(); // this forces the creation of the actual object which will fail if there is no request in flight + return headers; + } catch (IllegalStateException e) { + return null; + } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java index 06318c42c1c2d..74c67f3822420 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java @@ -133,6 +133,10 @@ SecurityContext securityContext() { } private ResteasyReactiveRequestContext getContext() { - return CurrentRequestManager.get(); + ResteasyReactiveRequestContext context = CurrentRequestManager.get(); + if (context == null) { + throw new IllegalStateException("No RESTEasy Reactive request in progress"); + } + return context; } } diff --git a/integration-tests/hibernate-validator-resteasy-reactive/pom.xml b/integration-tests/hibernate-validator-resteasy-reactive/pom.xml new file mode 100644 index 0000000000000..9862dac97f3d3 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/pom.xml @@ -0,0 +1,182 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-hibernate-validator-resteasy-reactive + Quarkus - Integration Tests - Hibernate Validator + Module that contains Hibernate Validator/Bean Validation related tests using RESTEasy Reactive + + + + io.quarkus + quarkus-resteasy-reactive-jsonb + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-arc + + + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-h2 + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-h2 + test + + + + io.quarkus + quarkus-arc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-validator-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jsonb-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + en + + + + + + + + + native-image + + + native + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + en + + + + + + + + + diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java new file mode 100644 index 0000000000000..cc7f0929c2e14 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java @@ -0,0 +1,167 @@ +package io.quarkus.it.hibernate.validator; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Email; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.hibernate.validator.constraints.Length; + +import io.quarkus.it.hibernate.validator.custom.MyOtherBean; +import io.quarkus.runtime.StartupEvent; + +@Path("/hibernate-validator/test") +public class HibernateValidatorTestResource { + + @Inject + Validator validator; + + public void testValidationOutsideOfResteasyContext(@Observes StartupEvent startupEvent) { + validator.validate(new MyOtherBean(null)); + } + + @GET + @Path("/basic-features") + @Produces(MediaType.TEXT_PLAIN) + public String testBasicFeatures() { + ResultBuilder result = new ResultBuilder(); + + Map> invalidCategorizedEmails = new HashMap<>(); + invalidCategorizedEmails.put("a", Collections.singletonList("b")); + + result.append(formatViolations(validator.validate(new MyBean( + "Bill Jones", + "b", + Collections.singletonList("c"), + -4d, + invalidCategorizedEmails)))); + + Map> validCategorizedEmails = new HashMap<>(); + validCategorizedEmails.put("Professional", Collections.singletonList("bill.jones@example.com")); + + result.append(formatViolations(validator.validate(new MyBean( + "Bill Jones", + "bill.jones@example.com", + Collections.singletonList("biji@example.com"), + 5d, + validCategorizedEmails)))); + + return result.build(); + } + + private String formatViolations(Set> violations) { + if (violations.isEmpty()) { + return "passed"; + } + + return "failed: " + violations.stream() + .map(v -> v.getPropertyPath().toString() + " (" + v.getMessage() + ")") + .sorted() + .collect(Collectors.joining(", ")); + } + + public static class MyBean { + + private String name; + + @Email + private String email; + + private List<@Email String> additionalEmails; + + @DecimalMin("0") + private Double score; + + private Map<@Length(min = 3) String, List<@Email String>> categorizedEmails; + + @Valid + private NestedBeanWithoutConstraints nestedBeanWithoutConstraints; + + public MyBean(String name, String email, List additionalEmails, Double score, + Map> categorizedEmails) { + this.name = name; + this.email = email; + this.additionalEmails = additionalEmails; + this.score = score; + this.categorizedEmails = categorizedEmails; + this.nestedBeanWithoutConstraints = new NestedBeanWithoutConstraints(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getAdditionalEmails() { + return additionalEmails; + } + + public void setAdditionalEmails(List additionalEmails) { + this.additionalEmails = additionalEmails; + } + + public Double getScore() { + return score; + } + + public void setScore(Double score) { + this.score = score; + } + + public Map> getCategorizedEmails() { + return categorizedEmails; + } + + public void setCategorizedEmails(Map> categorizedEmails) { + this.categorizedEmails = categorizedEmails; + } + } + + private static class ResultBuilder { + + private StringBuilder builder = new StringBuilder(); + + public ResultBuilder append(String element) { + if (builder.length() > 0) { + builder.append("\n"); + } + builder.append(element); + return this; + } + + public String build() { + return builder.toString(); + } + } + + private static class NestedBeanWithoutConstraints { + + @SuppressWarnings("unused") + private String property; + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java new file mode 100644 index 0000000000000..d2dfd4aa2222f --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java @@ -0,0 +1,9 @@ +package io.quarkus.it.hibernate.validator; + +import javax.validation.constraints.Digits; + +public interface HibernateValidatorTestResourceGenericInterface { + + T testRestEndpointGenericMethodValidation(@Digits(integer = 5, fraction = 0) T id); + +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java new file mode 100644 index 0000000000000..fe76470c7c252 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java @@ -0,0 +1,22 @@ +package io.quarkus.it.hibernate.validator; + +import javax.validation.constraints.Digits; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +public interface HibernateValidatorTestResourceInterface { + + @GET + @Path("/rest-end-point-interface-validation/{id}/") + @Produces(MediaType.TEXT_PLAIN) + public String testRestEndPointInterfaceValidation(@Digits(integer = 5, fraction = 0) @PathParam("id") String id); + + @GET + @Path("/rest-end-point-interface-validation-annotation-on-impl-method/{id}/") + @Produces(MediaType.TEXT_PLAIN) + public String testRestEndPointInterfaceValidationWithAnnotationOnImplMethod( + @Digits(integer = 5, fraction = 0) @PathParam("id") String id); +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java new file mode 100644 index 0000000000000..ad412dea04eb5 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java @@ -0,0 +1,31 @@ +package io.quarkus.it.hibernate.validator.custom; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; + +@Target({ ElementType.TYPE_USE, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = MyCustomConstraint.Validator.class) +public @interface MyCustomConstraint { + + String message() default "{MyCustomConstraint.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + class Validator implements ConstraintValidator { + + @Override + public boolean isValid(MyOtherBean value, ConstraintValidatorContext context) { + return value.getName() != null; + } + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java new file mode 100644 index 0000000000000..4961a6d0103b0 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java @@ -0,0 +1,19 @@ +package io.quarkus.it.hibernate.validator.custom; + +@MyCustomConstraint +public class MyOtherBean { + + private String name; + + public MyOtherBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java new file mode 100644 index 0000000000000..433213be9fad8 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java @@ -0,0 +1,31 @@ +package io.quarkus.it.hibernate.validator; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +@QuarkusTestResource(H2DatabaseTestResource.class) +public class HibernateValidatorFunctionalityTest { + + @Test + public void testBasicFeatures() throws Exception { + StringBuilder expected = new StringBuilder(); + expected.append("failed: additionalEmails[0]. (must be a well-formed email address)").append(", ") + .append("categorizedEmails[a]. (length must be between 3 and 2147483647)").append(", ") + .append("categorizedEmails[a].[0]. (must be a well-formed email address)").append(", ") + .append("email (must be a well-formed email address)").append(", ") + .append("score (must be greater than or equal to 0)").append("\n"); + expected.append("passed"); + + RestAssured.when() + .get("/hibernate-validator/test/basic-features") + .then() + .body(is(expected.toString())); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index a14eb85810d84..4d71601c206df 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -33,6 +33,7 @@ class-transformer shared-library hibernate-validator + hibernate-validator-resteasy-reactive common-jpa-entities infinispan-client devtools From d4bc367f1a53c90c2378228f95a4f369678a26f2 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Wed, 1 Sep 2021 14:53:43 +0200 Subject: [PATCH 24/60] Bump kubernetes-client-bom from 5.7.0 to 5.7.2 Signed-off-by: Marc Nuri (cherry picked from commit 679561e16faa0da7a9e8031716652b19981b2130) --- 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 905e66a6bca47..79f218fb98e85 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -170,7 +170,7 @@ 3.14.9 5.0.1 0.1.0 - 5.7.0 + 5.7.2 2.2.0 5.2.SP4 2.1.SP2 From d38b6c53d2d7b1e10f706a77e9c48c1115a014c6 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Fri, 3 Sep 2021 09:14:03 +0200 Subject: [PATCH 25/60] LambdaContainerHandlerSubstitution class must be marked final (cherry picked from commit d35a89a40e5a1036de54697b45c5bad21cc7366c) --- .../lambda/http/graal/LambdaContainerHandlerSubstitution.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java index e2f2ede57953a..d1c9ffebdd196 100644 --- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java +++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java @@ -5,7 +5,7 @@ import com.oracle.svm.core.annotate.TargetClass; @TargetClass(LambdaContainerHandler.class) -public class LambdaContainerHandlerSubstitution { +public final class LambdaContainerHandlerSubstitution { // afterburner does not work in native mode, so let's ensure it's never registered @Substitute From 0282ecb8346f3093b15220d23b1b2bf841d2c3cf Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 3 Sep 2021 11:28:21 +0300 Subject: [PATCH 26/60] Fix use of OpenTelemetry OTLP exporter in native mode Essentially the fact that ClientTracingFilter was using a static field for TextMapPropagator was causing GraalVM to initialize the entire telemetry infrastructure at build time Fixes: #19877 (cherry picked from commit d142b2fe4f201e67583391c2c14ebde110f219db) --- .../restclient/ClientTracingFilter.java | 2 +- .../resteasy-reactive-rest-client/pom.xml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/ClientTracingFilter.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/ClientTracingFilter.java index 9d34efb149b28..5f43898887b75 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/ClientTracingFilter.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/ClientTracingFilter.java @@ -26,7 +26,7 @@ @Priority(Priorities.HEADER_DECORATOR) public class ClientTracingFilter implements ClientRequestFilter, ClientResponseFilter { - private static final TextMapPropagator TEXT_MAP_PROPAGATOR = GlobalOpenTelemetry.getPropagators().getTextMapPropagator(); + private final TextMapPropagator TEXT_MAP_PROPAGATOR = GlobalOpenTelemetry.getPropagators().getTextMapPropagator(); private static final String SCOPE_KEY = ClientTracingFilter.class.getName() + ".scope"; private static final String SPAN_KEY = ClientTracingFilter.class.getName() + ".span"; diff --git a/integration-tests/resteasy-reactive-rest-client/pom.xml b/integration-tests/resteasy-reactive-rest-client/pom.xml index 2d8b52256bea0..00b80a5053e23 100644 --- a/integration-tests/resteasy-reactive-rest-client/pom.xml +++ b/integration-tests/resteasy-reactive-rest-client/pom.xml @@ -31,6 +31,11 @@ io.quarkus quarkus-opentelemetry + + + io.quarkus + quarkus-opentelemetry-exporter-otlp + @@ -99,6 +104,19 @@ + + io.quarkus + quarkus-opentelemetry-exporter-otlp-deployment + ${project.version} + pom + test + + + * + * + + + From 31b19ae44161958a8ddd3c8fabb7f928bc2b0f74 Mon Sep 17 00:00:00 2001 From: barreiro Date: Thu, 2 Sep 2021 15:22:40 +0100 Subject: [PATCH 27/60] Expose Agroal feature: idle validation (cherry picked from commit 3f37b7f8411bd87f450f83432fbcb99187fce12b) --- .../quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java | 6 ++++++ .../main/java/io/quarkus/agroal/runtime/DataSources.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java index d586ddb8bb388..0d4b4c50470a5 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceJdbcRuntimeConfig.java @@ -47,6 +47,12 @@ public class DataSourceJdbcRuntimeConfig { @ConfigItem(defaultValue = "2M") public Optional backgroundValidationInterval = Optional.of(Duration.ofMinutes(2)); + /** + * Perform foreground validation on connections that have been idle for longer than the specified interval. + */ + @ConfigItem + public Optional foregroundValidationInterval = Optional.empty(); + /** * The timeout before cancelling the acquisition of a new connection */ diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index e5a1d8c5e9e31..fa6c4290da30e 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -276,6 +276,9 @@ private void applyNewConfiguration(AgroalDataSourceConfigurationSupplier dataSou if (dataSourceJdbcRuntimeConfig.backgroundValidationInterval.isPresent()) { poolConfiguration.validationTimeout(dataSourceJdbcRuntimeConfig.backgroundValidationInterval.get()); } + if (dataSourceJdbcRuntimeConfig.foregroundValidationInterval.isPresent()) { + poolConfiguration.idleValidationTimeout(dataSourceJdbcRuntimeConfig.foregroundValidationInterval.get()); + } if (dataSourceJdbcRuntimeConfig.validationQuerySql.isPresent()) { String validationQuery = dataSourceJdbcRuntimeConfig.validationQuerySql.get(); poolConfiguration.connectionValidator(new ConnectionValidator() { From 104988985e99108f7a749e11e9b92d0b480c2147 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 3 Sep 2021 16:29:50 +0300 Subject: [PATCH 28/60] Fix issue with skip predicates in scheduled methods Before this fix, the implementations where actually eligible for bean removal Fixes: #19900 (cherry picked from commit 361494784de7ae7aa73e755be57d3a417113f2da) --- .../deployment/SchedulerProcessor.java | 6 +++++ .../test/ConditionalExecutionTest.java | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java index 490927bc4c653..f2e1825b9007d 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java @@ -96,6 +96,7 @@ public class SchedulerProcessor { static final DotName SCHEDULED_NAME = DotName.createSimple(Scheduled.class.getName()); static final DotName SCHEDULES_NAME = DotName.createSimple(Scheduled.Schedules.class.getName()); static final DotName SKIP_NEVER_NAME = DotName.createSimple(Scheduled.Never.class.getName()); + static final DotName SKIP_PREDICATE = DotName.createSimple(Scheduled.SkipPredicate.class.getName()); static final Type SCHEDULED_EXECUTION_TYPE = Type.create(DotName.createSimple(ScheduledExecution.class.getName()), Kind.CLASS); @@ -466,4 +467,9 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu return null; } + @BuildStep + UnremovableBeanBuildItem unremoveableSkipPredicates() { + return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanTypeExclusion(SKIP_PREDICATE)); + } + } diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConditionalExecutionTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConditionalExecutionTest.java index 6f7e0b9eab131..f04cd3eff6a4b 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConditionalExecutionTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/ConditionalExecutionTest.java @@ -1,11 +1,13 @@ package io.quarkus.scheduler.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import javax.enterprise.event.Observes; import javax.inject.Singleton; @@ -44,17 +46,25 @@ public void testExecution() { Thread.currentThread().interrupt(); throw new IllegalStateException(e); } + + assertTrue(OtherIsDisabled.TESTED.get()); + assertEquals(0, Jobs.OTHER_COUNT.get()); } static class Jobs { static final CountDownLatch COUNTER = new CountDownLatch(1); + static final AtomicInteger OTHER_COUNT = new AtomicInteger(0); @Scheduled(identity = "foo", every = "1s", skipExecutionIf = IsDisabled.class) void doSomething() throws InterruptedException { COUNTER.countDown(); } + @Scheduled(identity = "other-foo", every = "1s", skipExecutionIf = OtherIsDisabled.class) + void doSomethingElse() throws InterruptedException { + OTHER_COUNT.incrementAndGet(); + } } @Singleton @@ -77,4 +87,17 @@ void onSkip(@Observes SkippedExecution event) { } } + + @Singleton + public static class OtherIsDisabled implements Scheduled.SkipPredicate { + + static final AtomicBoolean TESTED = new AtomicBoolean(false); + + @Override + public boolean test(ScheduledExecution execution) { + TESTED.set(true); + return true; + } + + } } From 8e670f835751a5a87f10d5a2f61f94cd344b8db4 Mon Sep 17 00:00:00 2001 From: Ken Finnigan Date: Thu, 2 Sep 2021 15:48:17 -0400 Subject: [PATCH 29/60] Fix RESTEasy classic server templating for observability - Fixes #18251 - Add test to Micrometer integration test. Verified not a problem with RESTEasy Reactive - Add test to OpenTelemetry integration test to very fix for RESTEasy Classic (cherry picked from commit d57ddba8b3c19a78ad0dff9663c37b5c30d9b012) --- .../RestPathAnnotationProcessor.java | 27 +++++++++++++- .../prometheus/PathTemplateResource.java | 13 +++++++ .../PrometheusMetricsRegistryTest.java | 10 +++++ .../opentelemetry/PathTemplateResource.java | 13 +++++++ .../opentelemetry/OpenTelemetryTestCase.java | 37 +++++++++++++++++++ 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/PathTemplateResource.java create mode 100644 integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PathTemplateResource.java diff --git a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java index 16b3161928558..16cf244beafc9 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/RestPathAnnotationProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.deployment; +import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -18,6 +19,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.resteasy.common.spi.ResteasyDotNames; import io.quarkus.resteasy.runtime.QuarkusRestPathTemplate; import io.quarkus.resteasy.runtime.QuarkusRestPathTemplateInterceptor; import io.quarkus.resteasy.server.common.spi.ResteasyJaxrsConfigBuildItem; @@ -81,14 +83,22 @@ public void transform(TransformationContext ctx) { AnnotationInstance annotation = methodInfo.annotation(REST_PATH); if (annotation == null) { - return; + // Check for @Path on class and not method + if (!isRestEndpointMethod(methodInfo.annotations())) { + return; + } } // Don't create annotations for rest clients if (classInfo.classAnnotation(REGISTER_REST_CLIENT) != null) { return; } - StringBuilder stringBuilder = new StringBuilder(slashify(annotation.value().asString())); + StringBuilder stringBuilder; + if (annotation != null) { + stringBuilder = new StringBuilder(slashify(annotation.value().asString())); + } else { + stringBuilder = new StringBuilder(); + } // Look for @Path annotation on the class annotation = classInfo.classAnnotation(REST_PATH); @@ -128,6 +138,19 @@ String slashify(String path) { return '/' + path; } + boolean isRestEndpointMethod(List annotations) { + boolean isRestEndpointMethod = false; + + for (AnnotationInstance annotation : annotations) { + if (ResteasyDotNames.JAXRS_METHOD_ANNOTATIONS.contains(annotation.name())) { + isRestEndpointMethod = true; + break; + } + } + + return isRestEndpointMethod; + } + private boolean notRequired(Capabilities capabilities, Optional metricsCapability) { return capabilities.isMissing(Capability.RESTEASY) || diff --git a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/PathTemplateResource.java b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/PathTemplateResource.java new file mode 100644 index 0000000000000..721e384a04300 --- /dev/null +++ b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/PathTemplateResource.java @@ -0,0 +1,13 @@ +package io.quarkus.it.micrometer.prometheus; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +@Path("template/path/{value}") +public class PathTemplateResource { + @GET + public String get(@PathParam("value") String value) { + return "Received: " + value; + } +} diff --git a/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java b/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java index 595ed7ed54bfa..2a76940f1e80d 100644 --- a/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java +++ b/integration-tests/micrometer-prometheus/src/test/java/io/quarkus/it/micrometer/prometheus/PrometheusMetricsRegistryTest.java @@ -77,6 +77,13 @@ void testAllTheThings() { .body(containsString("OK")); } + @Test + @Order(9) + void testTemplatedPathOnClass() { + when().get("/template/path/anything").then().statusCode(200) + .body(containsString("Received: anything")); + } + @Test @Order(10) void testPrometheusScrapeEndpoint() { @@ -103,6 +110,9 @@ void testPrometheusScrapeEndpoint() { .body(containsString("uri=\"/message/match/{id}/{sub}\"")) .body(containsString("uri=\"/message/match/{other}\"")) + .body(containsString( + "http_server_requests_seconds_count{env=\"test\",method=\"GET\",outcome=\"SUCCESS\",registry=\"prometheus\",status=\"200\",uri=\"/template/path/{value}\"")) + // Verify Hibernate Metrics .body(containsString( "hibernate_sessions_open_total{entityManagerFactory=\"\",env=\"test\",registry=\"prometheus\",} 2.0")) diff --git a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PathTemplateResource.java b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PathTemplateResource.java new file mode 100644 index 0000000000000..323cd0119a683 --- /dev/null +++ b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PathTemplateResource.java @@ -0,0 +1,13 @@ +package io.quarkus.it.opentelemetry; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +@Path("template/path/{value}") +public class PathTemplateResource { + @GET + public String get(@PathParam("value") String value) { + return "Received: " + value; + } +} diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java index 70f3ccfdd5162..9d203cf3c13f3 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java @@ -708,6 +708,43 @@ void testAsyncClientTracing() { Assertions.assertTrue(clientServerFound); } + @Test + void testTemplatedPathOnClass() { + resetExporter(); + + given() + .contentType("application/json") + .when().get("/template/path/something") + .then() + .statusCode(200) + .body(containsString("Received: something")); + + Awaitility.await().atMost(Duration.ofMinutes(2)).until(() -> getSpans().size() == 1); + Map spanData = getSpans().get(0); + Assertions.assertNotNull(spanData); + Assertions.assertNotNull(spanData.get("spanId")); + + verifyResource(spanData); + + Assertions.assertEquals("template/path/{value}", spanData.get("name")); + Assertions.assertEquals(SpanKind.SERVER.toString(), spanData.get("kind")); + Assertions.assertTrue((Boolean) spanData.get("ended")); + + Assertions.assertEquals(SpanId.getInvalid(), spanData.get("parent_spanId")); + Assertions.assertEquals(TraceId.getInvalid(), spanData.get("parent_traceId")); + Assertions.assertFalse((Boolean) spanData.get("parent_valid")); + Assertions.assertFalse((Boolean) spanData.get("parent_remote")); + + Assertions.assertEquals("GET", spanData.get("attr_http.method")); + Assertions.assertEquals("1.1", spanData.get("attr_http.flavor")); + Assertions.assertEquals("/template/path/something", spanData.get("attr_http.target")); + Assertions.assertEquals(directUrl.getAuthority(), spanData.get("attr_http.host")); + Assertions.assertEquals("http", spanData.get("attr_http.scheme")); + Assertions.assertEquals("200", spanData.get("attr_http.status_code")); + Assertions.assertNotNull(spanData.get("attr_http.client_ip")); + Assertions.assertNotNull(spanData.get("attr_http.user_agent")); + } + private void verifyResource(Map spanData) { Assertions.assertEquals("opentelemetry-integration-test", spanData.get("resource_service.name")); Assertions.assertEquals("999-SNAPSHOT", spanData.get("resource_service.version")); From 3b876071bd84fdd093902520e53f36e6900dc014 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 3 Sep 2021 16:28:02 +0200 Subject: [PATCH 30/60] Include relevant upstream catalogs even if their versions aren't recommended for new projects Moved io.quarkus.maven.StreamCoords to io.quarkus.registry.catalog.PlatformStreamCoords (cherry picked from commit 9a15b82a7047d555f3bfdfb5530f046929539b79) --- .../cli/common/TargetQuarkusVersionGroup.java | 8 +- .../common/TargetQuarkusVersionGroupTest.java | 8 +- .../registry/client/TestRegistryClient.java | 3 + .../client/TestRegistryClientBuilder.java | 107 +++++++++--- .../create/MultiplePlatformBomsTestBase.java | 21 ++- ...eferencingArchivedUpstreamVersionTest.java | 162 ++++++++++++++++++ .../registry/ExtensionCatalogResolver.java | 158 +++++++++-------- .../registry/RegistryExtensionResolver.java | 8 + .../catalog/PlatformStreamCoords.java} | 10 +- .../catalog/json/JsonCatalogMerger.java | 46 +++++ .../catalog/json/JsonCatalogMergerTest.java | 99 +++++++++++ 11 files changed, 524 insertions(+), 106 deletions(-) create mode 100644 independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformReferencingArchivedUpstreamVersionTest.java rename independent-projects/tools/{artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java => registry-client/src/main/java/io/quarkus/registry/catalog/PlatformStreamCoords.java} (71%) create mode 100644 independent-projects/tools/registry-client/src/test/java/io/quarkus/registry/catalog/json/JsonCatalogMergerTest.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java index 7444fd2418a98..11c55b6fe0a9b 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java @@ -2,15 +2,15 @@ import io.quarkus.cli.Version; import io.quarkus.maven.ArtifactCoords; -import io.quarkus.maven.StreamCoords; import io.quarkus.platform.tools.ToolsConstants; +import io.quarkus.registry.catalog.PlatformStreamCoords; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; public class TargetQuarkusVersionGroup { final static String FULL_EXAMPLE = ToolsConstants.DEFAULT_PLATFORM_BOM_GROUP_ID + ":" + ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID + ":2.2.0.Final"; - StreamCoords streamCoords = null; + PlatformStreamCoords streamCoords = null; String validStream = null; ArtifactCoords platformBom = null; @@ -25,7 +25,7 @@ void setStream(String stream) { stream = stream.trim(); if (!stream.isEmpty()) { try { - streamCoords = StreamCoords.fromString(stream); + streamCoords = PlatformStreamCoords.fromString(stream); validStream = stream; } catch (IllegalArgumentException iex) { throw new CommandLine.ParameterException(spec.commandLine(), @@ -94,7 +94,7 @@ public boolean isStreamSpecified() { return streamCoords != null; } - public StreamCoords getStream() { + public PlatformStreamCoords getStream() { return streamCoords; } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/common/TargetQuarkusVersionGroupTest.java b/devtools/cli/src/test/java/io/quarkus/cli/common/TargetQuarkusVersionGroupTest.java index 9b0b62eb44fe1..e2d3f8593cc34 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/common/TargetQuarkusVersionGroupTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/common/TargetQuarkusVersionGroupTest.java @@ -5,8 +5,8 @@ import io.quarkus.cli.Version; import io.quarkus.maven.ArtifactCoords; -import io.quarkus.maven.StreamCoords; import io.quarkus.platform.tools.ToolsConstants; +import io.quarkus.registry.catalog.PlatformStreamCoords; public class TargetQuarkusVersionGroupTest { final static String clientVersion = Version.clientVersion(); @@ -64,7 +64,7 @@ void testPlatformUseDefaultGroupVersion() { void testStreamUseDFullyQualified() { qvg.setStream("stream-platform:stream-version"); - StreamCoords coords = qvg.getStream(); + PlatformStreamCoords coords = qvg.getStream(); Assertions.assertEquals("stream-platform", coords.getPlatformKey()); Assertions.assertEquals("stream-version", coords.getStreamId()); } @@ -73,7 +73,7 @@ void testStreamUseDFullyQualified() { void testStreamUseDefaultPlatformKey() { qvg.setStream(":stream-version"); - StreamCoords coords = qvg.getStream(); + PlatformStreamCoords coords = qvg.getStream(); Assertions.assertNull(coords.getPlatformKey()); Assertions.assertEquals("stream-version", coords.getStreamId()); } @@ -82,7 +82,7 @@ void testStreamUseDefaultPlatformKey() { void testStreamUseDefaultStreamId() { qvg.setStream("stream-platform:"); - StreamCoords coords = qvg.getStream(); + PlatformStreamCoords coords = qvg.getStream(); Assertions.assertEquals("stream-platform", coords.getPlatformKey()); Assertions.assertEquals("", coords.getStreamId()); } diff --git a/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClient.java b/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClient.java index 70f6a22777edd..97f532d2a3638 100644 --- a/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClient.java +++ b/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClient.java @@ -120,6 +120,9 @@ private ExtensionCatalog deserializeExtensionCatalog(Path p) throws RegistryReso public PlatformCatalog resolvePlatforms(String quarkusVersion) throws RegistryResolutionException { final Path json = TestRegistryClientBuilder.getRegistryPlatformsCatalogPath(registryDir, quarkusVersion); log.debug("%s resolvePlatforms %s", config.getId(), json); + if (!Files.exists(json)) { + return null; + } try { return JsonCatalogMapperHelper.deserialize(json, JsonPlatformCatalog.class); } catch (IOException e) { diff --git a/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClientBuilder.java b/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClientBuilder.java index 74936a9ea295a..195a7c1103ee4 100644 --- a/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClientBuilder.java +++ b/independent-projects/tools/devtools-testing/src/main/java/io/quarkus/devtools/testing/registry/client/TestRegistryClientBuilder.java @@ -106,6 +106,7 @@ public static class TestRegistryBuilder { private JsonRegistryQuarkusVersionsConfig quarkusVersions; private boolean external; private PlatformCatalog platformCatalog; + private JsonPlatformCatalog archivedPlatformCatalog; private List memberCatalogs; private List nonPlatformCatalogs; @@ -221,37 +222,72 @@ private void configure(Path registryDir) { platformConfig .setArtifact(new ArtifactCoords(registryGroupId, Constants.DEFAULT_REGISTRY_PLATFORMS_CATALOG_ARTIFACT_ID, null, "json", Constants.DEFAULT_REGISTRY_ARTIFACT_VERSION)); - if (platformCatalog == null) { + if (platformCatalog == null && archivedPlatformCatalog == null) { platformConfig.setDisabled(true); } else { final Path platformsDir = getRegistryPlatformsDir(registryDir); - persistPlatformCatalog(platformCatalog, platformsDir); final Map platformsByQuarkusVersion = new HashMap<>(); - for (Platform p : platformCatalog.getPlatforms()) { - for (PlatformStream s : p.getStreams()) { - for (PlatformRelease r : s.getReleases()) { - if (r.getQuarkusCoreVersion() == null) { - throw new IllegalStateException("Quarkus version has not be configured for platform release " - + p.getPlatformKey() + ":" + s.getId() + ":" + r.getVersion()); - } - final JsonPlatformCatalog c = platformsByQuarkusVersion.computeIfAbsent(r.getQuarkusCoreVersion(), - v -> new JsonPlatformCatalog()); - JsonPlatform platform = (JsonPlatform) c.getPlatform(p.getPlatformKey()); - if (platform == null) { - platform = new JsonPlatform(); - platform.setPlatformKey(p.getPlatformKey()); - c.addPlatform(platform); + if (platformCatalog != null) { + persistPlatformCatalog(platformCatalog, platformsDir); + for (Platform p : platformCatalog.getPlatforms()) { + for (PlatformStream s : p.getStreams()) { + for (PlatformRelease r : s.getReleases()) { + if (r.getQuarkusCoreVersion() == null) { + throw new IllegalStateException( + "Quarkus version has not be configured for platform release " + + p.getPlatformKey() + ":" + s.getId() + ":" + r.getVersion()); + } + final JsonPlatformCatalog c = platformsByQuarkusVersion.computeIfAbsent( + r.getQuarkusCoreVersion(), + v -> new JsonPlatformCatalog()); + JsonPlatform platform = (JsonPlatform) c.getPlatform(p.getPlatformKey()); + if (platform == null) { + platform = new JsonPlatform(); + platform.setPlatformKey(p.getPlatformKey()); + c.addPlatform(platform); + } + JsonPlatformStream stream = (JsonPlatformStream) platform.getStream(s.getId()); + if (stream == null) { + stream = new JsonPlatformStream(); + stream.setId(s.getId()); + platform.addStream(stream); + } + stream.addRelease(r); } - JsonPlatformStream stream = (JsonPlatformStream) platform.getStream(s.getId()); - if (stream == null) { - stream = new JsonPlatformStream(); - stream.setId(s.getId()); - platform.addStream(stream); + } + } + } + + if (archivedPlatformCatalog != null) { + for (Platform p : archivedPlatformCatalog.getPlatforms()) { + for (PlatformStream s : p.getStreams()) { + for (PlatformRelease r : s.getReleases()) { + if (r.getQuarkusCoreVersion() == null) { + throw new IllegalStateException( + "Quarkus version has not be configured for platform release " + + p.getPlatformKey() + ":" + s.getId() + ":" + r.getVersion()); + } + final JsonPlatformCatalog c = platformsByQuarkusVersion.computeIfAbsent( + r.getQuarkusCoreVersion(), + v -> new JsonPlatformCatalog()); + JsonPlatform platform = (JsonPlatform) c.getPlatform(p.getPlatformKey()); + if (platform == null) { + platform = new JsonPlatform(); + platform.setPlatformKey(p.getPlatformKey()); + c.addPlatform(platform); + } + JsonPlatformStream stream = (JsonPlatformStream) platform.getStream(s.getId()); + if (stream == null) { + stream = new JsonPlatformStream(); + stream.setId(s.getId()); + platform.addStream(stream); + } + stream.addRelease(r); } - stream.addRelease(r); } } } + for (Map.Entry entry : platformsByQuarkusVersion.entrySet()) { persistPlatformCatalog(entry.getValue(), platformsDir.resolve(entry.getKey())); } @@ -339,6 +375,33 @@ public TestPlatformCatalogReleaseBuilder newRelease(String version) { return new TestPlatformCatalogReleaseBuilder(this, release); } + public TestPlatformCatalogReleaseBuilder newArchivedRelease(String version) { + final JsonPlatformRelease release = new JsonPlatformRelease(); + release.setVersion(JsonPlatformReleaseVersion.fromString(version)); + + if (platform.registry.archivedPlatformCatalog == null) { + platform.registry.archivedPlatformCatalog = new JsonPlatformCatalog(); + } + + JsonPlatform archivedPlatform = (JsonPlatform) platform.registry.archivedPlatformCatalog + .getPlatform(platform.platform.getPlatformKey()); + if (archivedPlatform == null) { + archivedPlatform = new JsonPlatform(); + archivedPlatform.setPlatformKey(platform.platform.getPlatformKey()); + platform.registry.archivedPlatformCatalog.addPlatform(archivedPlatform); + } + + JsonPlatformStream archivedStream = (JsonPlatformStream) archivedPlatform.getStream(stream.getId()); + if (archivedStream == null) { + archivedStream = new JsonPlatformStream(); + archivedStream.setId(stream.getId()); + archivedPlatform.addStream(archivedStream); + } + + archivedStream.addRelease(release); + return new TestPlatformCatalogReleaseBuilder(this, release); + } + public TestPlatformCatalogPlatformBuilder platform() { return platform; } diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MultiplePlatformBomsTestBase.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MultiplePlatformBomsTestBase.java index d38ae9eb04136..59c58b2007a9d 100644 --- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MultiplePlatformBomsTestBase.java +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/MultiplePlatformBomsTestBase.java @@ -13,6 +13,8 @@ import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder; import io.quarkus.maven.ArtifactCoords; +import io.quarkus.registry.RegistryResolutionException; +import io.quarkus.registry.catalog.PlatformStreamCoords; import io.quarkus.registry.config.RegistriesConfigLocator; import java.io.IOException; import java.nio.file.Files; @@ -89,7 +91,7 @@ protected QuarkusCommandOutcome addExtensions(Path projectDir, List exte protected QuarkusCommandOutcome createProject(Path projectDir, List extensions) throws Exception { - return createProject(projectDir, null, extensions); + return createProject(projectDir, (String) null, extensions); } protected QuarkusCommandOutcome createProject(Path projectDir, String quarkusVersion, List extensions) @@ -103,6 +105,17 @@ protected QuarkusCommandOutcome createProject(Path projectDir, String quarkusVer .execute(); } + protected QuarkusCommandOutcome createProject(Path projectDir, PlatformStreamCoords stream, List extensions) + throws Exception { + return new CreateProject( + getQuarkusProject(projectDir, stream)) + .groupId("org.acme") + .artifactId("acme-app") + .version("0.0.1-SNAPSHOT") + .extensions(new HashSet<>(extensions)) + .execute(); + } + protected List toPlatformExtensionCoords(String... artifactIds) { return toPlatformExtensionCoords(Arrays.asList(artifactIds)); } @@ -171,6 +184,12 @@ protected QuarkusProject getQuarkusProject(Path projectDir, String quarkusVersio return QuarkusProjectHelper.getProject(projectDir, BuildTool.MAVEN, quarkusVersion); } + protected QuarkusProject getQuarkusProject(Path projectDir, PlatformStreamCoords stream) + throws RegistryResolutionException { + return QuarkusProjectHelper.getProject(projectDir, + QuarkusProjectHelper.getCatalogResolver().resolveExtensionCatalog(stream), BuildTool.MAVEN); + } + static Path newProjectDir(String name) { Path projectDir = getProjectDir(name); if (Files.exists(projectDir)) { diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformReferencingArchivedUpstreamVersionTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformReferencingArchivedUpstreamVersionTest.java new file mode 100644 index 0000000000000..289b13db42130 --- /dev/null +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformReferencingArchivedUpstreamVersionTest.java @@ -0,0 +1,162 @@ +package io.quarkus.devtools.project.create; + +import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder; +import io.quarkus.maven.ArtifactCoords; +import io.quarkus.registry.catalog.PlatformStreamCoords; +import java.nio.file.Path; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class QuarkusPlatformReferencingArchivedUpstreamVersionTest extends MultiplePlatformBomsTestBase { + + private static final String DOWNSTREAM_PLATFORM_KEY = "io.downstream.platform"; + private static final String UPSTREAM_PLATFORM_KEY = "io.upstream.platform"; + + @BeforeAll + public static void setup() throws Exception { + TestRegistryClientBuilder.newInstance() + //.debug() + .baseDir(configDir()) + // registry + .newRegistry("downstream.registry.test") + .recognizedQuarkusVersions("*-downstream") + // platform key + .newPlatform(DOWNSTREAM_PLATFORM_KEY) + // 2.0 STREAM + .newStream("2.0") + // 2.0.4 release + .newRelease("2.0.4-downstream") + .quarkusVersion("2.2.2-downstream") + .upstreamQuarkusVersion("2.2.2") + // default bom including quarkus-core + essential metadata + .addCoreMember() + // foo platform member + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "2.0.4-downstream").release().stream().platform() + .newStream("1.0") + // 1.0.4 release + .newRelease("1.0.4-downstream") + .quarkusVersion("1.1.1-downstream") + .upstreamQuarkusVersion("1.1.1") + // default bom including quarkus-core + essential metadata + .addCoreMember() + // foo platform member + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "1.0.4-downstream").release() + .newMember("acme-e-bom").addExtension("io.acme", "ext-e", "1.0.4-downstream").release() + .stream().platform().registry() + .newNonPlatformCatalog("1.1.1-downstream").addExtension("io.acme", "ext-d", "4.0-downstream").registry() + .clientBuilder() + .newRegistry("upstream.registry.test") + // platform key + .newPlatform(UPSTREAM_PLATFORM_KEY) + // 2.0 STREAM + .newStream("2.0") + // 2.0.5 release + .newRelease("2.0.5") + .quarkusVersion("2.2.5") + // default bom including quarkus-core + essential metadata + .addCoreMember() + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "2.0.5").release() + .newMember("acme-e-bom").addExtension("io.acme", "ext-e", "2.0.5").release() + .newMember("acme-bar-bom").addExtension("io.acme", "ext-b", "2.0.5").release().stream() + // 2.0.4 release + .newArchivedRelease("2.0.4") + .quarkusVersion("2.2.2") + // default bom including quarkus-core + essential metadata + .addCoreMember() + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "2.0.4").release() + .newMember("acme-e-bom").addExtension("io.acme", "ext-e", "2.0.4").release() + .newMember("acme-bar-bom").addExtension("io.acme", "ext-b", "2.0.4").release().stream().platform() + // 1.0 STREAM + .newStream("1.0") + .newRelease("1.0.5") + .quarkusVersion("1.1.5") + // default bom including quarkus-core + essential metadata + .addCoreMember() + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "1.0.5").addExtension("io.acme", "ext-e", "1.0.5") + .release() + .newMember("acme-bar-bom").addExtension("io.acme", "ext-b", "1.0.5").release() + .stream() + .newArchivedRelease("1.0.4") + .quarkusVersion("1.1.1") + // default bom including quarkus-core + essential metadata + .addCoreMember() + .newMember("acme-foo-bom").addExtension("io.acme", "ext-a", "1.0.4").addExtension("io.acme", "ext-e", "1.0.4") + .release() + .newMember("acme-bar-bom").addExtension("io.acme", "ext-b", "1.0.4").release() + .stream().platform().registry() + .newNonPlatformCatalog("2.2.2").addExtension("io.acme", "ext-c", "5.1").addExtension("io.acme", "ext-d", "6.0") + .registry() + .clientBuilder() + .build(); + + enableRegistryClient(); + } + + protected String getMainPlatformKey() { + return DOWNSTREAM_PLATFORM_KEY; + } + + @Test + public void addExtensionsFromAlreadyImportedPlatform() throws Exception { + final Path projectDir = newProjectDir("downstream-upstream-platform"); + createProject(projectDir, Arrays.asList("ext-a")); + + assertModel(projectDir, + toPlatformBomCoords("acme-foo-bom"), + Arrays.asList(new ArtifactCoords("io.acme", "ext-a", null)), + "2.0.4-downstream"); + + addExtensions(projectDir, Arrays.asList("ext-b", "ext-c", "ext-d", "ext-e")); + assertModel(projectDir, + Arrays.asList(mainPlatformBom(), platformMemberBomCoords("acme-foo-bom"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-bar-bom", "pom", "2.0.4"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-e-bom", "pom", "2.0.4")), + Arrays.asList(new ArtifactCoords("io.acme", "ext-a", null), + new ArtifactCoords("io.acme", "ext-b", null), + new ArtifactCoords("io.acme", "ext-e", null), + new ArtifactCoords("io.acme", "ext-c", "jar", "5.1"), + new ArtifactCoords("io.acme", "ext-d", "jar", "6.0")), + "2.0.4-downstream"); + } + + @Test + public void createWithExtensionsFromDifferentPlatforms() throws Exception { + final Path projectDir = newProjectDir("create-downstream-upstream-platform"); + createProject(projectDir, Arrays.asList("ext-a", "ext-b")); + + assertModel(projectDir, + Arrays.asList(mainPlatformBom(), platformMemberBomCoords("acme-foo-bom"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-bar-bom", "pom", "2.0.4")), + Arrays.asList(new ArtifactCoords("io.acme", "ext-a", null), new ArtifactCoords("io.acme", "ext-b", null)), + "2.0.4-downstream"); + } + + @Test + public void createPreferringOlderStreamToNewerStreamCoveringLessExtensions() throws Exception { + final Path projectDir = newProjectDir("create-downstream-upstream-platform"); + createProject(projectDir, Arrays.asList("ext-a", "ext-b", "ext-e")); + + assertModel(projectDir, + Arrays.asList(mainPlatformBom(), platformMemberBomCoords("acme-foo-bom"), platformMemberBomCoords("acme-e-bom"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-bar-bom", "pom", "1.0.4")), + Arrays.asList(new ArtifactCoords("io.acme", "ext-a", null), new ArtifactCoords("io.acme", "ext-b", null), + new ArtifactCoords("io.acme", "ext-e", null)), + "1.0.4-downstream"); + } + + @Test + public void createUsingStream2_0() throws Exception { + final Path projectDir = newProjectDir("created-using-downstream-stream"); + createProject(projectDir, new PlatformStreamCoords(DOWNSTREAM_PLATFORM_KEY, "2.0"), + Arrays.asList("ext-a", "ext-b", "ext-e")); + + assertModel(projectDir, + Arrays.asList(mainPlatformBom(), platformMemberBomCoords("acme-foo-bom"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-e-bom", "pom", "2.0.4"), + new ArtifactCoords(UPSTREAM_PLATFORM_KEY, "acme-bar-bom", "pom", "2.0.4")), + Arrays.asList(new ArtifactCoords("io.acme", "ext-a", null), new ArtifactCoords("io.acme", "ext-b", null), + new ArtifactCoords("io.acme", "ext-e", null)), + "2.0.4-downstream"); + } +} diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java index 3d2059862f146..ec04881f12ed5 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java @@ -4,12 +4,12 @@ import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.maven.ArtifactCoords; -import io.quarkus.maven.StreamCoords; import io.quarkus.registry.catalog.ExtensionCatalog; import io.quarkus.registry.catalog.Platform; import io.quarkus.registry.catalog.PlatformCatalog; import io.quarkus.registry.catalog.PlatformRelease; import io.quarkus.registry.catalog.PlatformStream; +import io.quarkus.registry.catalog.PlatformStreamCoords; import io.quarkus.registry.catalog.json.JsonCatalogMerger; import io.quarkus.registry.catalog.json.JsonExtensionCatalog; import io.quarkus.registry.catalog.json.JsonPlatform; @@ -291,6 +291,15 @@ public PlatformCatalog resolvePlatformCatalog(String quarkusVersion) throws Regi return result; } + private void collectPlatforms(PlatformCatalog catalog, List collectedPlatforms, + Set collectedPlatformKeys) { + for (Platform p : catalog.getPlatforms()) { + if (collectedPlatformKeys.add(p.getPlatformKey())) { + collectedPlatforms.add(p); + } + } + } + public PlatformCatalog resolvePlatformCatalogFromRegistry(String registryId) throws RegistryResolutionException { return registries.get(getRegistryIndex(registryId)).resolvePlatformCatalog(); } @@ -301,15 +310,6 @@ public PlatformCatalog resolvePlatformCatalogFromRegistry(String registryId, Str : registries.get(getRegistryIndex(registryId)).resolvePlatformCatalog(quarkusVersion); } - private void collectPlatforms(PlatformCatalog catalog, List collectedPlatforms, - Set collectedPlatformKeys) { - for (Platform p : catalog.getPlatforms()) { - if (collectedPlatformKeys.add(p.getPlatformKey())) { - collectedPlatforms.add(p); - } - } - } - private class ExtensionCatalogBuilder { private final List catalogs = new ArrayList<>(); final Map> registriesByQuarkusCore = new HashMap<>(); @@ -320,6 +320,12 @@ void addCatalog(ExtensionCatalog c) { catalogs.add(c); } + void addUpstreamQuarkusVersion(String quarkusVersion) { + if (!upstreamQuarkusVersions.contains(quarkusVersion)) { + upstreamQuarkusVersions.add(quarkusVersion); + } + } + List getRegistriesForQuarkusCore(String quarkusVersion) { return registriesByQuarkusCore.computeIfAbsent(quarkusVersion, v -> getRegistriesForQuarkusVersion(v)); } @@ -364,41 +370,71 @@ public ExtensionCatalog resolveExtensionCatalog() throws RegistryResolutionExcep final ExtensionCatalogBuilder catalogBuilder = new ExtensionCatalogBuilder(); for (int registryIndex = 0; registryIndex < registries.size(); ++registryIndex) { - RegistryExtensionResolver registry = registries.get(registryIndex); - final PlatformCatalog pc = registry.resolvePlatformCatalog(); + collectPlatformExtensions(catalogBuilder, registryIndex); + } + + return catalogBuilder.build(); + } + + private void collectPlatformExtensions(final ExtensionCatalogBuilder catalogBuilder, int registryIndex) + throws RegistryResolutionException { + final RegistryExtensionResolver registry = registries.get(registryIndex); + + final List downstreamPreferences = new ArrayList<>(catalogBuilder.upstreamQuarkusVersions.size()); + for (String quarkusVersion : catalogBuilder.upstreamQuarkusVersions) { + if (!registry.isAcceptsQuarkusVersionQueries(quarkusVersion)) { + continue; + } + final PlatformCatalog pc = registry.resolvePlatformCatalog(quarkusVersion); if (pc == null) { continue; } - int platformIndex = 0; - for (Platform platform : pc.getPlatforms()) { - platformIndex++; - for (PlatformStream stream : platform.getStreams()) { - int releaseIndex = 0; - for (PlatformRelease release : stream.getReleases()) { - releaseIndex++; - final int compatiblityCode = catalogBuilder - .getCompatibilityCode(release.getQuarkusCoreVersion(), - release.getUpstreamQuarkusCoreVersion()); - - int memberIndex = 0; - for (ArtifactCoords bom : release.getMemberBoms()) { - memberIndex++; - final ExtensionCatalog ec = registry.resolvePlatformExtensions(bom); - if (ec != null) { - final OriginPreference originPreference = new OriginPreference(registryIndex, platformIndex, - releaseIndex, memberIndex, compatiblityCode); - addOriginPreference(ec, originPreference); - catalogBuilder.addCatalog(ec); - } else { - log.warn("Failed to resolve extension catalog for %s from registry %s", bom, registry.getId()); - } + downstreamPreferences.add(pc); + } + + PlatformCatalog pc = registry.resolvePlatformCatalog(); + if (pc == null && downstreamPreferences.isEmpty()) { + return; + } + if (!downstreamPreferences.isEmpty()) { + downstreamPreferences.add(pc); + pc = JsonCatalogMerger.mergePlatformCatalogs(downstreamPreferences); + } + + int platformIndex = 0; + for (Platform platform : pc.getPlatforms()) { + platformIndex++; + for (PlatformStream stream : platform.getStreams()) { + int releaseIndex = 0; + for (PlatformRelease release : stream.getReleases()) { + releaseIndex++; + final String quarkusVersion = release.getQuarkusCoreVersion(); + final int compatiblityCode = catalogBuilder.getCompatibilityCode(quarkusVersion, + release.getUpstreamQuarkusCoreVersion()); + + if (!registry.isExclusiveProviderOf(quarkusVersion)) { + catalogBuilder.addUpstreamQuarkusVersion(quarkusVersion); + } + if (release.getUpstreamQuarkusCoreVersion() != null) { + catalogBuilder.addUpstreamQuarkusVersion(release.getUpstreamQuarkusCoreVersion()); + } + + int memberIndex = 0; + for (ArtifactCoords bom : release.getMemberBoms()) { + memberIndex++; + final ExtensionCatalog ec = registry.resolvePlatformExtensions(bom); + if (ec != null) { + final OriginPreference originPreference = new OriginPreference(registryIndex, platformIndex, + releaseIndex, memberIndex, compatiblityCode); + addOriginPreference(ec, originPreference); + catalogBuilder.addCatalog(ec); + } else { + log.warn("Failed to resolve extension catalog for %s from registry %s", bom, registry.getId()); } } } } } - - return catalogBuilder.build(); } private void addOriginPreference(final ExtensionCatalog ec, OriginPreference originPreference) { @@ -426,25 +462,22 @@ public ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion) throw private ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion, final ExtensionCatalogBuilder catalogBuilder, Set preferredPlatforms) throws RegistryResolutionException { - collectPlatforms(quarkusCoreVersion, catalogBuilder, preferredPlatforms); + collectPlatformExtensions(quarkusCoreVersion, catalogBuilder, preferredPlatforms); int i = 0; while (i < catalogBuilder.upstreamQuarkusVersions.size()) { - collectPlatforms(catalogBuilder.upstreamQuarkusVersions.get(i++), catalogBuilder, preferredPlatforms); + collectPlatformExtensions(catalogBuilder.upstreamQuarkusVersions.get(i++), catalogBuilder, preferredPlatforms); } return catalogBuilder.build(); } - public ExtensionCatalog resolveExtensionCatalog(StreamCoords streamCoords) throws RegistryResolutionException { + public ExtensionCatalog resolveExtensionCatalog(PlatformStreamCoords streamCoords) throws RegistryResolutionException { ensureRegistriesConfigured(); - final ExtensionCatalogBuilder catalogBuilder = new ExtensionCatalogBuilder(); - final String platformKey = streamCoords.getPlatformKey(); final String streamId = streamCoords.getStreamId(); PlatformStream stream = null; - RegistryExtensionResolver registry = null; int registryIndex = 0; while (registryIndex < registries.size()) { final RegistryExtensionResolver qer = registries.get(registryIndex++); @@ -456,7 +489,6 @@ public ExtensionCatalog resolveExtensionCatalog(StreamCoords streamCoords) throw for (Platform p : platforms.getPlatforms()) { stream = p.getStream(streamId); if (stream != null) { - registry = qer; break; } } @@ -466,7 +498,6 @@ public ExtensionCatalog resolveExtensionCatalog(StreamCoords streamCoords) throw continue; } stream = platform.getStream(streamId); - registry = qer; } break; } @@ -510,24 +541,12 @@ public ExtensionCatalog resolveExtensionCatalog(StreamCoords streamCoords) throw throw new RegistryResolutionException(buf.toString()); } - int releaseIndex = 0; + final List catalogs = new ArrayList<>(); for (PlatformRelease release : stream.getReleases()) { - final int compatiblityCode = catalogBuilder - .getCompatibilityCode(release.getQuarkusCoreVersion(), - release.getUpstreamQuarkusCoreVersion()); - ++releaseIndex; - int memberIndex = 0; - for (ArtifactCoords bom : release.getMemberBoms()) { - final ExtensionCatalog ec = registry.resolvePlatformExtensions(bom); - final OriginPreference originPreference = new OriginPreference(registryIndex, 1, - releaseIndex, ++memberIndex, compatiblityCode); - addOriginPreference(ec, originPreference); - - catalogBuilder.addCatalog(ec); - } + catalogs.add(resolveExtensionCatalog(release.getMemberBoms())); } - return catalogBuilder.build(); + return JsonCatalogMerger.merge(catalogs); } @SuppressWarnings("unchecked") @@ -623,7 +642,7 @@ public ExtensionCatalog resolveExtensionCatalog(Collection prefe release.setMemberBoms(coords); } - collectPlatforms(quarkusVersion, catalogBuilder, registry, platformIndex, + collectPlatformExtensions(quarkusVersion, catalogBuilder, registry, platformIndex, p); continue; } @@ -688,8 +707,8 @@ private int getRegistryIndex(String registryId) { throw new IllegalStateException(buf.toString()); } - private void collectPlatforms(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, - Set preferredPlatforms) + private void collectPlatformExtensions(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, + Set processedPlatforms) throws RegistryResolutionException { final List quarkusVersionRegistries = catalogBuilder .getRegistriesForQuarkusCore(quarkusCoreVersion); @@ -703,18 +722,18 @@ private void collectPlatforms(String quarkusCoreVersion, ExtensionCatalogBuilder if (platforms.isEmpty()) { continue; } - int platformIndex = preferredPlatforms.size(); + int platformIndex = processedPlatforms.size(); for (Platform p : platforms) { - if (preferredPlatforms.contains(p.getPlatformKey())) { + if (processedPlatforms.contains(p.getPlatformKey())) { continue; } ++platformIndex; - collectPlatforms(quarkusCoreVersion, catalogBuilder, registry, platformIndex, p); + collectPlatformExtensions(quarkusCoreVersion, catalogBuilder, registry, platformIndex, p); } } } - private void collectPlatforms(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, + private void collectPlatformExtensions(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, RegistryExtensionResolver registry, int platformIndex, Platform p) throws RegistryResolutionException { for (PlatformStream s : p.getStreams()) { @@ -738,9 +757,8 @@ private void collectPlatforms(String quarkusCoreVersion, ExtensionCatalogBuilder } final String upstreamQuarkusVersion = r.getUpstreamQuarkusCoreVersion(); - if (upstreamQuarkusVersion != null - && !catalogBuilder.upstreamQuarkusVersions.contains(upstreamQuarkusVersion)) { - catalogBuilder.upstreamQuarkusVersions.add(upstreamQuarkusVersion); + if (upstreamQuarkusVersion != null) { + catalogBuilder.addUpstreamQuarkusVersion(upstreamQuarkusVersion); } } } diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java index 72eda74c8fcd9..0f203f1a5dc78 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/RegistryExtensionResolver.java @@ -56,6 +56,14 @@ int checkQuarkusVersion(String quarkusVersion) { : VERSION_RECOGNIZED; } + boolean isExclusiveProviderOf(String quarkusVersion) { + return checkQuarkusVersion(quarkusVersion) == VERSION_EXCLUSIVE_PROVIDER; + } + + boolean isAcceptsQuarkusVersionQueries(String quarkusVersion) { + return checkQuarkusVersion(quarkusVersion) >= 0; + } + int checkPlatform(ArtifactCoords platform) { // TODO this should be allowed to check the full coordinates return checkQuarkusVersion(platform.getVersion()); diff --git a/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformStreamCoords.java similarity index 71% rename from independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java rename to independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformStreamCoords.java index 147ce22550601..8c8db274dbcca 100644 --- a/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/PlatformStreamCoords.java @@ -1,17 +1,17 @@ -package io.quarkus.maven; +package io.quarkus.registry.catalog; -public class StreamCoords { +public class PlatformStreamCoords { final String platformKey; final String streamId; - public static StreamCoords fromString(String stream) { + public static PlatformStreamCoords fromString(String stream) { final int colon = stream.indexOf(':'); String platformKey = colon <= 0 ? null : stream.substring(0, colon); String streamId = colon < 0 ? stream : stream.substring(colon + 1); - return new StreamCoords(platformKey, streamId); + return new PlatformStreamCoords(platformKey, streamId); } - public StreamCoords(String platformKey, String streamId) { + public PlatformStreamCoords(String platformKey, String streamId) { this.platformKey = platformKey; this.streamId = streamId; } diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java index 4f7b79ed2695b..621565acb161b 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/catalog/json/JsonCatalogMerger.java @@ -5,6 +5,10 @@ import io.quarkus.registry.catalog.Extension; import io.quarkus.registry.catalog.ExtensionCatalog; import io.quarkus.registry.catalog.ExtensionOrigin; +import io.quarkus.registry.catalog.Platform; +import io.quarkus.registry.catalog.PlatformCatalog; +import io.quarkus.registry.catalog.PlatformRelease; +import io.quarkus.registry.catalog.PlatformStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -72,6 +76,48 @@ public static ExtensionCatalog merge(List catalogs) { return combined; } + public static PlatformCatalog mergePlatformCatalogs(List catalogs) { + if (catalogs.isEmpty()) { + throw new IllegalArgumentException("No catalogs provided"); + } + if (catalogs.size() == 1) { + return catalogs.get(0); + } + final JsonPlatformCatalog merged = new JsonPlatformCatalog(); + final Map platformMap = new HashMap<>(); + for (PlatformCatalog c : catalogs) { + for (Platform p : c.getPlatforms()) { + final JsonPlatform mergedPlatform = platformMap.computeIfAbsent(p.getPlatformKey(), k -> { + final JsonPlatform pl = new JsonPlatform(); + pl.setPlatformKey(p.getPlatformKey()); + merged.addPlatform(pl); + return pl; + }); + for (PlatformStream s : p.getStreams()) { + JsonPlatformStream mergedStream = (JsonPlatformStream) mergedPlatform.getStream(s.getId()); + if (mergedStream == null) { + mergedStream = new JsonPlatformStream(); + mergedStream.setId(s.getId()); + mergedPlatform.addStream(mergedStream); + } + for (PlatformRelease r : s.getReleases()) { + final PlatformRelease release = mergedStream.getRelease(r.getVersion()); + if (release == null) { + mergedStream.addRelease(r); + } + } + final Map mergedStreamMetadata = mergedStream.getMetadata(); + s.getMetadata().entrySet() + .forEach(entry -> mergedStreamMetadata.putIfAbsent(entry.getKey(), entry.getValue())); + } + p.getMetadata().entrySet() + .forEach(entry -> mergedPlatform.getMetadata().putIfAbsent(entry.getKey(), entry.getValue())); + } + c.getMetadata().entrySet().forEach(entry -> merged.getMetadata().putIfAbsent(entry.getKey(), entry.getValue())); + } + return merged; + } + private static List detectRoots(List catalogs) { final Set allDerivedFrom = new HashSet<>(catalogs.size()); for (ExtensionCatalog catalog : catalogs) { diff --git a/independent-projects/tools/registry-client/src/test/java/io/quarkus/registry/catalog/json/JsonCatalogMergerTest.java b/independent-projects/tools/registry-client/src/test/java/io/quarkus/registry/catalog/json/JsonCatalogMergerTest.java new file mode 100644 index 0000000000000..0026ad35ba936 --- /dev/null +++ b/independent-projects/tools/registry-client/src/test/java/io/quarkus/registry/catalog/json/JsonCatalogMergerTest.java @@ -0,0 +1,99 @@ +package io.quarkus.registry.catalog.json; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkus.maven.ArtifactCoords; +import io.quarkus.registry.catalog.Platform; +import io.quarkus.registry.catalog.PlatformCatalog; +import io.quarkus.registry.catalog.PlatformRelease; +import io.quarkus.registry.catalog.PlatformStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class JsonCatalogMergerTest { + + @Test + void testMergePlatformCatalogs() throws Exception { + + final List catalogs = new ArrayList<>(); + + JsonPlatformCatalog c = new JsonPlatformCatalog(); + catalogs.add(c); + JsonPlatform p = new JsonPlatform(); + c.addPlatform(p); + p.setPlatformKey("platform"); + JsonPlatformStream s = new JsonPlatformStream(); + s.setId("2.0"); + p.addStream(s); + JsonPlatformRelease r = new JsonPlatformRelease(); + r.setQuarkusCoreVersion("2.0.0"); + r.setVersion(JsonPlatformReleaseVersion.fromString("2.2.2")); + r.setMemberBoms(Collections.singletonList(ArtifactCoords.fromString("org.acme:acme-quarkus-bom::pom:2.2.2"))); + s.addRelease(r); + + s = new JsonPlatformStream(); + s.setId("1.0"); + p.addStream(s); + r = new JsonPlatformRelease(); + r.setQuarkusCoreVersion("1.0.1"); + r.setVersion(JsonPlatformReleaseVersion.fromString("1.1.2")); + r.setMemberBoms(Collections.singletonList(ArtifactCoords.fromString("org.acme:acme-quarkus-bom::pom:1.1.2"))); + s.addRelease(r); + + c = new JsonPlatformCatalog(); + catalogs.add(c); + p = new JsonPlatform(); + c.addPlatform(p); + p.setPlatformKey("platform"); + s = new JsonPlatformStream(); + s.setId("2.0"); + p.addStream(s); + r = new JsonPlatformRelease(); + r.setQuarkusCoreVersion("2.0.1"); + r.setVersion(JsonPlatformReleaseVersion.fromString("2.2.3")); + r.setMemberBoms(Collections.singletonList(ArtifactCoords.fromString("org.acme:acme-quarkus-bom::pom:2.2.3"))); + s.addRelease(r); + + s = new JsonPlatformStream(); + s.setId("1.0"); + p.addStream(s); + r = new JsonPlatformRelease(); + r.setQuarkusCoreVersion("1.0.0"); + r.setVersion(JsonPlatformReleaseVersion.fromString("1.1.1")); + r.setMemberBoms(Collections.singletonList(ArtifactCoords.fromString("org.acme:acme-quarkus-bom::pom:1.1.1"))); + s.addRelease(r); + + final PlatformCatalog merged = JsonCatalogMerger.mergePlatformCatalogs(catalogs); + Collection platforms = merged.getPlatforms(); + assertThat(platforms.size()).isEqualTo(1); + Platform platform = platforms.iterator().next(); + assertThat(platform.getPlatformKey()).isEqualTo("platform"); + assertThat(platform.getStreams().size()).isEqualTo(2); + Iterator streams = platform.getStreams().iterator(); + PlatformStream stream = streams.next(); + assertThat(stream.getId()).isEqualTo("2.0"); + assertThat(stream.getReleases().size()).isEqualTo(2); + Iterator releases = stream.getReleases().iterator(); + PlatformRelease release = releases.next(); + assertThat(release.getVersion()).isEqualTo(JsonPlatformReleaseVersion.fromString("2.2.2")); + assertThat(release.getQuarkusCoreVersion()).isEqualTo("2.0.0"); + release = releases.next(); + assertThat(release.getVersion()).isEqualTo(JsonPlatformReleaseVersion.fromString("2.2.3")); + assertThat(release.getQuarkusCoreVersion()).isEqualTo("2.0.1"); + + stream = streams.next(); + assertThat(stream.getId()).isEqualTo("1.0"); + assertThat(stream.getReleases().size()).isEqualTo(2); + releases = stream.getReleases().iterator(); + release = releases.next(); + assertThat(release.getVersion()).isEqualTo(JsonPlatformReleaseVersion.fromString("1.1.2")); + assertThat(release.getQuarkusCoreVersion()).isEqualTo("1.0.1"); + release = releases.next(); + assertThat(release.getVersion()).isEqualTo(JsonPlatformReleaseVersion.fromString("1.1.1")); + assertThat(release.getQuarkusCoreVersion()).isEqualTo("1.0.0"); + } +} From 39efd93feee02566d69f927da3d9b203a0b95610 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Fri, 3 Sep 2021 11:48:52 -0400 Subject: [PATCH 31/60] Fixed a broken link in a guide (cherry picked from commit 793ffd7300c1ac4a732fc8cdf5e995d14aa95b8a) --- docs/src/main/asciidoc/kafka-schema-registry-avro.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc index ee4109b2640bf..e31ad1b5aa98e 100644 --- a/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc +++ b/docs/src/main/asciidoc/kafka-schema-registry-avro.adoc @@ -45,7 +45,7 @@ However, you can go right to the completed example. Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. -The solution is located in the `kafka-avro-schema-quickstart` {quickstarts-tree-url}/kafka-quickstart-avro-schema[directory]. +The solution is located in the `kafka-avro-schema-quickstart` {quickstarts-tree-url}/kafka-avro-schema-quickstart[directory]. == Creating the Maven Project From e6d8e5a004a672c64f2f3f6599a441efd3298447 Mon Sep 17 00:00:00 2001 From: Manaswini Das Date: Sun, 5 Sep 2021 19:46:27 +0530 Subject: [PATCH 32/60] Fix broken link for config-reference (cherry picked from commit 8798bd6a2c9002d4c8f8ec130adb160d4a6097e1) --- docs/src/main/asciidoc/mailer.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index 2bde3fce3ea53..d61e20586f862 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -184,7 +184,7 @@ The Quarkus mailer is using SMTP, so make sure you have access to a SMTP server. In the `src/main/resources/application.properties` file, you need to configure the host, port, username, password as well as the other configuration aspect. Note that the password can also be configured using system properties and environment variables. -See the xref:configuration-reference.adoc[configuration reference guide] for details. +See the xref:config-reference.adoc[configuration reference guide] for details. Here is an example using _sendgrid_: From fd7bb67b0ab9732366d176cb5422528a5a4859f3 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Sun, 5 Sep 2021 22:14:46 +0200 Subject: [PATCH 33/60] Set default jvm-target in basic kotlin sample project (cherry picked from commit 9d7292d0c58714419f94744970bc00123df8f955) --- .../basic-kotlin-application-project/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle index 6a3efd26bb298..5ac19470fa842 100644 --- a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle @@ -24,3 +24,10 @@ dependencies { compileJava { options.compilerArgs << '-parameters' } + +// This is a fix as kotlin 1.5.30 does not support Java 17 yet +if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { + tasks.quarkusDev { + compilerArgs = ["-jvm-target", "16"] + } +} From 3b9f05b525a41e6450a5820c4c412aa2f57ea73a Mon Sep 17 00:00:00 2001 From: Manaswini Das Date: Mon, 6 Sep 2021 12:54:05 +0530 Subject: [PATCH 34/60] Add instructions to access App passwords page (cherry picked from commit 80894adb77c298557b4834989aaf9dcb53ca58bd) --- docs/src/main/asciidoc/mailer-reference.adoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/main/asciidoc/mailer-reference.adoc b/docs/src/main/asciidoc/mailer-reference.adoc index d6e9af7836621..544b0a771c6e3 100644 --- a/docs/src/main/asciidoc/mailer-reference.adoc +++ b/docs/src/main/asciidoc/mailer-reference.adoc @@ -288,6 +288,11 @@ You can also create your own instance, and pass your own configuration. If you want to use the Gmail SMTP server, first create a dedicated password in `Google Account > Security > App passwords` or go to https://myaccount.google.com/apppasswords. +[NOTE] +==== +You need to switch on 2-Step Verification at https://myaccount.google.com/security in order to access the App passwords page. +==== + When done, you can configure your Quarkus application by adding the following properties to your `application.properties`: With TLS: From 025a42e837a11aa01f4042fefc514f93c4c8aa91 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Fri, 27 Aug 2021 11:51:38 +0200 Subject: [PATCH 35/60] Remove hard coded dependency version in ConditionalDependenciesTest (cherry picked from commit 93bf9f51f2882e0a222fc3ac5e7e0f7b8686a779) --- .../ext-a/deployment/build.gradle | 2 +- .../ext-a/runtime/build.gradle | 6 ++-- .../conditional-dependencies/settings.gradle | 3 +- .../simple-dependency/build.gradle | 32 +++++++++++++++++++ .../simple-dependency/settings.gradle | 16 ++++++++++ .../src/main/resources/META-INF/beans.xml | 0 .../transitive-dependency/build.gradle | 28 ++++++++++++++++ .../transitive-dependency/settings.gradle | 16 ++++++++++ .../src/main/resources/META-INF/beans.xml | 0 .../gradle/ConditionalDependenciesTest.java | 9 ++++-- 10 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/build.gradle create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/settings.gradle create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/src/main/resources/META-INF/beans.xml create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/build.gradle create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/settings.gradle create mode 100644 integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/src/main/resources/META-INF/beans.xml diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/deployment/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/deployment/build.gradle index e1df700721ca6..9ad3b5e14a594 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/deployment/build.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/deployment/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") implementation 'io.quarkus:quarkus-core-deployment' - implementation("org.hibernate:hibernate-core:5.4.9") + implementation("org.acme:simple-dependency:1.0-SNAPSHOT") implementation project(':ext-a:runtime') } diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle index 74cd2b2fa4622..7595b95a938b6 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle @@ -4,9 +4,9 @@ plugins { } dependencies { - implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") - api("org.hibernate:hibernate-core:5.4.9") { - exclude module: "byte-buddy" + implementation platform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + api("org.acme:simple-dependency:1.0-SNAPSHOT") { + exclude module: "transitive-dependency" } } diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle index 787375fe580ea..f51da33f65e6c 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle @@ -35,4 +35,5 @@ include 'ext-p:runtime', 'ext-p:deployment' include 'ext-r:runtime', 'ext-r:deployment' include 'ext-s:runtime', 'ext-s:deployment' include 'ext-t:runtime', 'ext-t:deployment' -include 'ext-u:runtime', 'ext-u:deployment' \ No newline at end of file +include 'ext-u:runtime', 'ext-u:deployment' +include 'simple-dependency', 'transitive-dependency' \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/build.gradle new file mode 100644 index 0000000000000..05847f8c1dcb0 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenCentral() + // in case a custom local repo is configured we are going to use that instead of the default mavenLocal() + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } +} + +dependencies { + api project(":transitive-dependency") +} + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'org.acme' + artifactId = 'simple-dependency' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/settings.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/settings.gradle new file mode 100644 index 0000000000000..bc22fa18a0e22 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + // in case a custom local repo is configured we are going to use that instead of the default mavenLocal() + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } + } +} + +rootProject.name = 'simple-dependency' \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/src/main/resources/META-INF/beans.xml b/integration-tests/gradle/src/main/resources/conditional-dependencies/simple-dependency/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/build.gradle new file mode 100644 index 0000000000000..3a9ec43ebee1e --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' + id 'maven-publish' +} + +repositories { + mavenCentral() + // in case a custom local repo is configured we are going to use that instead of the default mavenLocal() + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'org.acme' + artifactId = 'transitive-dependency' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/settings.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/settings.gradle new file mode 100644 index 0000000000000..b83ad94f36a32 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + // in case a custom local repo is configured we are going to use that instead of the default mavenLocal() + if (System.properties.containsKey('maven.repo.local')) { + maven { + url System.properties.get('maven.repo.local') + } + } else { + mavenLocal() + } + } +} + +rootProject.name = 'transitive-dependency' \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/src/main/resources/META-INF/beans.xml b/integration-tests/gradle/src/main/resources/conditional-dependencies/transitive-dependency/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java index b968f724f5b5c..fb6a41b53dc25 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java @@ -22,7 +22,10 @@ public class ConditionalDependenciesTest extends QuarkusGradleWrapperTestBase { @Order(1) public void publishTestExtensions() throws IOException, InterruptedException, URISyntaxException { File dependencyProject = getProjectDir("conditional-dependencies"); - runGradleWrapper(dependencyProject, ":ext-a:runtime:publishToMavenLocal", + runGradleWrapper(dependencyProject, ":transitive-dependency:publishToMavenLocal", + ":simple-dependency:publishToMavenLocal"); + runGradleWrapper(dependencyProject, + ":ext-a:runtime:publishToMavenLocal", ":ext-a:deployment:publishToMavenLocal", ":ext-b:runtime:publishToMavenLocal", ":ext-b:deployment:publishToMavenLocal", @@ -79,10 +82,10 @@ public void shouldImportConditionalDependency() throws IOException, URISyntaxExc assertThat(mainLib.resolve("org.acme.ext-c-1.0-SNAPSHOT.jar")).exists(); assertThat(mainLib.resolve("org.acme.ext-e-1.0-SNAPSHOT.jar")).exists(); assertThat(mainLib.resolve("org.acme.ext-d-1.0-SNAPSHOT.jar")).doesNotExist(); - assertThat(mainLib.resolve("net.bytebuddy.byte-buddy-1.11.12.jar")).doesNotExist(); + assertThat(mainLib.resolve("org.acme.transitive-dependency-1.0-SNAPSHOT.jar")).doesNotExist(); final Path deploymentLib = buildDir.toPath().resolve("quarkus-app").resolve("lib").resolve("deployment"); - assertThat(deploymentLib.resolve("net.bytebuddy.byte-buddy-1.11.12.jar")).exists(); + assertThat(deploymentLib.resolve("org.acme.transitive-dependency-1.0-SNAPSHOT.jar")).exists(); } @Test From fda09f9b4bea0af7d4759e2a6dca73a880421886 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Fri, 3 Sep 2021 14:23:30 +0200 Subject: [PATCH 36/60] Fix gradle deployment classpath leak (cherry picked from commit 66126ab4a48f8f0413bf269c21669e5324f082d7) --- .../java/io/quarkus/gradle/QuarkusPlugin.java | 10 +- ...ApplicationDeploymentClasspathBuilder.java | 18 +-- .../ConditionalDependenciesEnabler.java | 115 +++--------------- .../gradle/dependency/DependencyUtils.java | 80 ++++++++++++ .../ext-a/runtime/build.gradle | 1 + .../gradle/ConditionalDependenciesTest.java | 1 + 6 files changed, 110 insertions(+), 115 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index fb1b6b8618a33..c2f3d735004a5 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -33,7 +33,6 @@ import io.quarkus.gradle.builder.QuarkusModelBuilder; import io.quarkus.gradle.dependency.ApplicationDeploymentClasspathBuilder; import io.quarkus.gradle.dependency.ConditionalDependenciesEnabler; -import io.quarkus.gradle.dependency.ExtensionDependency; import io.quarkus.gradle.extension.QuarkusPluginExtension; import io.quarkus.gradle.extension.SourceSetExtension; import io.quarkus.gradle.tasks.QuarkusAddExtension; @@ -273,15 +272,14 @@ private void afterEvaluate(Project project) { ConditionalDependenciesEnabler conditionalDependenciesEnabler = new ConditionalDependenciesEnabler(project); ApplicationDeploymentClasspathBuilder deploymentClasspathBuilder = new ApplicationDeploymentClasspathBuilder(project); - Set commonExtensions = conditionalDependenciesEnabler + conditionalDependenciesEnabler .declareConditionalDependencies(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME); - deploymentClasspathBuilder.createBuildClasspath(commonExtensions, JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME); - deploymentClasspathBuilder.addCommonExtension(commonExtensions); + deploymentClasspathBuilder.createBuildClasspath(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, true); for (String baseConfiguration : CONDITIONAL_DEPENDENCY_LOOKUP) { - Set extensionDependencies = conditionalDependenciesEnabler + conditionalDependenciesEnabler .declareConditionalDependencies(baseConfiguration); - deploymentClasspathBuilder.createBuildClasspath(extensionDependencies, baseConfiguration); + deploymentClasspathBuilder.createBuildClasspath(baseConfiguration, false); } final HashSet visited = new HashSet<>(); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java index e9e53d11a2e59..f49fe94a7138f 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java @@ -22,25 +22,25 @@ public static String toDeploymentConfigurationName(String baseConfigurationName) return baseConfigurationName + DEPLOYMENT_CONFIGURATION_SUFFIX; } - public void createBuildClasspath(Set extensions, String baseConfigurationName) { + public void createBuildClasspath(String baseConfigurationName, boolean common) { String deploymentConfigurationName = toDeploymentConfigurationName(baseConfigurationName); project.getConfigurations().create(deploymentConfigurationName); DependencyHandler dependencies = project.getDependencies(); - for (ExtensionDependency extension : extensions) { - if (commonExtensions.contains(extension)) { + Set firstLevelExtensions = DependencyUtils.loadQuarkusExtension(project, + project.getConfigurations().findByName(baseConfigurationName)); + for (ExtensionDependency extension : firstLevelExtensions) { + if (common) { + commonExtensions.add(extension); + } else if (commonExtensions.contains(extension)) { continue; } extension.createDeploymentVariant(dependencies); - createDeploymentClasspath(deploymentConfigurationName, extension, dependencies); + requireDeploymentDependency(deploymentConfigurationName, extension, dependencies); } } - public void addCommonExtension(Set commonExtensions) { - this.commonExtensions.addAll(commonExtensions); - } - - private void createDeploymentClasspath(String deploymentConfigurationName, ExtensionDependency extension, + private void requireDeploymentDependency(String deploymentConfigurationName, ExtensionDependency extension, DependencyHandler dependencies) { ExternalDependency dependency = (ExternalDependency) dependencies.add(deploymentConfigurationName, extension.asDependencyNotation()); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index fcb942ff7dbc9..39414d74335b7 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -1,66 +1,46 @@ package io.quarkus.gradle.dependency; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; -import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ResolvedArtifact; -import io.quarkus.bootstrap.BootstrapConstants; -import io.quarkus.bootstrap.model.AppArtifactCoords; -import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.bootstrap.util.BootstrapUtils; -import io.quarkus.bootstrap.util.ZipUtils; - public class ConditionalDependenciesEnabler { private final Map featureVariants = new HashMap<>(); + private final Project project; public ConditionalDependenciesEnabler(Project project) { this.project = project; } - public Set declareConditionalDependencies(String baseConfigurationName) { + public void declareConditionalDependencies(String baseConfigurationName) { featureVariants.clear(); Configuration resolvedConfiguration = DependencyUtils.duplicateConfiguration(project, project.getConfigurations().getByName(baseConfigurationName)); + Set runtimeArtifacts = resolvedConfiguration.getResolvedConfiguration().getResolvedArtifacts(); List extensions = collectExtensionsForResolution(runtimeArtifacts); featureVariants.putAll(extractFeatureVariants(extensions)); resolveConditionalDependencies(extensions, resolvedConfiguration, baseConfigurationName); - - Configuration resolvedExtensionsConfiguration = DependencyUtils.duplicateConfiguration(project, - project.getConfigurations().getByName(baseConfigurationName)); - Set enabledExtension = getEnabledExtension(resolvedExtensionsConfiguration); - project.getConfigurations().remove(resolvedExtensionsConfiguration); - - return enabledExtension; } private List collectExtensionsForResolution(Set runtimeArtifacts) { List firstLevelExtensions = new ArrayList<>(); for (ResolvedArtifact artifact : runtimeArtifacts) { - ExtensionDependency extension = getExtensionInfoOrNull(artifact); + ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(project, artifact); if (extension != null) { if (!extension.conditionalDependencies.isEmpty()) { if (extension.needsResolution(runtimeArtifacts)) { @@ -72,43 +52,19 @@ private List collectExtensionsForResolution(Set getEnabledExtension(Configuration classpath) { - Set enabledExtensions = new HashSet<>(); - for (ResolvedArtifact artifact : classpath.getResolvedConfiguration().getResolvedArtifacts()) { - ExtensionDependency extension = getExtensionInfoOrNull(artifact); - if (extension != null) { - enabledExtensions.add(extension); - } - } - return enabledExtensions; - } - - private Map extractFeatureVariants(List extensions) { - Map possibleVariant = new HashMap<>(); - for (ExtensionDependency extension : extensions) { - for (Dependency dependency : extension.conditionalDependencies) { - possibleVariant.put(DependencyUtils.asFeatureName(dependency), extension); - } - } - return possibleVariant; - } - private void resolveConditionalDependencies(List conditionalExtensions, Configuration existingDependencies, String baseConfigurationName) { final Configuration conditionalDeps = createConditionalDependenciesConfiguration(existingDependencies, conditionalExtensions); boolean hasChanged = false; - Map validConditionalDependencies = new HashMap<>(); List newConditionalDependencies = new ArrayList<>(); newConditionalDependencies.addAll(conditionalExtensions); for (ResolvedArtifact artifact : conditionalDeps.getResolvedConfiguration().getResolvedArtifacts()) { - ExtensionDependency extensionDependency = getExtensionInfoOrNull(artifact); + ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); if (extensionDependency != null) { if (DependencyUtils.exist(conditionalDeps.getResolvedConfiguration().getResolvedArtifacts(), extensionDependency.dependencyConditions)) { enableConditionalDependency(extensionDependency.extensionId); - validConditionalDependencies.put(DependencyUtils.asFeatureName(extensionDependency.extensionId), - extensionDependency); if (!extensionDependency.conditionalDependencies.isEmpty()) { featureVariants.putAll(extractFeatureVariants(Collections.singletonList(extensionDependency))); } @@ -130,6 +86,16 @@ private void resolveConditionalDependencies(List conditiona } } + private Map extractFeatureVariants(List extensions) { + Map possibleVariant = new HashMap<>(); + for (ExtensionDependency extension : extensions) { + for (Dependency dependency : extension.conditionalDependencies) { + possibleVariant.put(DependencyUtils.asFeatureName(dependency), extension); + } + } + return possibleVariant; + } + private Configuration createConditionalDependenciesConfiguration(Configuration existingDeps, List extensions) { Configuration newConfiguration = existingDeps.copy(); @@ -153,55 +119,4 @@ private void enableConditionalDependency(ModuleVersionIdentifier dependency) { } extension.importConditionalDependency(project.getDependencies(), dependency); } - - private ExtensionDependency getExtensionInfoOrNull(ResolvedArtifact artifact) { - ModuleVersionIdentifier artifactId = artifact.getModuleVersion().getId(); - File artifactFile = artifact.getFile(); - if (!artifactFile.exists() || !"jar".equals(artifact.getExtension())) { - return null; - } - if (artifactFile.isDirectory()) { - Path descriptorPath = artifactFile.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(descriptorPath, artifactId); - } - } else { - try (FileSystem artifactFs = ZipUtils.newFileSystem(artifactFile.toPath())) { - Path descriptorPath = artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(descriptorPath)) { - return loadExtensionInfo(descriptorPath, artifactId); - } - } catch (IOException e) { - throw new GradleException("Failed to read " + artifactFile, e); - } - } - return null; - } - - private ExtensionDependency loadExtensionInfo(Path descriptorPath, ModuleVersionIdentifier exentionId) { - final Properties extensionProperties = new Properties(); - try (BufferedReader reader = Files.newBufferedReader(descriptorPath)) { - extensionProperties.load(reader); - } catch (IOException e) { - throw new GradleException("Failed to load " + descriptorPath, e); - } - AppArtifactCoords deploymentModule = AppArtifactCoords - .fromString(extensionProperties.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT)); - final List conditionalDependencies; - if (extensionProperties.containsKey(BootstrapConstants.CONDITIONAL_DEPENDENCIES)) { - final String[] deps = BootstrapUtils - .splitByWhitespace(extensionProperties.getProperty(BootstrapConstants.CONDITIONAL_DEPENDENCIES)); - conditionalDependencies = new ArrayList<>(deps.length); - for (String conditionalDep : deps) { - conditionalDependencies.add(DependencyUtils.create(project.getDependencies(), conditionalDep)); - } - } else { - conditionalDependencies = Collections.emptyList(); - } - - final AppArtifactKey[] constraints = BootstrapUtils - .parseDependencyCondition(extensionProperties.getProperty(BootstrapConstants.DEPENDENCY_CONDITION)); - return new ExtensionDependency(exentionId, deploymentModule, conditionalDependencies, - constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); - } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/DependencyUtils.java b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/DependencyUtils.java index d10ba758e398c..91e17dbe0bf19 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/DependencyUtils.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/dependency/DependencyUtils.java @@ -1,10 +1,20 @@ package io.quarkus.gradle.dependency; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Properties; import java.util.Set; +import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.UnknownDomainObjectException; import org.gradle.api.artifacts.Configuration; @@ -18,8 +28,11 @@ import org.gradle.api.initialization.IncludedBuild; import org.gradle.api.plugins.JavaPlugin; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.util.BootstrapUtils; +import io.quarkus.bootstrap.util.ZipUtils; public class DependencyUtils { @@ -74,6 +87,21 @@ public static boolean exists(Set runtimeArtifacts, Dependency return false; } + public static Set loadQuarkusExtension(Project project, Configuration configuration) { + Set extensions = new HashSet<>(); + Configuration configurationCopy = duplicateConfiguration(project, configuration); + + Set resolvedArtifacts = configurationCopy.getResolvedConfiguration().getResolvedArtifacts(); + for (ResolvedArtifact artifact : resolvedArtifacts) { + ExtensionDependency extension = getExtensionInfoOrNull(project, artifact); + if (extension != null) { + extensions.add(extension); + } + } + + return extensions; + } + public static boolean isTestFixtureDependency(Dependency dependency) { if (!(dependency instanceof ModuleDependency)) { return false; @@ -138,4 +166,56 @@ public static String asFeatureName(ModuleVersionIdentifier version) { public static String asFeatureName(Dependency version) { return version.getGroup() + ":" + version.getName(); } + + public static ExtensionDependency getExtensionInfoOrNull(Project project, ResolvedArtifact artifact) { + ModuleVersionIdentifier artifactId = artifact.getModuleVersion().getId(); + File artifactFile = artifact.getFile(); + if (!artifactFile.exists() || !"jar".equals(artifact.getExtension())) { + return null; + } + if (artifactFile.isDirectory()) { + Path descriptorPath = artifactFile.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH); + if (Files.exists(descriptorPath)) { + return loadExtensionInfo(project, descriptorPath, artifactId); + } + } else { + try (FileSystem artifactFs = ZipUtils.newFileSystem(artifactFile.toPath())) { + Path descriptorPath = artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH); + if (Files.exists(descriptorPath)) { + return loadExtensionInfo(project, descriptorPath, artifactId); + } + } catch (IOException e) { + throw new GradleException("Failed to read " + artifactFile, e); + } + } + return null; + } + + private static ExtensionDependency loadExtensionInfo(Project project, Path descriptorPath, + ModuleVersionIdentifier exentionId) { + final Properties extensionProperties = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(descriptorPath)) { + extensionProperties.load(reader); + } catch (IOException e) { + throw new GradleException("Failed to load " + descriptorPath, e); + } + AppArtifactCoords deploymentModule = AppArtifactCoords + .fromString(extensionProperties.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT)); + final List conditionalDependencies; + if (extensionProperties.containsKey(BootstrapConstants.CONDITIONAL_DEPENDENCIES)) { + final String[] deps = BootstrapUtils + .splitByWhitespace(extensionProperties.getProperty(BootstrapConstants.CONDITIONAL_DEPENDENCIES)); + conditionalDependencies = new ArrayList<>(deps.length); + for (String conditionalDep : deps) { + conditionalDependencies.add(DependencyUtils.create(project.getDependencies(), conditionalDep)); + } + } else { + conditionalDependencies = Collections.emptyList(); + } + + final AppArtifactKey[] constraints = BootstrapUtils + .parseDependencyCondition(extensionProperties.getProperty(BootstrapConstants.DEPENDENCY_CONDITION)); + return new ExtensionDependency(exentionId, deploymentModule, conditionalDependencies, + constraints == null ? Collections.emptyList() : Arrays.asList(constraints)); + } } diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle index 7595b95a938b6..76e136b702273 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-a/runtime/build.gradle @@ -8,6 +8,7 @@ dependencies { api("org.acme:simple-dependency:1.0-SNAPSHOT") { exclude module: "transitive-dependency" } + api("io.quarkus:quarkus-hibernate-reactive-panache") } publishing { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java index fb6a41b53dc25..4dacdd806b78e 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java @@ -86,6 +86,7 @@ public void shouldImportConditionalDependency() throws IOException, URISyntaxExc final Path deploymentLib = buildDir.toPath().resolve("quarkus-app").resolve("lib").resolve("deployment"); assertThat(deploymentLib.resolve("org.acme.transitive-dependency-1.0-SNAPSHOT.jar")).exists(); + assertThat(deploymentLib.resolve("io.quarkus.quarkus-agroal-" + getQuarkusVersion() + ".jar")).doesNotExist(); } @Test From 86e7efdbe5666505db8f7201454b0ef6c0c3d419 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Thu, 2 Sep 2021 12:31:20 -0700 Subject: [PATCH 37/60] Update JNA to work with Apple M1 (cherry picked from commit df57f2d26ea9a4a3bf375aa2656a92c6ed01f2ac) --- 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 79f218fb98e85..b73c4d913d531 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -177,7 +177,7 @@ 5.2.Final 2.1.SP1 3.11.2 - 5.3.1 + 5.8.0 4.8 1.1.4.Final 14.0.0 From 484e9e46eac45c188a2c1992527574726eec913e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 3 Sep 2021 10:54:48 +0300 Subject: [PATCH 38/60] Ensure that eager security handling works in native mode Fixes: #19878 (cherry picked from commit 690210e6db847c84736d0cf63c255958943e7c40) --- .../deployment/ResteasyReactiveProcessor.java | 49 ++++++++++++------- .../common/processor/EndpointIndexer.java | 32 ++++++++++-- .../io/quarkus/it/keycloak/HelloResource.java | 4 ++ .../it/keycloak/HelloResourceBase.java | 11 +++++ .../quarkus/it/keycloak/IHelloResource.java | 14 ++++++ .../quarkus/it/keycloak/HelloResourceIT.java | 7 +++ .../it/keycloak/HelloResourceTest.java | 18 +++++++ 7 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/HelloResource.java create mode 100644 integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/HelloResourceBase.java create mode 100644 integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/IHelloResource.java create mode 100644 integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/HelloResourceIT.java create mode 100644 integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/HelloResourceTest.java 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 d9b37a93faab0..62fac03831ff8 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 @@ -10,11 +10,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.ws.rs.Priorities; @@ -41,13 +41,13 @@ import org.jboss.resteasy.reactive.common.model.ResourceDynamicFeature; import org.jboss.resteasy.reactive.common.model.ResourceFeature; import org.jboss.resteasy.reactive.common.model.ResourceInterceptors; -import org.jboss.resteasy.reactive.common.model.ResourceMethod; import org.jboss.resteasy.reactive.common.model.ResourceReader; import org.jboss.resteasy.reactive.common.model.ResourceWriter; import org.jboss.resteasy.reactive.common.processor.AdditionalReaderWriter; import org.jboss.resteasy.reactive.common.processor.AdditionalReaders; import org.jboss.resteasy.reactive.common.processor.AdditionalWriters; import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler; +import org.jboss.resteasy.reactive.common.processor.EndpointIndexer; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult; @@ -335,7 +335,7 @@ public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem QuarkusServerEndpointIndexer.Builder serverEndpointIndexerBuilder = new QuarkusServerEndpointIndexer.Builder() .addMethodScanners( - methodScanners.stream().map(MethodScannerBuildItem::getMethodScanner).collect(Collectors.toList())) + methodScanners.stream().map(MethodScannerBuildItem::getMethodScanner).collect(toList())) .setIndex(index) .setFactoryCreator(new QuarkusFactoryCreator(recorder, beanContainerBuildItem.getValue())) .setEndpointInvokerFactory(new QuarkusInvokerFactory(generatedClassBuildItemBuildProducer, recorder)) @@ -353,13 +353,20 @@ public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem .setClassLevelExceptionMappers( classLevelExceptionMappers.isPresent() ? classLevelExceptionMappers.get().getMappers() : Collections.emptyMap()) - .setResourceMethodCallback(new Consumer>() { + .setResourceMethodCallback(new Consumer<>() { @Override - public void accept(Map.Entry entry) { - MethodInfo method = entry.getKey(); + public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { + MethodInfo method = entry.getMethodInfo(); String source = ResteasyReactiveProcessor.class.getSimpleName() + " > " + method.declaringClass() + "[" + method + "]"; + ClassInfo classInfoWithSecurity = consumeStandardSecurityAnnotations(method, + entry.getActualEndpointInfo(), index, c -> c); + if (classInfoWithSecurity != null) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, true, false, + entry.getActualEndpointInfo().name().toString())); + } + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem.Builder() .type(method.returnType()) .index(index) @@ -648,21 +655,29 @@ MethodScannerBuildItem integrateEagerSecurity(Capabilities capabilities, Combine @Override public List scan(MethodInfo method, ClassInfo actualEndpointClass, Map methodContext) { - if (SecurityTransformerUtils.hasStandardSecurityAnnotation(method)) { - return Collections.singletonList(new EagerSecurityHandler.Customizer()); - } - ClassInfo c = actualEndpointClass; - while (c.superName() != null) { - if (SecurityTransformerUtils.hasStandardSecurityAnnotation(c)) { - return Collections.singletonList(new EagerSecurityHandler.Customizer()); - } - c = index.getClassByName(c.superName()); - } - return Collections.emptyList(); + return Objects.requireNonNullElse( + consumeStandardSecurityAnnotations(method, actualEndpointClass, index, + (c) -> Collections.singletonList(new EagerSecurityHandler.Customizer())), + Collections.emptyList()); } }); } + private T consumeStandardSecurityAnnotations(MethodInfo methodInfo, ClassInfo classInfo, IndexView index, + Function function) { + if (SecurityTransformerUtils.hasStandardSecurityAnnotation(methodInfo)) { + return function.apply(methodInfo.declaringClass()); + } + ClassInfo c = classInfo; + while (c.superName() != null) { + if (SecurityTransformerUtils.hasStandardSecurityAnnotation(c)) { + return function.apply(c); + } + c = index.getClassByName(c.superName()); + } + return null; + } + private Optional getAppPath(Optional newPropertyValue) { Optional legacyProperty = ConfigProvider.getConfig().getOptionalValue("quarkus.rest.path", String.class); if (legacyProperty.isPresent()) { 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 517018911779c..851d59e5ea558 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 @@ -177,7 +177,7 @@ public abstract class EndpointIndexer> classLevelExceptionMappers; private final Function> factoryCreator; - private final Consumer> resourceMethodCallback; + private final Consumer resourceMethodCallback; protected EndpointIndexer(Builder builder) { this.index = builder.index; @@ -545,7 +545,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf handleAdditionalMethodProcessing((METHOD) method, currentClassInfo, currentMethodInfo); if (resourceMethodCallback != null) { - resourceMethodCallback.accept(new AbstractMap.SimpleEntry<>(currentMethodInfo, method)); + resourceMethodCallback.accept(new ResourceMethodCallbackData(currentMethodInfo, actualEndpointInfo, method)); } return method; } catch (Exception e) { @@ -1133,7 +1133,7 @@ public static abstract class Builder, B private AdditionalWriters additionalWriters; private boolean hasRuntimeConverters; private Map> classLevelExceptionMappers; - private Consumer> resourceMethodCallback; + private Consumer resourceMethodCallback; public B setDefaultBlocking(BlockingDefault defaultBlocking) { this.defaultBlocking = defaultBlocking; @@ -1195,7 +1195,7 @@ public B setClassLevelExceptionMappers(Map> classLe return (B) this; } - public B setResourceMethodCallback(Consumer> resourceMethodCallback) { + public B setResourceMethodCallback(Consumer resourceMethodCallback) { this.resourceMethodCallback = resourceMethodCallback; return (B) this; } @@ -1203,4 +1203,28 @@ public B setResourceMethodCallback(Consumer Date: Fri, 3 Sep 2021 13:50:29 +0200 Subject: [PATCH 39/60] ArC - make it possible to identify additional bean archives (cherry picked from commit c91431c9b1eea471d62da10a819be41e279dc7d9) --- .../BeanArchivePredicateBuildItem.java | 25 +++++++++++ .../arc/deployment/BeanArchiveProcessor.java | 41 +++++++++++++++---- 2 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java new file mode 100644 index 0000000000000..26aabbf179746 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java @@ -0,0 +1,25 @@ +package io.quarkus.arc.deployment; + +import java.util.function.Predicate; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.deployment.ApplicationArchive; + +/** + * + * By default, only explict/implicit bean archives (as defined by the spec) are considered during the bean discovery. However, + * extensions can register a logic to identify additional bean archives. + */ +public final class BeanArchivePredicateBuildItem extends MultiBuildItem { + + private final Predicate predicate; + + public BeanArchivePredicateBuildItem(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java index 2bb662fd5a7b0..8133de96db552 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java @@ -1,6 +1,11 @@ package io.quarkus.arc.deployment; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; @@ -35,12 +40,13 @@ public BeanArchiveIndexBuildItem build(ArcConfig config, ApplicationArchivesBuil List additionalBeanDefiningAnnotations, List additionalBeans, List generatedBeans, LiveReloadBuildItem liveReloadBuildItem, BuildProducer generatedClass, - CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems) + CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems, + List beanArchivePredicates) throws Exception { // First build an index from application archives IndexView applicationIndex = buildApplicationIndex(config, applicationArchivesBuildItem, - additionalBeanDefiningAnnotations, customScopes, excludeDependencyBuildItems); + additionalBeanDefiningAnnotations, customScopes, excludeDependencyBuildItems, beanArchivePredicates); // Then build additional index for beans added by extensions Indexer additionalBeanIndexer = new Indexer(); @@ -81,7 +87,8 @@ public BeanArchiveIndexBuildItem build(ArcConfig config, ApplicationArchivesBuil private IndexView buildApplicationIndex(ArcConfig config, ApplicationArchivesBuildItem applicationArchivesBuildItem, List additionalBeanDefiningAnnotations, - CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems) { + CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems, + List beanArchivePredicates) { Set archives = applicationArchivesBuildItem.getAllApplicationArchives(); @@ -116,10 +123,8 @@ private IndexView buildApplicationIndex(ArcConfig config, ApplicationArchivesBui continue; } IndexView index = archive.getIndex(); - // NOTE: Implicit bean archive without beans.xml contains one or more bean classes with a bean defining annotation and no extension - if (archive.getChildPath("META-INF/beans.xml") != null || archive.getChildPath("WEB-INF/beans.xml") != null - || (index.getAllKnownImplementors(DotNames.EXTENSION).isEmpty() - && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) { + if (isExplicitBeanArchive(archive) || isImplicitBeanArchive(index, beanDefiningAnnotations) + || isAdditionalBeanArchive(archive, beanArchivePredicates)) { indexes.add(index); } } @@ -127,6 +132,26 @@ && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) { return CompositeIndex.create(indexes); } + private boolean isExplicitBeanArchive(ApplicationArchive archive) { + return archive.getChildPath("META-INF/beans.xml") != null || archive.getChildPath("WEB-INF/beans.xml") != null; + } + + private boolean isImplicitBeanArchive(IndexView index, Set beanDefiningAnnotations) { + // NOTE: Implicit bean archive without beans.xml contains one or more bean classes with a bean defining annotation and no extension + return index.getAllKnownImplementors(DotNames.EXTENSION).isEmpty() + && containsBeanDefiningAnnotation(index, beanDefiningAnnotations); + } + + private boolean isAdditionalBeanArchive(ApplicationArchive archive, + List beanArchivePredicates) { + for (BeanArchivePredicateBuildItem p : beanArchivePredicates) { + if (p.getPredicate().test(archive)) { + return true; + } + } + return false; + } + private boolean isApplicationArchiveExcluded(ArcConfig config, List excludeDependencyBuildItems, ApplicationArchive archive) { if (archive.getArtifactKey() != null) { From c05ad99af18ea5eb37dfcb751588d5394dc9d909 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 3 Sep 2021 13:52:14 +0200 Subject: [PATCH 40/60] gRPC - register additional bean archives - an application archive that contains a MutinyBean implementor is an additional bean archive - resolves #19864 (cherry picked from commit 13bc4d27509dad67c6ef4fcfb4148ab23d802214) --- .../grpc/deployment/GrpcServerProcessor.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java index a87294e34fabb..d162a7dd8efb5 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java @@ -28,12 +28,14 @@ import io.grpc.internal.ServerImpl; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.BeanArchivePredicateBuildItem; import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem; import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; @@ -218,7 +220,7 @@ private Set gatherBlockingMethods(ClassInfo service) { AnnotationsTransformerBuildItem transformUserDefinedServices(CombinedIndexBuildItem combinedIndexBuildItem, CustomScopeAnnotationsBuildItem customScopes) { // User-defined services usually only declare the @GrpcService qualifier - // We need to add @GrpcEnableRequestContext and @Singleton if needed + // We need to add @GrpcEnableRequestContext and @Singleton if needed Set userDefinedServices = new HashSet<>(); for (AnnotationInstance annotation : combinedIndexBuildItem.getIndex().getAnnotations(GrpcDotNames.GRPC_SERVICE)) { if (annotation.target().kind() == Kind.CLASS) { @@ -393,4 +395,16 @@ void configureMetrics(GrpcBuildTimeConfig configuration, Optional() { + + @Override + public boolean test(ApplicationArchive archive) { + // Every archive that contains a generated implementor of MutinyBean is considered a bean archive + return !archive.getIndex().getKnownDirectImplementors(GrpcDotNames.MUTINY_BEAN).isEmpty(); + } + }); + } + } From 6ba3751620f47fa0cb02aea31a0474e0f6398165 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Thu, 2 Sep 2021 16:21:05 +0300 Subject: [PATCH 41/60] fix: KubernetesClientErrorHandler always throws a RuntimeException (cherry picked from commit 57b1ffd5135de82017fe6398e87c67aa09f82c02) --- .../deployment/KubernetesClientErrorHandler.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java index ed127a722a83b..58f3eff0b634e 100644 --- a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java +++ b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java @@ -12,11 +12,11 @@ public static void handle(Exception e) { if (e.getCause() instanceof SSLHandshakeException) { LOG.error( "The application could not be deployed to the cluster because the Kubernetes API Server certificates are not trusted. The certificates can be configured using the relevant configuration properties under the 'quarkus.kubernetes-client' config root, or \"quarkus.kubernetes-client.trust-certs=true\" can be set to explicitly trust the certificates (not recommended)"); - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); } } } From 0371458ae18622c9f18382768f880b801dfc94f2 Mon Sep 17 00:00:00 2001 From: Falko Modler Date: Sun, 5 Sep 2021 19:12:31 +0200 Subject: [PATCH 42/60] Fix combination of multiple `@Nested` tests with root lifecycle methods Fixes #19843 (cherry picked from commit 34391e670daa47ae9491d64e4c57d411f77e986a) --- .../java/io/quarkus/it/main/JaxbTestCase.java | 31 +---- .../it/main/QuarkusTestNestedTestCase.java | 127 ++++++++++++++++++ .../test/junit/QuarkusTestExtension.java | 11 +- 3 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedTestCase.java diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/JaxbTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxbTestCase.java index 62110d4baeb4a..8c32cf33add5c 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/JaxbTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxbTestCase.java @@ -2,11 +2,6 @@ import static org.hamcrest.Matchers.contains; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -15,27 +10,9 @@ @QuarkusTest public class JaxbTestCase { - private static final AtomicInteger count = new AtomicInteger(0); - - @BeforeEach - public void beforeInEnclosing() { - count.incrementAndGet(); + @Test + public void testNews() { + RestAssured.when().get("/test/jaxb/getnews").then() + .body("author", contains("Emmanuel Bernard")); } - - @Nested - class SomeClass { - - @BeforeEach - public void beforeInTest() { - count.incrementAndGet(); - } - - @Test - public void testNews() { - RestAssured.when().get("/test/jaxb/getnews").then() - .body("author", contains("Emmanuel Bernard")); - Assertions.assertEquals(2, count.get()); - } - } - } diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedTestCase.java new file mode 100644 index 0000000000000..30bb5e2fc1430 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedTestCase.java @@ -0,0 +1,127 @@ +package io.quarkus.it.main; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import io.quarkus.test.junit.QuarkusTest; + +/** + * Tests {@link Nested} support of {@link QuarkusTest}. Notes: + *
    + *
  • to avoid unexpected execution order, don't use surefire's {@code -Dtest=...}, use {@code -Dgroups=nested} instead
  • + *
  • order of nested test classes is reversed by JUnit (and there's no way to enforce a specific order)
  • + *
+ */ +@QuarkusTest +@Tag("nested") +public class QuarkusTestNestedTestCase { + + private static final AtomicInteger COUNT_BEFORE_ALL = new AtomicInteger(0); + private static final AtomicInteger COUNT_BEFORE_EACH = new AtomicInteger(0); + private static final AtomicInteger COUNT_TEST = new AtomicInteger(0); + private static final AtomicInteger COUNT_AFTER_EACH = new AtomicInteger(0); + private static final AtomicInteger COUNT_AFTER_ALL = new AtomicInteger(0); + + @BeforeAll + static void beforeAll() { + COUNT_BEFORE_ALL.incrementAndGet(); + } + + @BeforeEach + void beforeEach() { + COUNT_BEFORE_EACH.incrementAndGet(); + } + + @Test + void test() { + assertEquals(1, COUNT_BEFORE_ALL.get(), "COUNT_BEFORE_ALL"); + assertEquals(1, COUNT_BEFORE_EACH.get(), "COUNT_BEFORE_EACH"); + assertEquals(0, COUNT_TEST.getAndIncrement(), "COUNT_TEST"); + assertEquals(0, COUNT_AFTER_EACH.get(), "COUNT_AFTER_EACH"); + assertEquals(0, COUNT_AFTER_ALL.get(), "COUNT_AFTER_ALL"); + } + + @Nested + @TestMethodOrder(OrderAnnotation.class) + class FirstNested { + + @BeforeEach + void beforeEach() { + COUNT_BEFORE_EACH.incrementAndGet(); + } + + @Test + @Order(1) + void testOne() { + assertEquals(1, COUNT_BEFORE_ALL.get(), "COUNT_BEFORE_ALL"); + assertEquals(5, COUNT_BEFORE_EACH.get(), "COUNT_BEFORE_EACH"); + assertEquals(2, COUNT_TEST.getAndIncrement(), "COUNT_TEST"); + assertEquals(3, COUNT_AFTER_EACH.get(), "COUNT_AFTER_EACH"); + assertEquals(0, COUNT_AFTER_ALL.get(), "COUNT_AFTER_ALL"); + } + + @Test + @Order(2) + void testTwo() { + assertEquals(1, COUNT_BEFORE_ALL.get(), "COUNT_BEFORE_ALL"); + assertEquals(7, COUNT_BEFORE_EACH.get(), "COUNT_BEFORE_EACH"); + assertEquals(3, COUNT_TEST.getAndIncrement(), "COUNT_TEST"); + assertEquals(5, COUNT_AFTER_EACH.get(), "COUNT_AFTER_EACH"); + assertEquals(0, COUNT_AFTER_ALL.get(), "COUNT_AFTER_ALL"); + } + + @AfterEach + void afterEach() { + COUNT_AFTER_EACH.incrementAndGet(); + } + } + + @Nested + class SecondNested { + + @BeforeEach + void beforeEach() { + COUNT_BEFORE_EACH.incrementAndGet(); + } + + @Test + void testOne() { + assertEquals(1, COUNT_BEFORE_ALL.get(), "COUNT_BEFORE_ALL"); + assertEquals(3, COUNT_BEFORE_EACH.get(), "COUNT_BEFORE_EACH"); + assertEquals(1, COUNT_TEST.getAndIncrement(), "COUNT_TEST"); + assertEquals(1, COUNT_AFTER_EACH.get(), "COUNT_AFTER_EACH"); + assertEquals(0, COUNT_AFTER_ALL.get(), "COUNT_AFTER_ALL"); + } + + @AfterEach + void afterEach() { + COUNT_AFTER_EACH.incrementAndGet(); + } + } + + @AfterEach + void afterEach() { + COUNT_AFTER_EACH.incrementAndGet(); + } + + @AfterAll + static void afterAll() { + assertEquals(1, COUNT_BEFORE_ALL.get(), "COUNT_BEFORE_ALL"); + assertEquals(7, COUNT_BEFORE_EACH.get(), "COUNT_BEFORE_EACH"); + assertEquals(4, COUNT_TEST.get(), "COUNT_TEST"); + assertEquals(7, COUNT_AFTER_EACH.get(), "COUNT_AFTER_EACH"); + assertEquals(0, COUNT_AFTER_ALL.getAndIncrement(), "COUNT_AFTER_ALL"); + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 5e91160ff6ff6..65ece2f7ed1ae 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -197,8 +196,6 @@ public void run() { } }; - private final IdentityHashMap tcclMethodCache = new IdentityHashMap<>(); - static { ClassLoader classLoader = QuarkusTestExtension.class.getClassLoader(); if (classLoader instanceof QuarkusClassLoader) { @@ -432,7 +429,6 @@ public void close() throws IOException { } tm.close(); } finally { - tcclMethodCache.clear(); GroovyCacheCleaner.clearGroovyCache(); if (hangTaskKey != null) { hangTaskKey.cancel(true); @@ -1097,10 +1093,8 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation private Method determineTCCLExtensionMethod(Method originalMethod, Class c) throws ClassNotFoundException { - Method newMethod = tcclMethodCache.get(originalMethod); - if (newMethod != null) { - return newMethod; - } + + Method newMethod = null; while (c != Object.class) { if (c.getName().equals(originalMethod.getDeclaringClass().getName())) { try { @@ -1124,7 +1118,6 @@ private Method determineTCCLExtensionMethod(Method originalMethod, Class c) } c = c.getSuperclass(); } - tcclMethodCache.put(originalMethod, newMethod); return newMethod; } From 31dc74f2a2ba7fb858a75abd1843939a4a4d9f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Marti=C5=A1ka?= Date: Wed, 1 Sep 2021 13:20:42 +0200 Subject: [PATCH 43/60] SmallRye GraphQL 1.3.2 (cherry picked from commit 40ced5eed45d9ee94e42df8e99b46fa9043410fa) --- bom/application/pom.xml | 2 +- .../runtime/GraphQLClientConfigurationMergerBean.java | 10 +++++----- .../client/runtime/SmallRyeGraphQLClientRecorder.java | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index b73c4d913d531..d56b715a2377b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -47,7 +47,7 @@ 3.1.1 3.0.1 2.1.10 - 1.3.1 + 1.3.2 2.0.1 5.2.1 3.2.1 diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java index b14d2983bc62a..20eb364655d00 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java @@ -13,14 +13,13 @@ * On startup, this beans takes Quarkus-specific configuration of GraphQL clients (quarkus.* properties) * and merges this configuration with the configuration parsed by SmallRye GraphQL itself (CLIENT/mp-graphql/* properties) * - * The resulting merged configuration resides in the application-scoped `io.smallrye.graphql.client.GraphQLClientConfiguration` + * The resulting merged configuration resides in `io.smallrye.graphql.client.GraphQLClientsConfiguration` * * Quarkus configuration overrides SmallRye configuration where applicable. */ @Singleton public class GraphQLClientConfigurationMergerBean { - @Inject GraphQLClientsConfiguration upstreamConfiguration; @Inject @@ -31,6 +30,7 @@ public class GraphQLClientConfigurationMergerBean { @PostConstruct void enhanceGraphQLConfiguration() { + upstreamConfiguration = GraphQLClientsConfiguration.getInstance(); for (Map.Entry client : quarkusConfiguration.clients.entrySet()) { // the raw config key provided in the config, this might be a short class name, // so translate that into the fully qualified name if applicable @@ -43,14 +43,14 @@ void enhanceGraphQLConfiguration() { GraphQLClientConfig quarkusConfig = client.getValue(); // if SmallRye configuration does not contain this client, simply use it - if (!upstreamConfiguration.getClients().containsKey(configKey)) { + if (upstreamConfiguration.getClient(configKey) == null) { GraphQLClientConfiguration transformed = new GraphQLClientConfiguration(); transformed.setHeaders(quarkusConfig.headers); quarkusConfig.url.ifPresent(transformed::setUrl); - upstreamConfiguration.getClients().put(configKey, transformed); + upstreamConfiguration.addClient(configKey, transformed); } else { // if SmallRye configuration already contains this client, override it with the Quarkus configuration - GraphQLClientConfiguration upstreamConfig = upstreamConfiguration.getClients().get(configKey); + GraphQLClientConfiguration upstreamConfig = upstreamConfiguration.getClient(configKey); quarkusConfig.url.ifPresent(upstreamConfig::setUrl); // merge the headers if (quarkusConfig.headers != null) { diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java index 041c43129fd9c..a7bffb28e752a 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java @@ -22,7 +22,8 @@ public Supplier typesafeClientSupplier(Class targetClassName) { } public void setTypesafeApiClasses(List apiClassNames) { - GraphQLClientsConfiguration configBean = Arc.container().instance(GraphQLClientsConfiguration.class).get(); + GraphQLClientsConfiguration.setSingleApplication(true); + GraphQLClientsConfiguration configBean = GraphQLClientsConfiguration.getInstance(); List> classes = apiClassNames.stream().map(className -> { try { return Class.forName(className, true, Thread.currentThread().getContextClassLoader()); @@ -30,7 +31,7 @@ public void setTypesafeApiClasses(List apiClassNames) { throw new RuntimeException(e); } }).collect(Collectors.toList()); - configBean.apiClasses(classes, true); + configBean.addTypesafeClientApis(classes); } public RuntimeValue clientSupport(Map shortNamesToQualifiedNames) { From 10b192ba9d774b6f08207225e785c709c6bd7593 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 6 Sep 2021 13:29:53 +0100 Subject: [PATCH 44/60] Mark oidc-client and oidc-client-filter as stable and update descriptions for all OIDC extensions (cherry picked from commit 61ef8d84a29fb774619fb2a2fb3193400bd6f0e7) --- .../main/asciidoc/images/dev-ui-oidc-card.png | Bin 11227 -> 13414 bytes .../images/dev-ui-oidc-keycloak-card.png | Bin 13497 -> 15714 bytes extensions/oidc-client-filter/runtime/pom.xml | 2 +- .../resources/META-INF/quarkus-extension.yaml | 2 +- .../runtime/pom.xml | 2 +- extensions/oidc-client/runtime/pom.xml | 2 +- .../resources/META-INF/quarkus-extension.yaml | 2 +- .../oidc-token-propagation/runtime/pom.xml | 2 +- extensions/oidc/runtime/pom.xml | 2 +- 9 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/images/dev-ui-oidc-card.png b/docs/src/main/asciidoc/images/dev-ui-oidc-card.png index 5bd40cab30bc6ce9f5d3d630c1379cd37a847e26..b972a88330a7b68842de81a4284836e8bb8795dc 100644 GIT binary patch literal 13414 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 literal 11227 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�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 znIA<&Q5lLH7Q}`quarkus-oidc-client-filter Quarkus - OpenID Connect Client Filter - Runtime - Use JAX-RS client filter to get and refresh the access tokens and set them as HTTP Authorization Bearer values + Use JAX-RS Client filter to get and refresh access tokens with OpenId Connect Client and send them as HTTP Authorization Bearer tokens io.quarkus diff --git a/extensions/oidc-client-filter/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-client-filter/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 48d29eeda4cee..0ff5af4249bd9 100644 --- a/extensions/oidc-client-filter/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/oidc-client-filter/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,4 +11,4 @@ metadata: guide: "https://quarkus.io/guides/security-openid-connect-client" categories: - "security" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/oidc-client-reactive-filter/runtime/pom.xml b/extensions/oidc-client-reactive-filter/runtime/pom.xml index 31034ca04b9a7..9fbb3bd9b2593 100644 --- a/extensions/oidc-client-reactive-filter/runtime/pom.xml +++ b/extensions/oidc-client-reactive-filter/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-oidc-client-reactive-filter Quarkus - OpenID Connect Client Reactive Filter - Runtime - Use Reactive RestClient filter to get and refresh the access tokens and set them as HTTP Authorization Bearer values + Use Reactive RestClient filter to get and refresh access tokens with OpenId Connect Client and send them as HTTP Authorization Bearer tokens io.quarkus diff --git a/extensions/oidc-client/runtime/pom.xml b/extensions/oidc-client/runtime/pom.xml index f79cd02ff1613..fba3263875f86 100644 --- a/extensions/oidc-client/runtime/pom.xml +++ b/extensions/oidc-client/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-oidc-client Quarkus - OpenID Connect Client - Runtime - Use OpenID Connect Client to get and refresh access tokens + Get and refresh access tokens from OpenID Connect providers io.quarkus diff --git a/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml index a09103cec0de7..a3c71c97581f0 100644 --- a/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -10,4 +10,4 @@ metadata: guide: "https://quarkus.io/guides/security-openid-connect-client" categories: - "security" - status: "preview" \ No newline at end of file + status: "stable" diff --git a/extensions/oidc-token-propagation/runtime/pom.xml b/extensions/oidc-token-propagation/runtime/pom.xml index 1b9d5e48774f9..3f47c7e1982ea 100644 --- a/extensions/oidc-token-propagation/runtime/pom.xml +++ b/extensions/oidc-token-propagation/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-oidc-token-propagation Quarkus - OpenID Connect Token Propagation - Runtime - Use JAX-RS client filter to propagate the current access token as HTTP Authorization Bearer value + Use JAX-RS Client filter to propagate the incoming Bearer access token or token acquired from Authorization Code Flow as HTTP Authorization Bearer token io.quarkus diff --git a/extensions/oidc/runtime/pom.xml b/extensions/oidc/runtime/pom.xml index 37e725258cb06..06a08cc1d7643 100644 --- a/extensions/oidc/runtime/pom.xml +++ b/extensions/oidc/runtime/pom.xml @@ -11,7 +11,7 @@ quarkus-oidc Quarkus - OpenID Connect Adapter - Runtime - Secure your applications with OpenID Connect Adapter + Verify Bearer access tokens and authenticate users with Authorization Code Flow io.quarkus From 7c7983c9086aab939bda870045f4cd9d9ceb4ccb Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 24 Aug 2021 21:13:19 +0100 Subject: [PATCH 45/60] Upgrade the Oracle JDBC driver to benefit from JDK11 baseline (cherry picked from commit 766dd619bdf614870576a2bfef5399d1dae3c3ab) --- bom/application/pom.xml | 2 +- extensions/jdbc/jdbc-oracle/runtime/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d56b715a2377b..9eb6e1ffaacba 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -4491,7 +4491,7 @@ com.oracle.database.jdbc - ojdbc8 + ojdbc11 ${oracle-jdbc.version} diff --git a/extensions/jdbc/jdbc-oracle/runtime/pom.xml b/extensions/jdbc/jdbc-oracle/runtime/pom.xml index 9d56502238fd0..88044be2b5ec5 100644 --- a/extensions/jdbc/jdbc-oracle/runtime/pom.xml +++ b/extensions/jdbc/jdbc-oracle/runtime/pom.xml @@ -24,7 +24,7 @@ com.oracle.database.jdbc - ojdbc8 + ojdbc11 org.graalvm.nativeimage From e28fc9a2a8c4b7539e53efab82c4d3547cab7a75 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 3 Sep 2021 14:59:30 +0100 Subject: [PATCH 46/60] Upgrade to Oracle JDBC driver v 21.3.0.0 and import its BOM (cherry picked from commit f7b48a9871ad46d19cb93faa112dfe79b65988fc) --- bom/application/pom.xml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 9eb6e1ffaacba..bf872c1af0ad1 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -121,7 +121,7 @@ 2.7.4 8.0.26 7.2.2.jre8 - 21.1.0.0 + 21.3.0.0 10.14.2.0 11.5.6.0 1.2.6 @@ -353,6 +353,15 @@ import + + + com.oracle.database.jdbc + ojdbc-bom + ${oracle-jdbc.version} + pom + import + + io.smallrye.reactive @@ -4489,11 +4498,6 @@ mssql-jdbc ${mssql-jdbc.version} - - com.oracle.database.jdbc - ojdbc11 - ${oracle-jdbc.version} - org.elasticsearch.client elasticsearch-rest-client From 17504da50fede1fb8827c2e0a5d65ca9f65671ad Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 2 Sep 2021 10:53:01 +0200 Subject: [PATCH 47/60] Experimental Maven goal to check for Quarkus platform and extension updates Co-authored-by: Guillaume Smet (cherry picked from commit 05b4d363ade67cb76dd5d46760f8e32a5fa7192b) --- .../io/quarkus/maven/CheckForUpdatesMojo.java | 412 ++++++++++++++++++ .../io/quarkus/maven/CreateProjectMojo.java | 2 +- .../handlers/CreateProjectCommandHandler.java | 5 +- 3 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 devtools/maven/src/main/java/io/quarkus/maven/CheckForUpdatesMojo.java diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CheckForUpdatesMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CheckForUpdatesMojo.java new file mode 100644 index 0000000000000..4fbf90c7fdc90 --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/CheckForUpdatesMojo.java @@ -0,0 +1,412 @@ +package io.quarkus.maven; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Plugin; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.project.QuarkusProjectHelper; +import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.tools.maven.MojoMessageWriter; +import io.quarkus.registry.ExtensionCatalogResolver; +import io.quarkus.registry.RegistryResolutionException; +import io.quarkus.registry.catalog.Extension; +import io.quarkus.registry.catalog.ExtensionCatalog; +import io.quarkus.registry.catalog.ExtensionOrigin; +import io.quarkus.registry.catalog.selection.ExtensionOrigins; +import io.quarkus.registry.catalog.selection.OriginCombination; +import io.quarkus.registry.catalog.selection.OriginPreference; +import io.quarkus.registry.catalog.selection.OriginSelector; +import io.quarkus.registry.util.PlatformArtifacts; + +/** + * NOTE: this mojo is experimental + */ +@Mojo(name = "check-for-updates", requiresProject = true) +public class CheckForUpdatesMojo extends AbstractMojo { + + @Parameter(defaultValue = "${project}") + protected MavenProject project; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + private List repos; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + @Component + private RepositorySystem repoSystem; + + @Component + RemoteRepositoryManager remoteRepoManager; + + @SuppressWarnings("unchecked") + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + + getLog().warn( + "This goal is experimental. Its name, parameters, output and implementation will be evolving without the promise of keeping backward compatibility"); + + if (project.getFile() == null) { + throw new MojoExecutionException("This goal requires a project"); + } + + if (!QuarkusProjectHelper.isRegistryClientEnabled()) { + throw new MojoExecutionException("This goal requires a Quarkus extension registry client to be enabled"); + } + + final Map previousExtensions = getDirectExtensionDependencies(); + if (previousExtensions.isEmpty()) { + getLog().info("The project does not appear to depend on any Quarkus extension directly"); + return; + } + + final MavenArtifactResolver mvn; + try { + mvn = MavenArtifactResolver.builder() + .setRepositorySystem(repoSystem) + .setRepositorySystemSession( + getLog().isDebugEnabled() ? repoSession : MojoUtils.muteTransferListener(repoSession)) + .setRemoteRepositories(repos) + .setRemoteRepositoryManager(remoteRepoManager) + .build(); + } catch (Exception e) { + throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e); + } + final MojoMessageWriter log = new MojoMessageWriter(getLog()); + final ExtensionCatalogResolver catalogResolver; + try { + catalogResolver = QuarkusProjectHelper.getCatalogResolver(mvn, log); + } catch (RegistryResolutionException e) { + throw new MojoExecutionException("Failed to initialize Quarkus extension registry client", e); + } + + if (!catalogResolver.hasRegistries()) { + throw new MojoExecutionException("Configured Quarkus extension registries aren't available"); + } + + final ExtensionCatalog latestCatalog; + try { + latestCatalog = catalogResolver.resolveExtensionCatalog(); + } catch (RegistryResolutionException e) { + throw new MojoExecutionException( + "Failed to resolve the latest Quarkus extension catalog from the configured extension registries", e); + } + + final Map recommendedExtensionMap = new HashMap<>(latestCatalog.getExtensions().size()); + final Map> recommendedExtensionsByOrigin = new HashMap<>(); + for (Extension e : latestCatalog.getExtensions()) { + recommendedExtensionMap.put(e.getArtifact().getKey(), e); + for (ExtensionOrigin origin : e.getOrigins()) { + recommendedExtensionsByOrigin.computeIfAbsent(origin.getId(), k -> new ArrayList<>()) + .add(e.getArtifact().getKey()); + } + } + + final List notAvailableExtensions = new ArrayList<>(0); + final List recommendedExtensions = new ArrayList<>(previousExtensions.size()); + for (ArtifactKey key : previousExtensions.keySet()) { + final Extension e = recommendedExtensionMap.get(key); + if (e == null) { + notAvailableExtensions.add(e); + } else { + recommendedExtensions.add(e); + } + } + + if (!notAvailableExtensions.isEmpty()) { + final StringBuilder buf = new StringBuilder(); + buf.append( + "Could not find any information about the following extensions in the currently configured registries: "); + buf.append(notAvailableExtensions.get(0).getArtifact().getKey().toGacString()); + for (int i = 1; i < notAvailableExtensions.size(); ++i) { + buf.append(", ").append(notAvailableExtensions.get(i).getArtifact().getKey().toGacString()); + } + getLog().warn(buf.toString()); + return; + } + + final List recommendedOrigins; + try { + recommendedOrigins = getRecommendedOrigins(latestCatalog, recommendedExtensions); + } catch (QuarkusCommandException e) { + getLog().warn(e.getLocalizedMessage()); + return; + } + + final List previousBomImports = new ArrayList<>(); + for (Dependency d : project.getDependencyManagement().getDependencies()) { + if (PlatformArtifacts.isCatalogArtifactId(d.getArtifactId())) { + final ArtifactCoords platformBomCoords = new ArtifactCoords(d.getGroupId(), + PlatformArtifacts.ensureBomArtifactId(d.getArtifactId()), "pom", d.getVersion()); + if (d.getArtifactId().startsWith("quarkus-universe-bom-")) { + // in pre-2.x quarkus versions, the quarkus-bom descriptor would show up as a parent of the quarkus-universe-bom one + // even if it was not actually imported, so here we simply remove it, if it was found + previousBomImports.remove(new ArtifactCoords(platformBomCoords.getGroupId(), "quarkus-bom", "pom", + platformBomCoords.getVersion())); + } + previousBomImports.add(platformBomCoords); + } + } + + final List recommendedBomImports = new ArrayList<>(); + final Map nonPlatformUpdates = new LinkedHashMap<>(0); + + for (ExtensionCatalog origin : recommendedOrigins) { + if (origin.isPlatform()) { + if (!previousBomImports.remove(origin.getBom())) { + recommendedBomImports.add(origin.getBom()); + } + for (ArtifactKey extKey : recommendedExtensionsByOrigin.getOrDefault(origin.getId(), Collections.emptyList())) { + previousExtensions.remove(extKey); + } + } else { + for (ArtifactKey extKey : recommendedExtensionsByOrigin.getOrDefault(origin.getId(), Collections.emptyList())) { + final Extension recommendedExt = recommendedExtensionMap.get(extKey); + final String prevVersion = previousExtensions.remove(extKey); + if (prevVersion != null && !prevVersion.equals(recommendedExt.getArtifact().getVersion())) { + nonPlatformUpdates.put(recommendedExt.getArtifact(), prevVersion); + } + } + } + } + + if (recommendedBomImports.isEmpty() && nonPlatformUpdates.isEmpty()) { + log.info("The project is up-to-date"); + return; + } + + ArtifactCoords prevPluginCoords = null; + for (Plugin p : project.getBuildPlugins()) { + if (p.getArtifactId().equals("quarkus-maven-plugin")) { + prevPluginCoords = new ArtifactCoords(p.getGroupId(), p.getArtifactId(), p.getVersion()); + break; + } + } + + ExtensionCatalog core = null; + for (ExtensionOrigin o : recommendedOrigins) { + if (o.isPlatform() && o.getBom().getArtifactId().equals("quarkus-bom")) { + core = (ExtensionCatalog) o; + } + } + + ArtifactCoords recommendedPluginCoords = null; + if (core != null) { + final Map props = (Map) ((Map) core.getMetadata().getOrDefault("project", + Collections.emptyMap())).getOrDefault("properties", Collections.emptyMap()); + final String pluginGroupId = (String) props.get("maven-plugin-groupId"); + final String pluginArtifactId = (String) props.get("maven-plugin-artifactId"); + final String pluginVersion = (String) props.get("maven-plugin-version"); + if (pluginGroupId == null || pluginArtifactId == null || pluginVersion == null) { + log.warn("Failed to locate the recommended Quarkus Maven plugin coordinates"); + } else { + recommendedPluginCoords = new ArtifactCoords(pluginGroupId, pluginArtifactId, pluginVersion); + } + } + + final StringWriter buf = new StringWriter(); + try (BufferedWriter writer = new BufferedWriter(buf)) { + + writer.append("Currently recommended updates for the application include:"); + writer.newLine(); + writer.newLine(); + + if (!previousBomImports.isEmpty()) { + writer.append(" * BOM imports to be replaced:"); + writer.newLine(); + writer.newLine(); + for (ArtifactCoords bom : previousBomImports) { + logBomImport(writer, bom); + } + writer.newLine(); + } + + if (!recommendedBomImports.isEmpty()) { + writer.append(" * New recommended BOM imports:"); + writer.newLine(); + writer.newLine(); + for (ArtifactCoords bom : recommendedBomImports) { + logBomImport(writer, bom); + } + writer.newLine(); + } + + if (!nonPlatformUpdates.isEmpty()) { + writer.append(" * New recommended extension versions (not managed by the BOMs):"); + writer.newLine(); + writer.newLine(); + + for (ArtifactCoords coords : nonPlatformUpdates.keySet()) { + writer.append(" "); + writer.newLine(); + writer.append(" ").append(coords.getGroupId()).append(""); + writer.newLine(); + writer.append(" ").append(coords.getArtifactId()).append(""); + writer.newLine(); + writer.append(" ").append(coords.getVersion()).append(""); + writer.newLine(); + writer.append(" "); + writer.newLine(); + } + writer.newLine(); + } + + if (prevPluginCoords != null && recommendedPluginCoords != null + && !prevPluginCoords.equals(recommendedPluginCoords)) { + writer.append(" * Recommended Quarkus Maven plugin:"); + writer.newLine(); + writer.newLine(); + writer.append(" "); + writer.newLine(); + writer.append(" ").append(recommendedPluginCoords.getGroupId()).append(""); + writer.newLine(); + writer.append(" ").append(recommendedPluginCoords.getArtifactId()).append(""); + writer.newLine(); + writer.append(" ").append(recommendedPluginCoords.getVersion()).append(""); + writer.newLine(); + writer.append(" "); + writer.newLine(); + + } + } catch (IOException e) { + throw new MojoExecutionException("Failed to compose the update report", e); + } + + getLog().info(buf.toString()); + } + + private void logBomImport(BufferedWriter writer, ArtifactCoords bom) throws IOException { + writer.append(" "); + writer.newLine(); + writer.append(" ").append(bom.getGroupId()).append(""); + writer.newLine(); + writer.append(" ").append(bom.getArtifactId()).append(""); + writer.newLine(); + writer.append(" ").append(bom.getVersion()).append(""); + writer.newLine(); + writer.append(" pom"); + writer.newLine(); + writer.append(" import"); + writer.newLine(); + writer.append(" "); + writer.newLine(); + } + + private Map getDirectExtensionDependencies() throws MojoExecutionException { + final List modelDeps = project.getModel().getDependencies(); + final List requests = new ArrayList<>(modelDeps.size()); + for (Dependency d : modelDeps) { + if ("jar".equals(d.getType())) { + requests.add(new ArtifactRequest().setArtifact( + new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion())) + .setRepositories(repos)); + } + } + final List artifactResults; + try { + artifactResults = repoSystem.resolveArtifacts(repoSession, requests); + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Failed to resolve project dependencies", e); + } + final Map extensions = new HashMap<>(artifactResults.size()); + for (ArtifactResult ar : artifactResults) { + final Artifact a = ar.getArtifact(); + if (isExtension(a.getFile().toPath())) { + extensions.put(new ArtifactKey(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()), + a.getVersion()); + } + } + return extensions; + } + + private static boolean isExtension(Path p) throws MojoExecutionException { + if (!Files.exists(p)) { + throw new MojoExecutionException("Extension artifact " + p + " does not exist"); + } + if (Files.isDirectory(p)) { + return Files.exists(p.resolve(BootstrapConstants.DESCRIPTOR_PATH)); + } else { + try (FileSystem fs = FileSystems.newFileSystem(p, (ClassLoader) null)) { + return Files.exists(fs.getPath(BootstrapConstants.DESCRIPTOR_PATH)); + } catch (IOException e) { + throw new MojoExecutionException("Failed to read archive " + p, e); + } + } + } + + private static List getRecommendedOrigins(ExtensionCatalog extensionCatalog, List extensions) + throws QuarkusCommandException { + final List extOrigins = new ArrayList<>(extensions.size()); + for (Extension e : extensions) { + addOrigins(extOrigins, e); + } + final OriginSelector os = new OriginSelector(extOrigins); + os.calculateCompatibleCombinations(); + + final OriginCombination recommendedCombination = os.getRecommendedCombination(); + if (recommendedCombination == null) { + final StringBuilder buf = new StringBuilder(); + buf.append("Failed to determine a compatible Quarkus version for the requested extensions: "); + buf.append(extensions.get(0).getArtifact().getKey().toGacString()); + for (int i = 1; i < extensions.size(); ++i) { + buf.append(", ").append(extensions.get(i).getArtifact().getKey().toGacString()); + } + throw new QuarkusCommandException(buf.toString()); + } + return recommendedCombination.getUniqueSortedOrigins().stream().map(o -> o.getCatalog()).collect(Collectors.toList()); + } + + private static void addOrigins(final List extOrigins, Extension e) { + ExtensionOrigins.Builder eoBuilder = null; + for (ExtensionOrigin o : e.getOrigins()) { + if (!(o instanceof ExtensionCatalog)) { + continue; + } + final ExtensionCatalog c = (ExtensionCatalog) o; + final OriginPreference op = (OriginPreference) c.getMetadata().get("origin-preference"); + if (op == null) { + continue; + } + if (eoBuilder == null) { + eoBuilder = ExtensionOrigins.builder(e.getArtifact().getKey()); + } + eoBuilder.addOrigin(c, op); + } + if (eoBuilder != null) { + extOrigins.add(eoBuilder.build()); + } + } +} diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index e5e6a77cd8117..12df08a031baa 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -251,7 +251,7 @@ public void execute() throws MojoExecutionException { } } else if (containsAtLeastOneGradleFile) { throw new MojoExecutionException( - "You are trying to create maven project in a directory that contains only gradle build files."); + "You are trying to create a Maven project in a directory that contains only Gradle build files."); } } else if (BuildTool.GRADLE.equals(buildToolEnum) || BuildTool.GRADLE_KOTLIN_DSL.equals(buildToolEnum)) { if (containsAtLeastOneGradleFile) { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index d33429cbfeb49..d06ba6177c47b 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -189,7 +189,8 @@ private List computeRequiredExtensions(ExtensionCatalog catalog, return extensionsToAdd; } - private List getExtensionOrigins(ExtensionCatalog extensionCatalog, List extensionsToAdd) + private static List getExtensionOrigins(ExtensionCatalog extensionCatalog, + List extensionsToAdd) throws QuarkusCommandException { final List extOrigins = new ArrayList<>(extensionsToAdd.size()); @@ -227,7 +228,7 @@ private List getExtensionOrigins(ExtensionCatalog extensionCat return recommendedCombination.getUniqueSortedOrigins().stream().map(o -> o.getCatalog()).collect(Collectors.toList()); } - public void addOrigins(final List extOrigins, Extension e) { + private static void addOrigins(final List extOrigins, Extension e) { ExtensionOrigins.Builder eoBuilder = null; for (ExtensionOrigin o : e.getOrigins()) { if (!(o instanceof ExtensionCatalog)) { From a613262bc52810f1057df41c4a0a108c33ecd094 Mon Sep 17 00:00:00 2001 From: Geoffrey GREBERT Date: Mon, 6 Sep 2021 17:23:12 +0200 Subject: [PATCH 48/60] add mailer-deployment to bom (cherry picked from commit ab82e7ae303c15908f54616ad87f61da7457edc6) --- bom/application/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index bf872c1af0ad1..fdaef17a893fb 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1617,6 +1617,11 @@ quarkus-mailer ${project.version} + + io.quarkus + quarkus-mailer-deployment + ${project.version} + io.quarkus quarkus-mongodb-client From 4346744d091f502a3dadd7c04987eb208b3fb0e9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 6 Sep 2021 17:01:14 +0200 Subject: [PATCH 49/60] Fix build report archive name for Initial JDK 11 build (cherry picked from commit 68be98e02d00ab7b5f10bb83fcff11abaa7cf4c0) --- .github/workflows/ci-actions-incremental.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 130660b300b42..ab02df44870dd 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -178,7 +178,7 @@ jobs: uses: actions/upload-artifact@v2 if: ${{ failure() || cancelled() }} with: - name: "build-reports-JVM Tests - JDK ${{matrix.java.name}}" + name: "build-reports-Initial JDK 11 Build" path: | target/build-report.json retention-days: 2 From c9f3ee2aa0130b09593279a9b9591e4321293d39 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 6 Sep 2021 17:04:34 +0200 Subject: [PATCH 50/60] CI - Always include a file at the root to get the full hierarchy The upload-artifact action takes the common ancestor as the root path of the archive which will lead to issues when we have only one file. Always uploading a file from the root of the repository should fix this. (cherry picked from commit 370d94426dd560618465f4682c05f2c77a12b889) --- .github/workflows/ci-actions-incremental.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index ab02df44870dd..495652aacd6a4 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -181,6 +181,7 @@ jobs: name: "build-reports-Initial JDK 11 Build" path: | target/build-report.json + LICENSE.txt retention-days: 2 calculate-test-jobs: @@ -322,6 +323,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 - name: Upload gc.log uses: actions/upload-artifact@v2 @@ -388,6 +390,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 gradle-tests: @@ -447,6 +450,7 @@ jobs: **/build/test-results/test/TEST-*.xml **/target/*-reports/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 devtools-tests: @@ -506,6 +510,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 tcks-test: @@ -564,6 +569,7 @@ jobs: path: | **/target/*-reports/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 native-tests: @@ -643,4 +649,5 @@ jobs: **/target/*-reports/TEST-*.xml **/build/test-results/test/TEST-*.xml target/build-report.json + LICENSE.txt retention-days: 2 From 1fe7fbca62e949558748edd81b089c672574a348 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 25 Aug 2021 13:17:53 +0300 Subject: [PATCH 51/60] Add the ability for extensions to exclude GraalVM config from jars Resolves: #17482 (cherry picked from commit f36724b75ffcedc4c8c7cdb05a2c449c2cd58080) --- .../nativeimage/ExcludeConfigBuildItem.java | 41 +++++++++++++++++++ .../pkg/steps/NativeImageBuildStep.java | 20 ++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java new file mode 100644 index 0000000000000..fc6e88f8116f8 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java @@ -0,0 +1,41 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A build item that allows extension to configure the native-image compiler to effectively + * ignore certain configuration files in specific jars. + * + * The {@code jarFile} property specifies the name of the jar file or a regular expression that can be used to + * match multiple jar files. + * Matching jar files using regular expressions should be done as a last resort. + * + * The {@code resourceName} property specifies the name of the resource file or a regular expression that can be used to + * match multiple resource files. + * For the match to work, the resources need to be part of the matched jar file(s) (see {@code jarFile}). + * Matching resource files using regular expressions should be done as a last resort. + * + * See https://github.com/oracle/graal/pull/3179 for more details. + */ +public final class ExcludeConfigBuildItem extends MultiBuildItem { + + private final String jarFile; + private final String resourceName; + + public ExcludeConfigBuildItem(String jarFile, String resourceName) { + this.jarFile = jarFile; + this.resourceName = resourceName; + } + + public ExcludeConfigBuildItem(String jarFile) { + this(jarFile, "META-INF/native-image/native-image.properties"); + } + + public String getJarFile() { + return jarFile; + } + + public String getResourceName() { + return resourceName; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 2210a067887cb..e1dba7ed6f7e9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -26,6 +26,7 @@ import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.pkg.NativeConfig; @@ -81,7 +82,8 @@ ArtifactResultBuildItem nativeSourcesResult(NativeConfig nativeConfig, NativeImageSourceJarBuildItem nativeImageSourceJarBuildItem, OutputTargetBuildItem outputTargetBuildItem, PackageConfig packageConfig, - List nativeImageProperties) { + List nativeImageProperties, + List excludeConfigs) { Path outputDir; try { @@ -100,6 +102,7 @@ ArtifactResultBuildItem nativeSourcesResult(NativeConfig nativeConfig, .setNativeConfig(nativeConfig) .setOutputTargetBuildItem(outputTargetBuildItem) .setNativeImageProperties(nativeImageProperties) + .setExcludeConfigs(excludeConfigs) .setOutputDir(outputDir) .setRunnerJarName(runnerJar.getFileName().toString()) // the path to native-image is not known now, it is only known at the time the native-sources will be consumed @@ -130,6 +133,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa PackageConfig packageConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, List nativeImageProperties, + List excludeConfigs, List nativeImageSecurityProviders, Optional processInheritIODisabled) { if (nativeConfig.debug.enabled) { @@ -187,6 +191,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa .setNativeConfig(nativeConfig) .setOutputTargetBuildItem(outputTargetBuildItem) .setNativeImageProperties(nativeImageProperties) + .setExcludeConfigs(excludeConfigs) .setNativeImageSecurityProviders(nativeImageSecurityProviders) .setOutputDir(outputDir) .setRunnerJarName(runnerJarName) @@ -477,6 +482,7 @@ static class Builder { private NativeConfig nativeConfig; private OutputTargetBuildItem outputTargetBuildItem; private List nativeImageProperties; + private List excludeConfigs; private List nativeImageSecurityProviders; private Path outputDir; private String runnerJarName; @@ -500,6 +506,11 @@ public Builder setNativeImageProperties(List return this; } + public Builder setExcludeConfigs(List excludeConfigs) { + this.excludeConfigs = excludeConfigs; + return this; + } + public Builder setNativeImageSecurityProviders( List nativeImageSecurityProviders) { this.nativeImageSecurityProviders = nativeImageSecurityProviders; @@ -700,6 +711,13 @@ public NativeImageInvokerInfo build() { .collect(Collectors.joining(",")); nativeImageArgs.add("-H:AdditionalSecurityProviders=" + additionalSecurityProviders); } + + // --exclude-config options + for (ExcludeConfigBuildItem excludeConfig : excludeConfigs) { + nativeImageArgs.add("--exclude-config"); + nativeImageArgs.add(excludeConfig.getJarFile()); + nativeImageArgs.add(excludeConfig.getResourceName()); + } } nativeImageArgs.add(nativeImageName); From b78d149cc7d28350b9dfaa5df42ffde8426b4354 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 31 Aug 2021 17:23:08 +0100 Subject: [PATCH 52/60] Make sure the --exclude-config arguments precede the -jar arguments (cherry picked from commit 58e48e0594f43992a89209826da98348f41ac84e) --- .../builditem/nativeimage/ExcludeConfigBuildItem.java | 2 +- .../quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java index fc6e88f8116f8..8c2cf955c2639 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ExcludeConfigBuildItem.java @@ -28,7 +28,7 @@ public ExcludeConfigBuildItem(String jarFile, String resourceName) { } public ExcludeConfigBuildItem(String jarFile) { - this(jarFile, "META-INF/native-image/native-image.properties"); + this(jarFile, "/META-INF/native-image/native-image\\.properties"); } public String getJarFile() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index e1dba7ed6f7e9..2236dd642d26b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -595,8 +595,6 @@ public NativeImageInvokerInfo build() { "-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time nativeImageArgs.add("-H:+JNI"); nativeImageArgs.add("-H:+AllowFoldMethods"); - nativeImageArgs.add("-jar"); - nativeImageArgs.add(runnerJarName); if (nativeConfig.enableFallbackImages) { nativeImageArgs.add("-H:FallbackThreshold=5"); @@ -722,6 +720,10 @@ public NativeImageInvokerInfo build() { nativeImageArgs.add(nativeImageName); + //Make sure to have the -jar as last one, as it otherwise breaks "--exclude-config" + nativeImageArgs.add("-jar"); + nativeImageArgs.add(runnerJarName); + return new NativeImageInvokerInfo(nativeImageArgs); } From 08e028cf081e4f0c975a06c2cb81f780b2efd309 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 3 Sep 2021 14:59:43 +0100 Subject: [PATCH 53/60] Override the Oracle JDBC automatic native-image metadata and re-implement it explicitly (cherry picked from commit a7f87e266f8f68028fd7b0c4d51e1d4038ba59a2) --- .../deployment/OracleMetadataOverrides.java | 99 +++++++++++++++++++ .../oracle/deployment/OracleReflections.java | 19 ---- .../oracle/deployment/RegexMatchTest.java | 45 +++++++++ .../src/main/resources/application.properties | 5 +- 4 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java create mode 100644 extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java new file mode 100644 index 0000000000000..a2d917a0d07c3 --- /dev/null +++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java @@ -0,0 +1,99 @@ +package io.quarkus.jdbc.oracle.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; + +/** + * The Oracle JDBC driver includes a {@literal META-INF/native-image} which enables a set + * of global flags we need to control better, so to ensure such flags do not interfere + * with requirements of other libraries. + *

+ * For this reason, the {@literal META-INF/native-image/native-image.properties} resource + * is excluded explicitly; then we re-implement the equivalent directives using Quarkus + * build items. + *

+ * Other resources such as {@literal jni-config.json} and {@literal resource-config.json} + * are not excluded, so to ensure we match the recommendations from the Oracle JDBC + * engineering team and make it easier to pick up improvements in these when the driver + * gets updated. + *

+ * Regarding {@literal reflect-config.json}, we also prefer excluding it for the time + * being even though it's strictly not necessary: the reason is that the previous driver + * version had a build-breaking mistake; this was fixed in version 21.3 so should no + * longer be necessary, but the previous driver had been tested more widely and would + * require it, so this would facilitate the option to revert to the older version in + * case of problems. + */ +public final class OracleMetadataOverrides { + + static final String DRIVER_JAR_MATCH_REGEX = ".*com\\.oracle\\.database\\.jdbc.*"; + static final String NATIVE_IMAGE_RESOURCE_MATCH_REGEX = "/META-INF/native-image/(?:native-image\\.properties|reflect-config\\.json)"; + + /** + * Should match the contents of {@literal reflect-config.json} + * + * @param reflectiveClass builItem producer + */ + @BuildStep + void build(BuildProducer reflectiveClass) { + //This is to match the Oracle metadata (which we excluded so that we can apply fixes): + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.internal.ACProxyable")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.T4CDriverExtension")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.T2CDriverExtension")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.ShardingDriverExtension")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.Ano")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.AuthenticationService")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.DataIntegrityService")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.EncryptionService")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.SupervisorService")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.Message11")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, true, "oracle.sql.TypeDescriptor")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.sql.TypeDescriptorFactory")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.sql.AnyDataFactory")); + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, false, false, "com.sun.rowset.providers.RIOptimisticProvider")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.logging.annotations.Supports")); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.logging.annotations.Feature")); + } + + @BuildStep + void runtimeInitializeDriver(BuildProducer runtimeInitialized) { + //These re-implement all the "--initialize-at-build-time" arguments found in the native-image.properties : + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.OracleDriver")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleDriver")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("java.sql.DriverManager")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.LogicalConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OraclePooledConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OracleDataSource")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.datasource.impl.OracleDataSource")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OracleOCIConnectionPool")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleTimeoutThreadPerVM")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TimeoutInterruptHandler")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.HAManager")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.Clock")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer$LazyHolder")); + runtimeInitialized + .produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem( + "oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXADataSource")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.replay.OracleXADataSourceImpl")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.datasource.OracleXAConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXAConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXAHeteroConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.T4CXAConnection")); + runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.security.o5logon.O5Logon")); + } + + @BuildStep + ExcludeConfigBuildItem excludeOracleDirectives() { + // Excludes both native-image.properties and reflect-config.json, which are reimplemented above + return new ExcludeConfigBuildItem(DRIVER_JAR_MATCH_REGEX, NATIVE_IMAGE_RESOURCE_MATCH_REGEX); + } + +} diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java index 53854430001ff..118a24d4c1fdd 100644 --- a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java +++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java @@ -3,7 +3,6 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; /** * Registers the {@code oracle.jdbc.driver.OracleDriver} so that it can be loaded @@ -24,22 +23,4 @@ void build(BuildProducer reflectiveClass) { reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, driverName)); } - @BuildStep - void runtimeInitializeDriver(BuildProducer runtimeInitialized) { - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleDriver")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.SQLUtil$XMLFactory")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.NamedTypeAccessor$XMLFactory")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleTimeoutThreadPerVM")); - runtimeInitialized - .produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.T4CTTIoauthenticate")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer$LazyHolder")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.security.o5logon.O5Logon")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem( - "oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TimeoutInterruptHandler")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.Clock")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.NoSupportHAManager")); - runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.LogicalConnection")); - } } diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java b/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java new file mode 100644 index 0000000000000..5a66b4d51b26a --- /dev/null +++ b/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java @@ -0,0 +1,45 @@ +package io.quarkus.jdbc.oracle.deployment; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; + +import io.smallrye.common.constraint.Assert; + +/** + * The metadata override facility of GraalVM's native-image + * works with regular expressions. + * We're testing our expressions here to match against the + * constants the compiler is expecting (inferred by debugging + * the compiler) as it's otherwise a bit tricky to assert + * if they have been applied. + */ +public class RegexMatchTest { + + @Test + public void jarRegexIsMatching() { + final String EXAMPLE_CLASSPATH = "/home/sanne/sources/quarkus/integration-tests/jpa-oracle/target/quarkus-integration-test-jpa-oracle-999-SNAPSHOT-native-image-source-jar/lib/com.oracle.database.jdbc.ojdbc11-21.3.0.0.jar"; + final Pattern pattern = Pattern.compile(OracleMetadataOverrides.DRIVER_JAR_MATCH_REGEX); + final Matcher matcher = pattern.matcher(EXAMPLE_CLASSPATH); + Assert.assertTrue(matcher.find()); + } + + @Test + public void resourceRegexIsMatching() { + //We need to exclude both of these: + final String RES1 = "/META-INF/native-image/native-image.properties"; + final String RES2 = "/META-INF/native-image/reflect-config.json"; + final Pattern pattern = Pattern.compile(OracleMetadataOverrides.NATIVE_IMAGE_RESOURCE_MATCH_REGEX); + + Assert.assertTrue(pattern.matcher(RES1).find()); + Assert.assertTrue(pattern.matcher(RES2).find()); + + //While this one should NOT be ignored: + final String RES3 = "/META-INF/native-image/resource-config.json"; + final String RES4 = "/META-INF/native-image/jni-config.json"; + Assert.assertFalse(pattern.matcher(RES3).find()); + Assert.assertFalse(pattern.matcher(RES4).find()); + } + +} diff --git a/integration-tests/jpa-oracle/src/main/resources/application.properties b/integration-tests/jpa-oracle/src/main/resources/application.properties index 7c08376aa4a75..9d2e3ec2d86dc 100644 --- a/integration-tests/jpa-oracle/src/main/resources/application.properties +++ b/integration-tests/jpa-oracle/src/main/resources/application.properties @@ -4,4 +4,7 @@ quarkus.datasource.password=hibernate_orm_test quarkus.datasource.jdbc.url=${oracledb.url} quarkus.datasource.jdbc.max-size=1 quarkus.hibernate-orm.database.generation=drop-and-create -quarkus.datasource.jdbc.additional-jdbc-properties."oracle.jdbc.timezoneAsRegion"=false \ No newline at end of file +quarkus.datasource.jdbc.additional-jdbc-properties."oracle.jdbc.timezoneAsRegion"=false + +#Unfortunately, this is currently required when using the Oracle JDBC driver: +quarkus.native.additional-build-args=--allow-incomplete-classpath From 46064df64d3287487a31a67cd749255776bc4f36 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 6 Sep 2021 12:59:44 +0100 Subject: [PATCH 54/60] Introduce a build item to control flag --allow-incomplete-classpath of native-image (cherry picked from commit 5fab70f27a5380ec85db3da373930445634d75a4) --- ...IncompleteClasspathAggregateBuildItem.java | 21 ++++++++++ ...mageAllowIncompleteClasspathBuildItem.java | 32 ++++++++++++++++ .../pkg/steps/NativeImageBuildStep.java | 17 ++++++++- ...AllowIncompleteClasspathAggregateStep.java | 38 +++++++++++++++++++ .../deployment/OracleMetadataOverrides.java | 6 +++ .../src/main/resources/application.properties | 3 -- 6 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathAggregateBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAllowIncompleteClasspathAggregateStep.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathAggregateBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathAggregateBuildItem.java new file mode 100644 index 0000000000000..147d85716e390 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathAggregateBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Do not use directly: use {@see io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathBuildItem} + * instead. + */ +public final class NativeImageAllowIncompleteClasspathAggregateBuildItem extends SimpleBuildItem { + + private final boolean allow; + + public NativeImageAllowIncompleteClasspathAggregateBuildItem(boolean allow) { + this.allow = allow; + } + + public boolean isAllow() { + return allow; + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathBuildItem.java new file mode 100644 index 0000000000000..c0d6223607f54 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageAllowIncompleteClasspathBuildItem.java @@ -0,0 +1,32 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * If any build item of this type is produced, the native-image build tool + * will run with {@literal --allow-incomplete-classpath} set. + *

+ * This should be strongly discouraged as it makes diagnostics of any issue + * much more complex, and we have it seen affect error message of code + * seemingly unrelated to the code which is having the broken classpath. + *

+ * Use of this build item will trigger a warning during build. + * + * @Deprecated Please don't use it unless there is general consensus that we can't practically find a better solution. + */ +@Deprecated +public final class NativeImageAllowIncompleteClasspathBuildItem extends MultiBuildItem { + + private final String extensionName; + + /** + * @param extensionName Name the extension requiring this, so that it can be shamed appropriately during build. + */ + public NativeImageAllowIncompleteClasspathBuildItem(String extensionName) { + this.extensionName = extensionName; + } + + public String getExtensionName() { + return extensionName; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 2236dd642d26b..75ba060b0e62c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -27,6 +27,7 @@ import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathAggregateBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.pkg.NativeConfig; @@ -83,7 +84,8 @@ ArtifactResultBuildItem nativeSourcesResult(NativeConfig nativeConfig, OutputTargetBuildItem outputTargetBuildItem, PackageConfig packageConfig, List nativeImageProperties, - List excludeConfigs) { + List excludeConfigs, + NativeImageAllowIncompleteClasspathAggregateBuildItem incompleteClassPathAllowed) { Path outputDir; try { @@ -102,6 +104,7 @@ ArtifactResultBuildItem nativeSourcesResult(NativeConfig nativeConfig, .setNativeConfig(nativeConfig) .setOutputTargetBuildItem(outputTargetBuildItem) .setNativeImageProperties(nativeImageProperties) + .setBrokenClasspath(incompleteClassPathAllowed.isAllow()) .setExcludeConfigs(excludeConfigs) .setOutputDir(outputDir) .setRunnerJarName(runnerJar.getFileName().toString()) @@ -134,6 +137,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa CurateOutcomeBuildItem curateOutcomeBuildItem, List nativeImageProperties, List excludeConfigs, + NativeImageAllowIncompleteClasspathAggregateBuildItem incompleteClassPathAllowed, List nativeImageSecurityProviders, Optional processInheritIODisabled) { if (nativeConfig.debug.enabled) { @@ -192,6 +196,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa .setOutputTargetBuildItem(outputTargetBuildItem) .setNativeImageProperties(nativeImageProperties) .setExcludeConfigs(excludeConfigs) + .setBrokenClasspath(incompleteClassPathAllowed.isAllow()) .setNativeImageSecurityProviders(nativeImageSecurityProviders) .setOutputDir(outputDir) .setRunnerJarName(runnerJarName) @@ -490,6 +495,7 @@ static class Builder { private boolean isContainerBuild = false; private GraalVM.Version graalVMVersion = GraalVM.Version.UNVERSIONED; private String nativeImageName; + private boolean classpathIsBroken; public Builder setNativeConfig(NativeConfig nativeConfig) { this.nativeConfig = nativeConfig; @@ -506,6 +512,11 @@ public Builder setNativeImageProperties(List return this; } + public Builder setBrokenClasspath(boolean classpathIsBroken) { + this.classpathIsBroken = classpathIsBroken; + return this; + } + public Builder setExcludeConfigs(List excludeConfigs) { this.excludeConfigs = excludeConfigs; return this; @@ -604,6 +615,10 @@ public NativeImageInvokerInfo build() { nativeImageArgs.add("-H:FallbackThreshold=0"); } + if (classpathIsBroken) { + nativeImageArgs.add("--allow-incomplete-classpath"); + } + if (nativeConfig.reportErrorsAtRuntime) { nativeImageArgs.add("-H:+ReportUnsupportedElementsAtRuntime"); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAllowIncompleteClasspathAggregateStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAllowIncompleteClasspathAggregateStep.java new file mode 100644 index 0000000000000..d30507eaad93a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAllowIncompleteClasspathAggregateStep.java @@ -0,0 +1,38 @@ +package io.quarkus.deployment.steps; + +import java.util.List; +import java.util.stream.Collectors; + +import org.jboss.logging.Logger; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathAggregateBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathBuildItem; + +@SuppressWarnings("deprecation") +public final class NativeImageAllowIncompleteClasspathAggregateStep { + + private static final Logger log = Logger.getLogger(NativeImageAllowIncompleteClasspathAggregateStep.class); + + @BuildStep + NativeImageAllowIncompleteClasspathAggregateBuildItem aggregateIndividualItems( + List list) { + if (list.isEmpty()) { + return new NativeImageAllowIncompleteClasspathAggregateBuildItem(false); + } else { + final String extensionsRequiringBrokenClasspath = list.stream() + .map(NativeImageAllowIncompleteClasspathBuildItem::getExtensionName) + .collect(Collectors.joining(",")); + log.warn("The following extensions have required the '--allow-incomplete-classpath' flag to be set: {" + + extensionsRequiringBrokenClasspath + + "}. This is a global flag which might have unexpected effects on other extensions as well, and is a hint of the library " + + + "needing some additional refactoring to better support GraalVM native-image. In the case of 3rd party dependencies and/or" + + + " proprietary code there is not much we can do - please ask for support to your library vendor." + + " If you incur in any problem with other Quarkus extensions, please try reproducing the problem without these extensions first."); + return new NativeImageAllowIncompleteClasspathAggregateBuildItem(true); + } + } + +} diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java index a2d917a0d07c3..3970870f72b53 100644 --- a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java +++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java @@ -3,6 +3,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; @@ -96,4 +97,9 @@ ExcludeConfigBuildItem excludeOracleDirectives() { return new ExcludeConfigBuildItem(DRIVER_JAR_MATCH_REGEX, NATIVE_IMAGE_RESOURCE_MATCH_REGEX); } + @BuildStep + NativeImageAllowIncompleteClasspathBuildItem naughtyDriver() { + return new NativeImageAllowIncompleteClasspathBuildItem("quarkus-jdbc-oracle"); + } + } diff --git a/integration-tests/jpa-oracle/src/main/resources/application.properties b/integration-tests/jpa-oracle/src/main/resources/application.properties index 9d2e3ec2d86dc..0abd503fde3ec 100644 --- a/integration-tests/jpa-oracle/src/main/resources/application.properties +++ b/integration-tests/jpa-oracle/src/main/resources/application.properties @@ -5,6 +5,3 @@ quarkus.datasource.jdbc.url=${oracledb.url} quarkus.datasource.jdbc.max-size=1 quarkus.hibernate-orm.database.generation=drop-and-create quarkus.datasource.jdbc.additional-jdbc-properties."oracle.jdbc.timezoneAsRegion"=false - -#Unfortunately, this is currently required when using the Oracle JDBC driver: -quarkus.native.additional-build-args=--allow-incomplete-classpath From f64385b3b4beda68aeaf891765afbf1001f82745 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 2 Sep 2021 10:51:13 +1000 Subject: [PATCH 55/60] Clean up hang detection on failed start This can cause a false positive in the vertx-http-deployment tests where a continuous test fails to start, then spits out a hang detection warning a minute later. (cherry picked from commit ebddbc3bc1d22753c23d198ed7d8df0ffa6315ab) --- .../test/junit/QuarkusTestExtension.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 65ece2f7ed1ae..798048d7b9627 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -430,15 +430,7 @@ public void close() throws IOException { tm.close(); } finally { GroovyCacheCleaner.clearGroovyCache(); - if (hangTaskKey != null) { - hangTaskKey.cancel(true); - hangTaskKey = null; - } - var h = hangDetectionExecutor; - if (h != null) { - h.shutdownNow(); - hangDetectionExecutor = null; - } + shutdownHangDetection(); } } try { @@ -467,6 +459,18 @@ public void close() throws IOException { } } + private void shutdownHangDetection() { + if (hangTaskKey != null) { + hangTaskKey.cancel(true); + hangTaskKey = null; + } + var h = hangDetectionExecutor; + if (h != null) { + h.shutdownNow(); + hangDetectionExecutor = null; + } + } + private void populateDeepCloneField(StartupAction startupAction) { deepClone = new SerializationWithXStreamFallbackDeepClone(startupAction.getClassLoader()); } @@ -1325,6 +1329,7 @@ class FailedCleanup implements ExtensionContext.Store.CloseableResource { @Override public void close() { + resetHangTimeout(); firstException = null; failedBoot = false; ConfigProviderResolver.setInstance(null); From 5b881602440f085799b2ac3076e449d178209a15 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 7 Sep 2021 10:29:27 +1000 Subject: [PATCH 56/60] Fix continuous testing race Files to watch must be updated before the listeners are notified, otherwise the test may modify the config file before the file set has been updated, and the tests will not be run. (cherry picked from commit 18852338c81eb38cc9cc22ab98a36a771b10fddb) --- .../io/quarkus/deployment/dev/testing/JunitTestRunner.java | 7 +++++++ .../java/io/quarkus/deployment/dev/testing/TestRunner.java | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java index f3ebf58184b3c..1c6f5009c660c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java @@ -73,8 +73,10 @@ import io.quarkus.deployment.QuarkusClassWriter; import io.quarkus.deployment.dev.ClassScanResult; import io.quarkus.deployment.dev.DevModeContext; +import io.quarkus.deployment.dev.RuntimeUpdatesProcessor; import io.quarkus.deployment.util.IoUtil; import io.quarkus.dev.console.QuarkusConsole; +import io.quarkus.dev.testing.TestWatchedFiles; import io.quarkus.dev.testing.TracingHandler; /** @@ -379,6 +381,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e QuarkusConsole.INSTANCE.setOutputFilter(null); + //this has to happen before notifying the listeners + Map watched = TestWatchedFiles.retrieveWatchedFilePaths(); + if (watched != null) { + RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(watched, true); + } for (TestRunListener listener : listeners) { listener.runComplete(new TestRunResults(runId, classScanResult, classScanResult == null, start, System.currentTimeMillis(), toResultsMap(testState.getCurrentResults()))); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunner.java index 3c057a5422295..cdbe69cf0fdd4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunner.java @@ -24,8 +24,6 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.deployment.dev.ClassScanResult; import io.quarkus.deployment.dev.DevModeContext; -import io.quarkus.deployment.dev.RuntimeUpdatesProcessor; -import io.quarkus.dev.testing.TestWatchedFiles; import io.quarkus.runtime.configuration.HyphenateEnumConverter; public class TestRunner { @@ -254,10 +252,6 @@ public void noTests(TestRunResults results) { synchronized (this) { runner = null; } - Map watched = TestWatchedFiles.retrieveWatchedFilePaths(); - if (watched != null) { - RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(watched, true); - } if (disabled) { return; } From 84f18814acd19e6daabd7ad806cb5c8ea0b5be83 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 7 Sep 2021 09:13:26 +1000 Subject: [PATCH 57/60] SCI's can't add websocket endpoints Fixes #19781 (cherry picked from commit fb37943764b5f700b3e2a2127de23bc6adee271f) --- .../runtime/UndertowDeploymentRecorder.java | 6 ++- .../websockets/client/deployment/pom.xml | 4 ++ .../deployment/WebsocketClientProcessor.java | 20 +++++---- .../client/runtime/WebsocketCoreRecorder.java | 10 ++++- .../it/websocket/AddWebSocketHandler.java | 41 +++++++++++++++++++ .../io/quarkus/it/main/WebsocketTestCase.java | 25 +++++++++++ 6 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 integration-tests/main/src/main/java/io/quarkus/it/websocket/AddWebSocketHandler.java diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index aa6532a2f53f3..a04122a1ce38d 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -532,7 +532,11 @@ public void run() { } public void addServletContextAttribute(RuntimeValue deployment, String key, Object value1) { - deployment.getValue().addServletContextAttribute(key, value1); + if (value1 instanceof RuntimeValue) { + deployment.getValue().addServletContextAttribute(key, ((RuntimeValue) value1).getValue()); + } else { + deployment.getValue().addServletContextAttribute(key, value1); + } } public void addServletExtension(RuntimeValue deployment, ServletExtension extension) { diff --git a/extensions/websockets/client/deployment/pom.xml b/extensions/websockets/client/deployment/pom.xml index 079ab2aa0e009..0fa240617ff7e 100644 --- a/extensions/websockets/client/deployment/pom.xml +++ b/extensions/websockets/client/deployment/pom.xml @@ -21,6 +21,10 @@ io.quarkus quarkus-websockets-client + + io.quarkus + quarkus-undertow-spi + io.quarkus quarkus-junit5-internal diff --git a/extensions/websockets/client/deployment/src/main/java/io/quarkus/undertow/websockets/client/deployment/WebsocketClientProcessor.java b/extensions/websockets/client/deployment/src/main/java/io/quarkus/undertow/websockets/client/deployment/WebsocketClientProcessor.java index 44574779a8e86..c38f426a850e0 100644 --- a/extensions/websockets/client/deployment/src/main/java/io/quarkus/undertow/websockets/client/deployment/WebsocketClientProcessor.java +++ b/extensions/websockets/client/deployment/src/main/java/io/quarkus/undertow/websockets/client/deployment/WebsocketClientProcessor.java @@ -12,6 +12,7 @@ import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; +import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; import org.jboss.jandex.AnnotationInstance; @@ -34,10 +35,11 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.netty.deployment.EventLoopSupplierBuildItem; import io.quarkus.runtime.RuntimeValue; +import io.quarkus.undertow.deployment.ServletContextAttributeBuildItem; import io.quarkus.undertow.websockets.client.runtime.WebsocketCoreRecorder; import io.undertow.websockets.DefaultContainerConfigurator; +import io.undertow.websockets.ServerWebSocketContainer; import io.undertow.websockets.UndertowContainerProvider; import io.undertow.websockets.WebSocketDeploymentInfo; @@ -70,16 +72,16 @@ void scanForAnnotatedEndpoints(CombinedIndexBuildItem indexBuildItem, } @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) + @Record(ExecutionTime.STATIC_INIT) public ServerWebSocketContainerBuildItem deploy(final CombinedIndexBuildItem indexBuildItem, WebsocketCoreRecorder recorder, BuildProducer reflection, - EventLoopSupplierBuildItem eventLoopSupplierBuildItem, List annotatedEndpoints, BeanContainerBuildItem beanContainerBuildItem, WebsocketConfig websocketConfig, BuildProducer infoBuildItemBuildProducer, - Optional factoryBuildItem) throws Exception { + Optional factoryBuildItem, + BuildProducer servletContextAttributeBuildItemBuildProducer) throws Exception { final Set endpoints = new HashSet<>(); final Set config = new HashSet<>(); @@ -121,10 +123,14 @@ public ServerWebSocketContainerBuildItem deploy(final CombinedIndexBuildItem ind websocketConfig.maxFrameSize, websocketConfig.dispatchToWorker); infoBuildItemBuildProducer.produce(new WebSocketDeploymentInfoBuildItem(deploymentInfo)); + RuntimeValue serverContainer = recorder.createServerContainer( + beanContainerBuildItem.getValue(), + deploymentInfo, + factoryBuildItem.map(ServerWebSocketContainerFactoryBuildItem::getFactory).orElse(null)); + servletContextAttributeBuildItemBuildProducer + .produce(new ServletContextAttributeBuildItem(ServerContainer.class.getName(), serverContainer)); return new ServerWebSocketContainerBuildItem( - recorder.createServerContainer(beanContainerBuildItem.getValue(), eventLoopSupplierBuildItem.getMainSupplier(), - deploymentInfo, - factoryBuildItem.map(ServerWebSocketContainerFactoryBuildItem::getFactory).orElse(null))); + serverContainer); } public static void registerCodersForReflection(BuildProducer reflection, diff --git a/extensions/websockets/client/runtime/src/main/java/io/quarkus/undertow/websockets/client/runtime/WebsocketCoreRecorder.java b/extensions/websockets/client/runtime/src/main/java/io/quarkus/undertow/websockets/client/runtime/WebsocketCoreRecorder.java index 1c384fc46a3e5..1a420478fefb4 100644 --- a/extensions/websockets/client/runtime/src/main/java/io/quarkus/undertow/websockets/client/runtime/WebsocketCoreRecorder.java +++ b/extensions/websockets/client/runtime/src/main/java/io/quarkus/undertow/websockets/client/runtime/WebsocketCoreRecorder.java @@ -19,6 +19,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.core.runtime.VertxCoreRecorder; import io.undertow.websockets.ServerWebSocketContainer; import io.undertow.websockets.UndertowContainerProvider; import io.undertow.websockets.WebSocketDeploymentInfo; @@ -26,6 +27,7 @@ import io.undertow.websockets.util.ObjectFactory; import io.undertow.websockets.util.ObjectHandle; import io.undertow.websockets.util.ObjectIntrospecter; +import io.vertx.core.impl.VertxInternal; @Recorder public class WebsocketCoreRecorder { @@ -105,7 +107,6 @@ public RuntimeValue createDeploymentInfo(Set an } public RuntimeValue createServerContainer(BeanContainer beanContainer, - Supplier eventLoopGroupSupplier, RuntimeValue infoVal, ServerWebSocketContainerFactory serverContainerFactory) throws DeploymentException { WebSocketDeploymentInfo info = infoVal.getValue(); @@ -136,7 +137,12 @@ public void release() { } }; } - }, Thread.currentThread().getContextClassLoader(), eventLoopGroupSupplier, + }, Thread.currentThread().getContextClassLoader(), new Supplier() { + @Override + public EventLoopGroup get() { + return ((VertxInternal) VertxCoreRecorder.getVertx().get()).getEventLoopGroup(); + } + }, Collections.singletonList(new ContextSetupHandler() { @Override public Action create(Action action) { diff --git a/integration-tests/main/src/main/java/io/quarkus/it/websocket/AddWebSocketHandler.java b/integration-tests/main/src/main/java/io/quarkus/it/websocket/AddWebSocketHandler.java new file mode 100644 index 0000000000000..93dd2b6222880 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/websocket/AddWebSocketHandler.java @@ -0,0 +1,41 @@ +package io.quarkus.it.websocket; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.Session; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +@WebListener +public class AddWebSocketHandler implements ServletContextListener { + @Override + public void contextDestroyed(ServletContextEvent sce) { + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + try { + ((ServerContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName())) + .addEndpoint(ServerEndpointConfig.Builder.create(WebSocketEndpoint.class, "/added-dynamic").build()); + + } catch (DeploymentException | javax.websocket.DeploymentException e) { + throw new RuntimeException(e); + } + } + + @RegisterForReflection + public static class WebSocketEndpoint extends Endpoint { + + @Override + public void onOpen(Session session, EndpointConfig config) { + session.getAsyncRemote().sendText("DYNAMIC"); + } + + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java index 2fcf61f794671..e92668b8eb883 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java @@ -32,6 +32,8 @@ public class WebsocketTestCase { @TestHTTPResource("wsopen") URI openURI; + @TestHTTPResource("added-dynamic") + URI added; @Test public void websocketTest() throws Exception { @@ -57,6 +59,29 @@ public void onMessage(String s) { } } + @Test + public void addedWebSocketTest() throws Exception { + + LinkedBlockingDeque message = new LinkedBlockingDeque<>(); + Session session = ContainerProvider.getWebSocketContainer().connectToServer(new Endpoint() { + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String s) { + message.add(s); + } + }); + } + }, ClientEndpointConfig.Builder.create().build(), added); + + try { + Assertions.assertEquals("DYNAMIC", message.poll(20, TimeUnit.SECONDS)); + } finally { + session.close(); + } + } + @Test public void websocketServerEncodingAndDecodingTest() throws Exception { LinkedBlockingDeque message = new LinkedBlockingDeque<>(); From 6770abb81d077a2864ea3cee140dc7dc8086a388 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 7 Sep 2021 08:15:42 +1000 Subject: [PATCH 58/60] Fix log assertions This should not prevent Quarkus from shutting down (cherry picked from commit 31138b0896045aa53099422d0185a45ad06b6c2d) --- .../src/main/java/io/quarkus/test/QuarkusUnitTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java index 8d28ccae9e6d3..463cc04bb00c6 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java @@ -572,8 +572,9 @@ private Throwable unwrapException(Throwable cause) { public void afterAll(ExtensionContext extensionContext) throws Exception { actualTestClass = null; actualTestInstance = null; + List records = null; if (assertLogRecords != null) { - assertLogRecords.accept(inMemoryLogHandler.records); + records = new ArrayList<>(inMemoryLogHandler.records); } rootLogger.setHandlers(originalHandlers); inMemoryLogHandler.clearRecords(); @@ -606,9 +607,12 @@ public void afterAll(ExtensionContext extensionContext) throws Exception { if (afterAllCustomizer != null) { afterAllCustomizer.run(); } + ClearCache.clearAnnotationCache(); + GroovyCacheCleaner.clearGroovyCache(); + } + if (records != null) { + assertLogRecords.accept(records); } - ClearCache.clearAnnotationCache(); - GroovyCacheCleaner.clearGroovyCache(); } @Override From 065f05127928c9b303c8dbe57380b24e3d39924a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 7 Sep 2021 08:39:20 +0200 Subject: [PATCH 59/60] Force jvm-target 11 for BasicKotlinApplicationModuleDevModeTest --- .../resources/basic-kotlin-application-project/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle index 5ac19470fa842..ed525b0701145 100644 --- a/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle +++ b/integration-tests/gradle/src/main/resources/basic-kotlin-application-project/build.gradle @@ -26,8 +26,10 @@ compileJava { } // This is a fix as kotlin 1.5.30 does not support Java 17 yet -if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { - tasks.quarkusDev { +tasks.quarkusDev { + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { compilerArgs = ["-jvm-target", "16"] + } else { + compilerArgs = ["-jvm-target", "11"] } } From dc10e6305d3b3f11fe1dea2be0386a04c4562062 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 6 Sep 2021 15:51:15 +0100 Subject: [PATCH 60/60] Revert "Register @ConfigMappings directly into the Config builder" This reverts commit fd4b3028 (cherry picked from commit 3504a54217e64a482b88c1345106a01a86b9f005) --- .../configuration/ConfigMappingUtils.java | 150 ------------------ .../RunTimeConfigurationGenerator.java | 27 ---- .../steps/ConfigGenerationBuildStep.java | 15 +- .../runtime/configuration/ConfigUtils.java | 9 -- .../arc/deployment/ConfigBuildStep.java | 139 +++++++++++++++- .../arc/deployment}/ConfigClassBuildItem.java | 4 +- .../HibernateValidatorProcessor.java | 2 +- .../config/ConfigurableExceptionMapper.java | 10 +- .../it/smallrye/config/ExceptionConfig.java | 8 - 9 files changed, 139 insertions(+), 225 deletions(-) delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java rename {core/deployment/src/main/java/io/quarkus/deployment/builditem => extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment}/ConfigClassBuildItem.java (94%) delete mode 100644 integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java deleted file mode 100644 index 2cbe8dc5f7340..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.MAPPING; -import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.PROPERTIES; -import static java.util.Collections.emptySet; -import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; -import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; -import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; -import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.ClassType; -import org.jboss.jandex.DotName; -import org.jboss.jandex.MethodInfo; - -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.ConfigClassBuildItem; -import io.quarkus.deployment.builditem.GeneratedClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.ConfigMappingLoader; -import io.smallrye.config.ConfigMappingMetadata; - -public class ConfigMappingUtils { - public static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName()); - - private ConfigMappingUtils() { - } - - @BuildStep - public static void generateConfigClasses( - CombinedIndexBuildItem combinedIndex, - BuildProducer generatedClasses, - BuildProducer reflectiveClasses, - BuildProducer configClasses, - DotName configAnnotation) { - - for (AnnotationInstance instance : combinedIndex.getIndex().getAnnotations(configAnnotation)) { - AnnotationTarget target = instance.target(); - AnnotationValue annotationPrefix = instance.value("prefix"); - - if (target.kind().equals(FIELD)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - configClasses.produce( - toConfigClassBuildItem(instance, toClass(target.asField().type().name()), - annotationPrefix.asString())); - continue; - } - } - - if (target.kind().equals(METHOD_PARAMETER)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - ClassType classType = target.asMethodParameter().method().parameters() - .get(target.asMethodParameter().position()).asClassType(); - configClasses - .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); - continue; - } - } - - if (!target.kind().equals(CLASS)) { - continue; - } - - Class configClass = toClass(target.asClass().name()); - String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); - - List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); - Set generatedClassesNames = new HashSet<>(); - Set mappingsInfo = new HashSet<>(); - configMappingsMetadata.forEach(mappingMetadata -> { - generatedClasses.produce( - new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes())); - reflectiveClasses - .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build()); - reflectiveClasses - .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build()); - - for (Class parent : getHierarchy(mappingMetadata.getInterfaceType())) { - reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build()); - } - - generatedClassesNames.add(mappingMetadata.getClassName()); - - ClassInfo mappingInfo = combinedIndex.getIndex() - .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); - if (mappingInfo != null) { - mappingsInfo.add(mappingInfo); - } - }); - - // For implicit converters - for (ClassInfo classInfo : mappingsInfo) { - for (MethodInfo method : classInfo.methods()) { - reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString())); - } - } - - configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); - } - } - - private static Class toClass(DotName dotName) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - return classLoader.loadClass(dotName.toString()); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("The class (" + dotName.toString() + ") cannot be created during deployment.", e); - } - } - - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - String prefix) { - return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); - } - - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - Set generatedClasses, - String prefix) { - if (instance.name().equals(CONFIG_MAPPING_NAME)) { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); - } else { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); - } - } - - private static List> getHierarchy(Class mapping) { - List> interfaces = new ArrayList<>(); - for (Class i : mapping.getInterfaces()) { - interfaces.add(i); - interfaces.addAll(getHierarchy(i)); - } - return interfaces; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index 377bac70e18c2..ee5ebd04d064d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -70,7 +70,6 @@ import io.quarkus.runtime.configuration.RuntimeConfigSource; import io.quarkus.runtime.configuration.RuntimeConfigSourceFactory; import io.quarkus.runtime.configuration.RuntimeConfigSourceProvider; -import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.Converters; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.SmallRyeConfig; @@ -175,8 +174,6 @@ public final class RunTimeConfigurationGenerator { static final MethodDescriptor CU_ADD_SOURCE_FACTORY_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceFactoryProvider", void.class, SmallRyeConfigBuilder.class, ConfigSourceFactoryProvider.class); - static final MethodDescriptor CU_WITH_MAPPING = MethodDescriptor.ofMethod(ConfigUtils.class, "addMapping", - void.class, SmallRyeConfigBuilder.class, String.class, String.class); static final MethodDescriptor RCS_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSource.class, String.class); static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class); @@ -301,7 +298,6 @@ public static final class GenerateOperation implements AutoCloseable { final Set runtimeConfigSources; final Set runtimeConfigSourceProviders; final Set runtimeConfigSourceFactories; - final Set configMappings; /** * Regular converters organized by type. Each converter is stored in a separate field. Some are used * only at build time, some only at run time, and some at both times. @@ -339,7 +335,6 @@ public static final class GenerateOperation implements AutoCloseable { runtimeConfigSources = builder.getRuntimeConfigSources(); runtimeConfigSourceProviders = builder.getRuntimeConfigSourceProviders(); runtimeConfigSourceFactories = builder.getRuntimeConfigSourceFactories(); - configMappings = builder.getConfigMappings(); cc = ClassCreator.builder().classOutput(classOutput).className(CONFIG_CLASS_NAME).setFinal(true).build(); generateEmptyParsers(cc); // not instantiable @@ -428,11 +423,6 @@ public static final class GenerateOperation implements AutoCloseable { clinit.invokeStaticMethod(CU_ADD_SOURCE_FACTORY_PROVIDER, buildTimeBuilder, clinit.newInstance(RCSF_NEW, clinit.load(discoveredConfigSourceFactory))); } - // add mappings - for (ConfigClassWithPrefix configMapping : configMappings) { - clinit.invokeStaticMethod(CU_WITH_MAPPING, buildTimeBuilder, - clinit.load(configMapping.getKlass().getName()), clinit.load(configMapping.getPrefix())); - } clinitConfig = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, buildTimeBuilder), SmallRyeConfig.class); @@ -676,12 +666,6 @@ public void run() { readConfig.newInstance(RCSF_NEW, readConfig.load(discoveredConfigSourceFactory))); } - // add mappings - for (ConfigClassWithPrefix configMapping : configMappings) { - readConfig.invokeStaticMethod(CU_WITH_MAPPING, runTimeBuilder, - readConfig.load(configMapping.getKlass().getName()), readConfig.load(configMapping.getPrefix())); - } - ResultHandle bootstrapConfig = null; if (bootstrapConfigSetupNeeded()) { bootstrapConfig = readBootstrapConfig.invokeVirtualMethod(SRCB_BUILD, bootstrapBuilder); @@ -1703,8 +1687,6 @@ public static final class Builder { private Set runtimeConfigSourceProviders; private Set runtimeConfigSourceFactories; - private Set configMappings; - Builder() { } @@ -1816,15 +1798,6 @@ public Builder setRuntimeConfigSourceFactories(final Set runtimeConfigSo return this; } - Set getConfigMappings() { - return configMappings; - } - - public Builder setConfigMappings(final Set configMappings) { - this.configMappings = configMappings; - return this; - } - public GenerateOperation build() { return new GenerateOperation(this); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index 76baaac11caab..0eacb06801099 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -2,9 +2,7 @@ import static io.quarkus.deployment.steps.ConfigBuildSteps.SERVICES_PREFIX; import static io.quarkus.deployment.util.ServiceUtil.classNamesNamedIn; -import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; import java.io.IOException; import java.lang.reflect.Modifier; @@ -31,7 +29,6 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalBootstrapConfigSourceProviderBuildItem; import io.quarkus.deployment.builditem.AdditionalStaticInitConfigSourceProviderBuildItem; -import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -54,7 +51,6 @@ import io.quarkus.runtime.configuration.ConfigChangeRecorder; import io.quarkus.runtime.configuration.ConfigurationRuntimeConfig; import io.quarkus.runtime.configuration.RuntimeOverrideConfigSource; -import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.ConfigSourceFactory; import io.smallrye.config.PropertiesLocationConfigSourceFactory; @@ -92,8 +88,7 @@ void generateConfigClass( LiveReloadBuildItem liveReloadBuildItem, List additionalBootstrapConfigSourceProviders, List staticInitConfigSourceProviders, - List staticInitConfigSourceFactories, - List configClasses) + List staticInitConfigSourceFactories) throws IOException { if (liveReloadBuildItem.isLiveReload()) { @@ -135,7 +130,6 @@ void generateConfigClass( .setRuntimeConfigSources(discoveredConfigSources) .setRuntimeConfigSourceProviders(discoveredConfigSourceProviders) .setRuntimeConfigSourceFactories(discoveredConfigSourceFactories) - .setConfigMappings(getConfigClassesWithPrefix(configClasses)) .build() .run(); } @@ -249,11 +243,4 @@ private static Set staticSafeServices(Set services) { } return staticSafe; } - - private static Set getConfigClassesWithPrefix(List configClasses) { - return configClasses.stream() - .filter(ConfigClassBuildItem::isMapping) - .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) - .collect(toSet()); - } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index b362ff6e39826..c9d2c35e3088c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -191,15 +191,6 @@ public static void addSourceFactoryProvider(SmallRyeConfigBuilder builder, Confi builder.withSources(provider.getConfigSourceFactory(Thread.currentThread().getContextClassLoader())); } - public static void addMapping(SmallRyeConfigBuilder builder, String mappingClass, String prefix) { - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - try { - builder.withMapping(contextClassLoader.loadClass(mappingClass), prefix); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - /** * Checks if a property is present in the current Configuration. * diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index ee60809ab577f..a0d855f1ce38f 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -1,12 +1,17 @@ package io.quarkus.arc.deployment; +import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.MAPPING; +import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.PROPERTIES; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; -import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME; import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; +import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; import static org.jboss.jandex.AnnotationInstance.create; import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; +import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; +import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER; import static org.jboss.jandex.AnnotationValue.createStringValue; import java.util.ArrayList; @@ -14,6 +19,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; @@ -25,8 +31,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; @@ -46,15 +54,16 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.deployment.configuration.ConfigMappingUtils; import io.quarkus.deployment.configuration.definition.RootDefinition; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.annotations.ConfigPhase; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappingLoader; +import io.smallrye.config.ConfigMappingMetadata; import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.inject.ConfigProducer; @@ -66,6 +75,7 @@ public class ConfigBuildStep { private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); + private static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName()); private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName()); private static final DotName SET_NAME = DotName.createSimple(Set.class.getName()); private static final DotName LIST_NAME = DotName.createSimple(List.class.getName()); @@ -242,11 +252,79 @@ void generateConfigClasses( BuildProducer reflectiveClasses, BuildProducer configClasses) { - // TODO - Generation of Mapping interface classes can be done in core because they don't require CDI - ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, - CONFIG_MAPPING_NAME); - ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, - MP_CONFIG_PROPERTIES_NAME); + List mappingAnnotations = new ArrayList<>(); + mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(CONFIG_MAPPING_NAME)); + mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(MP_CONFIG_PROPERTIES_NAME)); + + for (AnnotationInstance instance : mappingAnnotations) { + AnnotationTarget target = instance.target(); + AnnotationValue annotationPrefix = instance.value("prefix"); + + if (target.kind().equals(FIELD)) { + if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { + configClasses.produce( + toConfigClassBuildItem(instance, toClass(target.asField().type().name()), + annotationPrefix.asString())); + continue; + } + } + + if (target.kind().equals(METHOD_PARAMETER)) { + if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { + ClassType classType = target.asMethodParameter().method().parameters() + .get(target.asMethodParameter().position()).asClassType(); + configClasses + .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); + continue; + } + } + + if (!target.kind().equals(CLASS)) { + continue; + } + + Class configClass = toClass(target.asClass().name()); + String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); + + List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); + Set generatedClassesNames = new HashSet<>(); + Set mappingsInfo = new HashSet<>(); + configMappingsMetadata.forEach(mappingMetadata -> { + generatedClasses.produce( + new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes())); + reflectiveClasses + .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build()); + reflectiveClasses + .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build()); + + for (Class parent : getHierarchy(mappingMetadata.getInterfaceType())) { + reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build()); + } + + generatedClassesNames.add(mappingMetadata.getClassName()); + + ClassInfo mappingInfo = combinedIndex.getIndex() + .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); + if (mappingInfo != null) { + mappingsInfo.add(mappingInfo); + } + }); + + // Search and register possible classes for implicit Converter methods + for (ClassInfo classInfo : mappingsInfo) { + for (MethodInfo method : classInfo.methods()) { + if (!isHandledByProducers(method.returnType()) && + mappingsInfo.stream() + .map(ClassInfo::name) + .noneMatch(name -> name.equals(method.returnType().name()))) { + reflectiveClasses + .produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString())); + } + } + } + + configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); + } } @BuildStep @@ -327,6 +405,12 @@ void registerConfigClasses( configClassWithPrefix -> Stream.of(configClassWithPrefix.getKlass(), configClassWithPrefix.getPrefix()) .collect(toList())); + recorder.registerConfigMappings( + configClasses.stream() + .filter(ConfigClassBuildItem::isMapping) + .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) + .collect(toSet())); + recorder.registerConfigProperties( configClasses.stream() .filter(ConfigClassBuildItem::isProperties) @@ -335,6 +419,45 @@ void registerConfigClasses( .collect(toSet())); } + private static Class toClass(DotName dotName) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + return classLoader.loadClass(dotName.toString()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("The class (" + dotName.toString() + ") cannot be created during deployment.", e); + } + } + + private static ConfigClassBuildItem toConfigClassBuildItem( + AnnotationInstance instance, + Class configClass, + String prefix) { + return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); + } + + private static ConfigClassBuildItem toConfigClassBuildItem( + AnnotationInstance instance, + Class configClass, + Set generatedClasses, + String prefix) { + if (instance.name().equals(CONFIG_MAPPING_NAME)) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); + } else if (instance.name().equals(MP_CONFIG_PROPERTIES_NAME)) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); + } else { + throw new IllegalArgumentException(); + } + } + + private static List> getHierarchy(Class mapping) { + List> interfaces = new ArrayList<>(); + for (Class i : mapping.getInterfaces()) { + interfaces.add(i); + interfaces.addAll(getHierarchy(i)); + } + return interfaces; + } + private String getPropertyName(String name, ClassInfo declaringClass) { StringBuilder builder = new StringBuilder(); if (declaringClass.enclosingClass() == null) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java similarity index 94% rename from core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java rename to extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java index fb3e80e17225e..1d011ddbe5824 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java @@ -1,4 +1,4 @@ -package io.quarkus.deployment.builditem; +package io.quarkus.arc.deployment; import java.util.Set; @@ -48,6 +48,6 @@ public boolean isProperties() { public enum Type { MAPPING, - PROPERTIES + PROPERTIES; } } diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index b854dcada8f55..61048fcf548c7 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -49,6 +49,7 @@ import io.quarkus.arc.deployment.AutoAddScopeBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +import io.quarkus.arc.deployment.ConfigClassBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.BeanInfo; @@ -62,7 +63,6 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java index 8a1e5132fd993..ff1f0f1125489 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ConfigurableExceptionMapper.java @@ -13,15 +13,13 @@ public class ConfigurableExceptionMapper @Inject @ConfigProperty(name = "exception.message") String message; - @Inject - ExceptionConfig exceptionConfig; + + public ConfigurableExceptionMapper() { + System.out.println("ConfigurableExceptionMapper.ConfigurableExceptionMapper"); + } @Override public Response toResponse(final ConfigurableExceptionMapperException exception) { - if (!message.equals(exceptionConfig.message())) { - return Response.serverError().build(); - } - return Response.ok().entity(message).build(); } diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java deleted file mode 100644 index 669464b2504c9..0000000000000 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ExceptionConfig.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.quarkus.it.smallrye.config; - -import io.smallrye.config.ConfigMapping; - -@ConfigMapping(prefix = "exception") -public interface ExceptionConfig { - String message(); -}