From 7c9674e769a2559f12b7d29334689869bb31b37c Mon Sep 17 00:00:00 2001 From: Kyrylo Shpak Date: Thu, 17 Jun 2021 15:28:34 +0200 Subject: [PATCH 01/24] Add info about Logger into OpenTracing guide (cherry picked from commit 84173858f53cedb80d662753b64c4c849a3ade24) --- docs/src/main/asciidoc/opentracing.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/opentracing.adoc b/docs/src/main/asciidoc/opentracing.adoc index 5257de4a29d21..080b90378e910 100644 --- a/docs/src/main/asciidoc/opentracing.adoc +++ b/docs/src/main/asciidoc/opentracing.adoc @@ -93,12 +93,14 @@ public class TracedResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - LOG.info("hello"); + LOG.info("hello"); // <1> return "hello"; } } ---- +<1> The log event carries OpenTracing information as well. In order to print OpenTracing information to the console output, the console log handler with the required OpenTracing event's keys needs to be defined in the `application.properties` file. + Notice that there is no tracing specific code included in the application. By default, requests sent to this endpoint will be traced without any code changes being required. It is also possible to enhance the tracing information. This can be achieved by https://github.com/smallrye/smallrye-opentracing/[SmallRye OpenTracing] an implementation of From 07bdcc2490b2f0e5bb74d7891f17658109d91fd8 Mon Sep 17 00:00:00 2001 From: Julien Ponge Date: Thu, 17 Jun 2021 15:32:05 +0200 Subject: [PATCH 02/24] Mutiny Vert.x bindings upgrade to 2.7.0 Also apply the split of the reactive converter utils that are going on a different release lifecycle. (cherry picked from commit 537e3543d70b5d8ab2c838477947ff61ad125f94) --- bom/application/pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 97e9f0e684331..4c1a7ce699430 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -52,7 +52,8 @@ 3.2.0 1.2.0 1.0.13 - 2.6.0 + 2.6.0 + 2.7.0 3.5.0 1.2.1 1.3.5 @@ -4831,17 +4832,17 @@ io.smallrye.reactive smallrye-reactive-converter-api - ${smallrye-reactive-utils.version} + ${smallrye-reactive-types-converter.version} io.smallrye.reactive smallrye-reactive-converter-mutiny - ${smallrye-reactive-utils.version} + ${smallrye-reactive-types-converter.version} io.smallrye.reactive smallrye-reactive-converter-rxjava2 - ${smallrye-reactive-utils.version} + ${smallrye-reactive-types-converter.version} From b1126c16a073285676371b7edfe650ecf63b13a5 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 21 Jun 2021 11:13:14 +0100 Subject: [PATCH 03/24] Update to Keycloak 14.0.0 (cherry picked from commit fe7aad55067c72b0a44297b3cfd10b6d635c0351) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4c1a7ce699430..8252ca01fcd72 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -188,7 +188,7 @@ 5.3.1 4.8 1.1.4.Final - 12.0.4 + 14.0.0 1.14.1 0.1.55 1.1.3 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index b0dfea9fec2ea..b5a28f282a508 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -88,7 +88,7 @@ - quay.io/keycloak/keycloak:12.0.3 + quay.io/keycloak/keycloak:14.0.0 4.0.13 From 6409a0144e06daa66f8fbfcc92954fe9e91c02ad Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 21 Jun 2021 10:55:34 -0300 Subject: [PATCH 04/24] Remove JDK 8 from the README badge (cherry picked from commit e4005ddb8ae10d70bcf2bbceb039e86e2226171a) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64dd9cf60be49..c58f46a2b377e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GitHub Actions Status]()](https://github.com/quarkusio/quarkus/actions?query=workflow%3A%22Quarkus+CI%22) [![License](https://img.shields.io/github/license/quarkusio/quarkus?style=for-the-badge&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0) [![Project Chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?style=for-the-badge&logo=zulip)](https://quarkusio.zulipchat.com/) -[![Supported JVM Versions](https://img.shields.io/badge/JVM-8--11--16-brightgreen.svg?style=for-the-badge&logo=Java)](https://github.com/quarkusio/quarkus/actions/runs/113853915/) +[![Supported JVM Versions](https://img.shields.io/badge/JVM-11--16-brightgreen.svg?style=for-the-badge&logo=Java)](https://github.com/quarkusio/quarkus/actions/runs/113853915/) # Quarkus - Supersonic Subatomic Java From c47c9b4eead48274750237c316d14949b87ba758 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 21 Jun 2021 18:42:25 +0200 Subject: [PATCH 05/24] Force locale for Hibernate ORM REST Data Panache tests Fixes #18017 (cherry picked from commit 0c0b51adcf1f12adfe97770fe32b021c45681953) --- .../src/main/resources/application.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-tests/hibernate-orm-rest-data-panache/src/main/resources/application.properties b/integration-tests/hibernate-orm-rest-data-panache/src/main/resources/application.properties index 324fe9cf65b78..2e90eeae20135 100644 --- a/integration-tests/hibernate-orm-rest-data-panache/src/main/resources/application.properties +++ b/integration-tests/hibernate-orm-rest-data-panache/src/main/resources/application.properties @@ -4,3 +4,6 @@ quarkus.datasource.jdbc.max-size=8 quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.log.sql=true quarkus.hibernate-orm.sql-load-script=import.sql + +quarkus.default-locale=en_US +quarkus.locales=en_US \ No newline at end of file From 1aeb763bfa08208d1b773ed2779a77de8c48a2f1 Mon Sep 17 00:00:00 2001 From: Mingyuan Wu Date: Mon, 21 Jun 2021 23:48:49 +0800 Subject: [PATCH 06/24] Set the compilation target to JDK 11 (cherry picked from commit c251dc6eab9d7f97a3d9047b3050643e4add3299) --- .../main/resources/create-extension-templates/parent-pom.xml | 4 ++-- .../resources/projects/capabilities-conflict/acme-ext/pom.xml | 4 ++-- .../resources/projects/capabilities-conflict/alt-ext/pom.xml | 4 ++-- .../src/test/resources/projects/capabilities-conflict/pom.xml | 4 ++-- .../src/test/resources/projects/modules-in-profiles/pom.xml | 4 ++-- .../src/test/resources/projects/setup-on-existing-pom/pom.xml | 4 ++-- .../projects/setup-with-custom-quarkus-version/pom.xml | 4 ++-- integration-tests/qute/pom.xml | 4 ++-- .../scala/src/test/resources/projects/classic-scala/pom.xml | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml b/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml index cfc99377dfd3b..6c562bbc23dd1 100644 --- a/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml +++ b/devtools/maven/src/main/resources/create-extension-templates/parent-pom.xml @@ -26,8 +26,8 @@ UTF-8 UTF-8 - 1.8 - 1.8 + 11 + 11 true diff --git a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/acme-ext/pom.xml b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/acme-ext/pom.xml index a8ee960692d9c..7f64b75e7a3dc 100644 --- a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/acme-ext/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/acme-ext/pom.xml @@ -18,8 +18,8 @@ UTF-8 UTF-8 - 1.8 - 1.8 + 11 + 11 true @project.version@ 3.8.1 diff --git a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/alt-ext/pom.xml b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/alt-ext/pom.xml index c72dbb182c799..eb7283171217b 100644 --- a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/alt-ext/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/alt-ext/pom.xml @@ -18,8 +18,8 @@ UTF-8 UTF-8 - 1.8 - 1.8 + 11 + 11 true @project.version@ 3.8.1 diff --git a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/pom.xml b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/pom.xml index 23a0fa0e0697a..c3def9fef230d 100644 --- a/integration-tests/maven/src/test/resources/projects/capabilities-conflict/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/capabilities-conflict/pom.xml @@ -14,8 +14,8 @@ @project.version@ 3.0.0-M5 UTF-8 - 1.8 - 1.8 + 11 + 11 acme-ext diff --git a/integration-tests/maven/src/test/resources/projects/modules-in-profiles/pom.xml b/integration-tests/maven/src/test/resources/projects/modules-in-profiles/pom.xml index 6dc917b4b7f78..f49fdb1db6d29 100644 --- a/integration-tests/maven/src/test/resources/projects/modules-in-profiles/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/modules-in-profiles/pom.xml @@ -14,8 +14,8 @@ @project.version@ 3.0.0-M5 UTF-8 - 1.8 - 1.8 + 11 + 11 runner diff --git a/integration-tests/maven/src/test/resources/projects/setup-on-existing-pom/pom.xml b/integration-tests/maven/src/test/resources/projects/setup-on-existing-pom/pom.xml index 0b1d69bba4f24..0c4e8818e1ff3 100644 --- a/integration-tests/maven/src/test/resources/projects/setup-on-existing-pom/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/setup-on-existing-pom/pom.xml @@ -12,8 +12,8 @@ maven-compiler-plugin 3.8.0 - 1.8 - 1.8 + 11 + 11 diff --git a/integration-tests/maven/src/test/resources/projects/setup-with-custom-quarkus-version/pom.xml b/integration-tests/maven/src/test/resources/projects/setup-with-custom-quarkus-version/pom.xml index 403d86236a775..4847e549e56c9 100644 --- a/integration-tests/maven/src/test/resources/projects/setup-with-custom-quarkus-version/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/setup-with-custom-quarkus-version/pom.xml @@ -11,8 +11,8 @@ maven-compiler-plugin 3.8.0 - 1.8 - 1.8 + 11 + 11 diff --git a/integration-tests/qute/pom.xml b/integration-tests/qute/pom.xml index adb4055911d9f..b748518b351a0 100644 --- a/integration-tests/qute/pom.xml +++ b/integration-tests/qute/pom.xml @@ -110,8 +110,8 @@ maven-compiler-plugin ${compiler-plugin.version} - 1.8 - 1.8 + 11 + 11 true diff --git a/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml b/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml index d13c1561e686f..707ad3601dedc 100644 --- a/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml +++ b/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml @@ -102,7 +102,7 @@ -deprecation -feature -explaintypes - -target:jvm-1.8 + -target:jvm-11 -Ypartial-unification From 051cbc76c47d750584bce1c6fb367144e7aec110 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 2 Jun 2021 09:51:05 +0200 Subject: [PATCH 07/24] CLI: Change default short option to display version from "-V" to "-v" The current short option to display the version is "-V" (uppercase): `quarkus -V`. This is set by Picocli, but can be changed using the `picocli.version.name.0` property. The problem with `-V` is that does not match with the other short options to display version in the extensions and other commands which is "-v". Also, in other command tools like `mvn`, the option is `-v`. I did check that the `-v` is not conflicting with other existing options. (cherry picked from commit bca4177e7bbad63a7dcfec61ed2cf2ee0c74e452) --- devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java | 2 ++ devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java index 97b796c629450..177c922d6ed56 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java @@ -29,6 +29,8 @@ public class QuarkusCli implements QuarkusApplication, Callable { static { System.setProperty("picocli.endofoptions.description", "End of command line options."); + // Change default short option to display version from "-V" to "-v": + System.setProperty("picocli.version.name.0", "-v"); } @Inject diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java index 9377483e663ad..55c3f05315b5c 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java @@ -9,7 +9,7 @@ public void testCommandVersion() throws Exception { CliDriver.Result result = CliDriver.execute("version"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute("-V"); + CliDriver.Result result2 = CliDriver.execute("-v"); Assertions.assertEquals(result.stdout, result2.stdout, "Version output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); From 150e36c99c0fb31664b4b40b888685f2b22ff348 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Wed, 2 Jun 2021 22:44:23 +0200 Subject: [PATCH 08/24] Export maven.repo.local for gradle tests (cherry picked from commit 33bab86289c719758668f7a7908badc31a93e8ab) --- .../io/quarkus/cli/CliProjectGradleTest.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java index ba48b79778f18..8871b9a3dc317 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java @@ -3,6 +3,7 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -54,16 +55,36 @@ void startGradleDaemon(boolean useWrapper) throws Exception { gradle = ExecuteUtil.findExecutableFile("gradle"); } - CliDriver.Result result = CliDriver.executeArbitraryCommand(gradle.getAbsolutePath(), "--daemon", "-q", - "--project-dir=" + project.toAbsolutePath()); + List args = new ArrayList<>(); + args.add(gradle.getAbsolutePath()); + args.add("--daemon"); + args.add("-q"); + args.add("--project-dir=" + project.toAbsolutePath()); + + String localMavenRepo = System.getProperty("maven.repo.local", null); + if (localMavenRepo != null) { + args.add("-Dmaven.repo.local=" + localMavenRepo); + } + + CliDriver.Result result = CliDriver.executeArbitraryCommand(args.toArray(new String[0])); Assertions.assertEquals(0, result.exitCode, "Gradle daemon should start properly"); } @AfterEach void stopGradleDaemon() throws Exception { if (gradle != null) { - CliDriver.Result result = CliDriver.executeArbitraryCommand(gradle.getAbsolutePath(), "--stop", - "--project-dir=" + project.toAbsolutePath()); + + List args = new ArrayList<>(); + args.add(gradle.getAbsolutePath()); + args.add("--stop"); + args.add("--project-dir=" + project.toAbsolutePath()); + + String localMavenRepo = System.getProperty("maven.repo.local", null); + if (localMavenRepo != null) { + args.add("-Dmaven.repo.local=" + localMavenRepo); + } + + CliDriver.Result result = CliDriver.executeArbitraryCommand(args.toArray(new String[0])); Assertions.assertEquals(0, result.exitCode, "Gradle daemon should stop properly"); } } From 866188fde4a4fefa622222e66b6e24cbd22d3922 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Tue, 1 Jun 2021 12:03:29 -0400 Subject: [PATCH 09/24] Use main.java for JBang projects (cherry picked from commit 2f8cc9579bde4fd7a0b46788955f1cc43c239c4b) --- .../src/test/java/io/quarkus/cli/CliProjectJBangTest.java | 2 +- .../quarkus-jbang/code/jbang-picocli-code/codestart.yml | 2 +- .../quarkus-jbang/code/jbang-resteasy-code/codestart.yml | 2 +- .../jbang/QuarkusJBangCodestartGenerationTest.java | 4 ++-- .../generateDefaultProject/dir-tree.snapshot | 2 +- .../{src_GreetingResource.java => src_main.java} | 2 +- .../generatePicocliProject/dir-tree.snapshot | 2 +- .../{src_GreetingCommand.java => src_main.java} | 2 +- .../quarkus/devtools/commands/CreateJBangProjectTest.java | 6 +++--- 9 files changed, 12 insertions(+), 12 deletions(-) rename independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/{src_GreetingResource.java => src_main.java} (93%) rename independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/{src_GreetingCommand.java => src_main.java} (94%) diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java index 683e40562fb0b..835b51b1b7d16 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java @@ -106,6 +106,6 @@ Path valdiateJBangSourcePackage(Path project, String name) { Assertions.assertTrue(packagePath.toFile().isDirectory(), "Package directory should be a directory: " + packagePath.toAbsolutePath().toString()); - return packagePath.resolve("GreetingResource.java"); + return packagePath.resolve("main.java"); } } diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-picocli-code/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-picocli-code/codestart.yml index 166616c962f27..75fb2f98f08d0 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-picocli-code/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-picocli-code/codestart.yml @@ -5,7 +5,7 @@ language: base: data: command: - class-name: GreetingCommand + class-name: main name: Greeting dependencies: - io.quarkus:quarkus-picocli diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/codestart.yml index dd495fc6e7c3d..43a04c5ae5995 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/codestart.yml @@ -5,7 +5,7 @@ language: base: data: resource: - class-name: GreetingResource + class-name: main path: "/hello-resteasy" response: "Hello RESTEasy" dependencies: diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java index 24b0f63139bab..ee3ec9669f8aa 100644 --- a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/codestarts/jbang/QuarkusJBangCodestartGenerationTest.java @@ -33,7 +33,7 @@ void generateDefaultProject(TestInfo testInfo) throws Throwable { final Path projectDir = testDirPath.resolve("default"); getCatalog().createProject(input).generate(projectDir); assertThatDirectoryTreeMatchSnapshots(testInfo, projectDir); - assertThatMatchSnapshot(testInfo, projectDir, "src/GreetingResource.java"); + assertThatMatchSnapshot(testInfo, projectDir, "src/main.java"); } @Test @@ -47,7 +47,7 @@ void generatePicocliProject(TestInfo testInfo) throws Throwable { final Path projectDir = testDirPath.resolve("picocli"); getCatalog().createProject(input).generate(projectDir); assertThatDirectoryTreeMatchSnapshots(testInfo, projectDir); - assertThatMatchSnapshot(testInfo, projectDir, "src/GreetingCommand.java"); + assertThatMatchSnapshot(testInfo, projectDir, "src/main.java"); } private QuarkusJBangCodestartCatalog getCatalog() throws IOException { diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/dir-tree.snapshot b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/dir-tree.snapshot index 55896a02385e8..6e585838bc453 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/dir-tree.snapshot +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/dir-tree.snapshot @@ -4,4 +4,4 @@ README.md jbang jbang.cmd src/ -src/GreetingResource.java \ No newline at end of file +src/main.java \ No newline at end of file diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_GreetingResource.java b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_main.java similarity index 93% rename from independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_GreetingResource.java rename to independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_main.java index ac7ffd9f86f91..ca73154842dfd 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_GreetingResource.java +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generateDefaultProject/src_main.java @@ -11,7 +11,7 @@ @Path("/hello-resteasy") @ApplicationScoped -public class GreetingResource { +public class main { @GET public String sayHello() { diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/dir-tree.snapshot b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/dir-tree.snapshot index 77e37b07d9164..6e585838bc453 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/dir-tree.snapshot +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/dir-tree.snapshot @@ -4,4 +4,4 @@ README.md jbang jbang.cmd src/ -src/GreetingCommand.java \ No newline at end of file +src/main.java \ No newline at end of file diff --git a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_GreetingCommand.java b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_main.java similarity index 94% rename from independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_GreetingCommand.java rename to independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_main.java index c2f215e4d510f..04372dcffdd86 100644 --- a/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_GreetingCommand.java +++ b/independent-projects/tools/devtools-testing/src/test/resources/__snapshots__/QuarkusJBangCodestartGenerationTest/generatePicocliProject/src_main.java @@ -9,7 +9,7 @@ import picocli.CommandLine.Parameters; @Command(name = "Greeting", mixinStandardHelpOptions = true) -public class GreetingCommand { +public class main { @Parameters(paramLabel = "", defaultValue = "picocli", description = "Your name.") diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java index 2ad00735d8211..009b594a48551 100644 --- a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java @@ -29,7 +29,7 @@ public void createRESTEasy() throws Exception { assertThat(projectDir.resolve("jbang")).exists(); - assertThat(projectDir.resolve("src/GreetingResource.java")) + assertThat(projectDir.resolve("src/main.java")) .exists() .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")); @@ -45,7 +45,7 @@ public void createRESTEasyWithNoJBangWrapper() throws Exception { assertThat(projectDir.resolve("jbang")).doesNotExist(); - assertThat(projectDir.resolve("src/GreetingResource.java")) + assertThat(projectDir.resolve("src/main.java")) .exists() .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")); @@ -65,7 +65,7 @@ public void createRESTEasyWithExtensions() throws Exception { assertThat(projectDir.resolve("jbang")).exists(); - assertThat(projectDir.resolve("src/GreetingResource.java")) + assertThat(projectDir.resolve("src/main.java")) .exists() .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")) From b3a502f6c5e5247b80eba76ed056bab004719610 Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Mon, 7 Jun 2021 22:24:52 +0200 Subject: [PATCH 10/24] Export maven.repo.local property for Jbang cli tests (cherry picked from commit 42b6e853ec7d4c8ea80ad6e12cd36cc9fcca3906) --- devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java index 2439678fd27c3..1c79a441af8af 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java @@ -72,6 +72,7 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru args.add("--native"); } args.add("build"); + setJbangProperties(args, false); args.addAll(params); args.add(getMainPath()); return prependExecutable(args); From a3af9a071d6cc73a5ce3f7b6786de9f0edc8336e Mon Sep 17 00:00:00 2001 From: Guillaume Le Floch Date: Thu, 17 Jun 2021 16:57:38 +0200 Subject: [PATCH 11/24] Export JBANG_REPO env variable for release test ci job (cherry picked from commit 1b0c2ff3ee7803f146d9c04c903dc0ccc2569bc4) --- .github/workflows/release-build.yml | 1 + .../src/main/java/io/quarkus/cli/build/JBangRunner.java | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index b5e79728806ce..655410d0c62a2 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -12,6 +12,7 @@ jobs: if: github.repository == 'quarkusio/quarkus' env: MAVEN_OPTS: -Xmx2048m -XX:MaxMetaspaceSize=1000m + JBANG_REPO: $HOME/release/repository steps: - uses: actions/checkout@v2 with: diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java index 1c79a441af8af..2cea8ce57d985 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java @@ -14,7 +14,6 @@ import io.quarkus.cli.common.RegistryClientMixin; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.registry.config.RegistriesConfigLocator; public class JBangRunner implements BuildSystemRunner { static final String[] windowsWrapper = { "jbang.cmd", "jbang.ps1" }; @@ -72,7 +71,6 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru args.add("--native"); } args.add("build"); - setJbangProperties(args, false); args.addAll(params); args.add(getMainPath()); return prependExecutable(args); @@ -126,10 +124,4 @@ String getMainPath() { } return mainPath; } - - void setJbangProperties(ArrayDeque args, boolean batchMode) { - ExecuteUtil.propagatePropertyIfSet("maven.repo.local", args); - ExecuteUtil.propagatePropertyIfSet(RegistriesConfigLocator.CONFIG_FILE_PATH_PROPERTY, args); - ExecuteUtil.propagatePropertyIfSet("io.quarkus.maven.secondary-local-repo", args); - } } From a5b4840e9a16723feb3393ef0ece21211a785568 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Fri, 18 Jun 2021 16:25:24 -0400 Subject: [PATCH 12/24] CLI: more -D support; don't set user.dir (cherry picked from commit 9d105f6c0da5ac6da73903b5150d5669452bc3d2) --- .../src/main/java/io/quarkus/cli/Build.java | 9 +- .../main/java/io/quarkus/cli/CreateApp.java | 10 +- .../main/java/io/quarkus/cli/CreateCli.java | 10 +- .../cli/src/main/java/io/quarkus/cli/Dev.java | 12 ++- .../quarkus/cli/build/BaseBuildCommand.java | 5 +- .../quarkus/cli/build/BuildSystemRunner.java | 7 +- .../io/quarkus/cli/build/GradleRunner.java | 11 ++- .../io/quarkus/cli/build/JBangRunner.java | 7 +- .../io/quarkus/cli/build/MavenRunner.java | 11 ++- .../io/quarkus/cli/common/BuildOptions.java | 10 -- .../io/quarkus/cli/common/DevOptions.java | 23 ++--- .../quarkus/cli/common/OutputOptionMixin.java | 17 ++++ .../quarkus/cli/common/PropertiesOptions.java | 20 ++++ .../cli/create/CreateProjectMixin.java | 19 +++- .../test/java/io/quarkus/cli/CliDriver.java | 91 ++++++++++--------- .../test/java/io/quarkus/cli/CliHelpTest.java | 53 +++++++---- .../io/quarkus/cli/CliNonProjectTest.java | 25 ++--- .../io/quarkus/cli/CliProjectGradleTest.java | 67 ++++++-------- .../io/quarkus/cli/CliProjectJBangTest.java | 34 ++----- .../io/quarkus/cli/CliProjectMavenTest.java | 62 +++++-------- .../java/io/quarkus/cli/CliVersionTest.java | 11 ++- 21 files changed, 286 insertions(+), 228 deletions(-) create mode 100644 devtools/cli/src/main/java/io/quarkus/cli/common/PropertiesOptions.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Build.java b/devtools/cli/src/main/java/io/quarkus/cli/Build.java index 800e7f43b766a..023296434d50b 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/Build.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/Build.java @@ -9,6 +9,7 @@ import io.quarkus.cli.build.BaseBuildCommand; import io.quarkus.cli.build.BuildSystemRunner; import io.quarkus.cli.common.BuildOptions; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.project.BuildTool; import picocli.CommandLine; @@ -23,6 +24,9 @@ public class Build extends BaseBuildCommand implements Callable { @CommandLine.ArgGroup(order = 1, exclusive = false, validate = false, heading = "%nBuild options%n") BuildOptions buildOptions = new BuildOptions(); + @CommandLine.ArgGroup(order = 2, exclusive = false, validate = false) + PropertiesOptions propertiesOptions = new PropertiesOptions(); + @Parameters(description = "Additional parameters passed to the build system") List params = new ArrayList<>(); @@ -33,7 +37,8 @@ public Integer call() { output.throwIfUnmatchedArguments(spec.commandLine()); BuildSystemRunner runner = getRunner(); - BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareBuild(buildOptions, runMode, params); + BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareBuild(buildOptions, propertiesOptions, runMode, + params); if (runMode.isDryRun()) { dryRunBuild(spec.commandLine().getHelp(), runner.getBuildTool(), commandArgs); @@ -68,7 +73,7 @@ public String toString() { + ", buildNative=" + buildOptions.buildNative + ", offline=" + buildOptions.offline + ", runTests=" + buildOptions.runTests - + ", properties=" + buildOptions.properties + + ", properties=" + propertiesOptions.properties + ", output=" + output + ", params=" + params + "]"; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index 32ba4a0216027..d2fe8eca2aa45 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.create.BaseCreateCommand; import io.quarkus.cli.create.CodeGenerationGroup; import io.quarkus.cli.create.CreateProjectMixin; @@ -41,6 +42,9 @@ public class CreateApp extends BaseCreateCommand { @CommandLine.ArgGroup(order = 5, exclusive = false, heading = "%nCode Generation%n") CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); + @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + PropertiesOptions propertiesOptions = new PropertiesOptions(); + @CommandLine.Parameters(arity = "0..1", paramLabel = "EXTENSION", description = "Extension(s) to add to the project.") Set extensions = new HashSet<>(); @@ -51,6 +55,7 @@ public Integer call() throws Exception { output.throwIfUnmatchedArguments(spec.commandLine()); createProject.setSingleProjectGAV(gav); + createProject.setTestOutputDirectory(output.getTestDirectory()); createProject.projectRoot(); // verify project directories early BuildTool buildTool = targetBuildTool.getBuildTool(BuildTool.MAVEN); @@ -58,7 +63,9 @@ public Integer call() throws Exception { createProject.setSourceTypeExtensions(extensions, sourceType); createProject.setCodegenOptions(codeGeneration); - QuarkusCommandInvocation invocation = createProject.build(buildTool, targetQuarkusVersion, output); + QuarkusCommandInvocation invocation = createProject.build(buildTool, targetQuarkusVersion, + output, propertiesOptions.properties); + boolean success = true; if (runMode.isDryRun()) { @@ -85,6 +92,7 @@ public String toString() { + ", codeGeneration=" + codeGeneration + ", extensions=" + extensions + ", project=" + createProject + + ", properties=" + propertiesOptions.properties + '}'; } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index 92b3b054c7388..5b24e8217a99f 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.create.BaseCreateCommand; import io.quarkus.cli.create.CodeGenerationGroup; import io.quarkus.cli.create.CreateProjectMixin; @@ -41,6 +42,9 @@ public class CreateCli extends BaseCreateCommand { @CommandLine.ArgGroup(order = 5, exclusive = false, heading = "%nCode Generation%n") CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); + @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + PropertiesOptions propertiesOptions = new PropertiesOptions(); + @CommandLine.Parameters(arity = "0..1", paramLabel = "EXTENSION", description = "Extensions to add to project.") Set extensions = new HashSet<>(); @@ -51,6 +55,7 @@ public Integer call() throws Exception { output.throwIfUnmatchedArguments(spec.commandLine()); createProject.setSingleProjectGAV(gav); + createProject.setTestOutputDirectory(output.getTestDirectory()); createProject.projectRoot(); // verify project directories early BuildTool buildTool = targetBuildTool.getBuildTool(BuildTool.MAVEN); @@ -58,7 +63,9 @@ public Integer call() throws Exception { createProject.setSourceTypeExtensions(extensions, sourceType); createProject.setCodegenOptions(codeGeneration); - QuarkusCommandInvocation invocation = createProject.build(buildTool, targetQuarkusVersion, output); + QuarkusCommandInvocation invocation = createProject.build(buildTool, targetQuarkusVersion, + output, propertiesOptions.properties); + boolean success = true; // TODO: default extension (picocli) @@ -88,6 +95,7 @@ public String toString() { + ", codeGeneration=" + codeGeneration + ", extensions=" + extensions + ", project=" + createProject + + ", properties=" + propertiesOptions.properties + '}'; } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Dev.java b/devtools/cli/src/main/java/io/quarkus/cli/Dev.java index 43b4951d49d2e..df0cb6f543eed 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/Dev.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/Dev.java @@ -10,6 +10,7 @@ import io.quarkus.cli.build.BuildSystemRunner; import io.quarkus.cli.common.DebugOptions; import io.quarkus.cli.common.DevOptions; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.devtools.project.BuildTool; import picocli.CommandLine; import picocli.CommandLine.Parameters; @@ -20,7 +21,10 @@ public class Dev extends BaseBuildCommand implements Callable { @CommandLine.ArgGroup(order = 1, exclusive = false, heading = "%nDev Mode options%n") DevOptions devOptions = new DevOptions(); - @CommandLine.ArgGroup(order = 2, exclusive = false, validate = true, heading = "%nDebug options%n") + @CommandLine.ArgGroup(order = 2, exclusive = false, validate = false) + PropertiesOptions propertiesOptions = new PropertiesOptions(); + + @CommandLine.ArgGroup(order = 3, exclusive = false, validate = true, heading = "%nDebug options%n") DebugOptions debugOptions = new DebugOptions(); @Parameters(description = "Parameters passed to the application.") @@ -33,9 +37,10 @@ public Integer call() { output.throwIfUnmatchedArguments(spec.commandLine()); BuildSystemRunner runner = getRunner(); - BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareDevMode(devOptions, debugOptions, params); + BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareDevMode(devOptions, propertiesOptions, debugOptions, + params); - if (devOptions.dryRun) { + if (devOptions.isDryRun()) { dryRunDev(spec.commandLine().getHelp(), runner.getBuildTool(), commandArgs); return CommandLine.ExitCode.OK; } @@ -66,6 +71,7 @@ void dryRunDev(CommandLine.Help help, BuildTool buildTool, BuildSystemRunner.Bui public String toString() { return "Dev [debugOptions=" + debugOptions + ", devOptions=" + devOptions + + ", properties=" + propertiesOptions.properties + ", output=" + output + ", params=" + params + "]"; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/BaseBuildCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/build/BaseBuildCommand.java index 887885e5d77c0..3e6256b45550b 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/BaseBuildCommand.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/BaseBuildCommand.java @@ -27,7 +27,10 @@ public class BaseBuildCommand { public Path projectRoot() { if (projectRoot == null) { - projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); + projectRoot = output.getTestDirectory(); + if (projectRoot == null) { + projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath(); + } } return projectRoot; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/BuildSystemRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/BuildSystemRunner.java index 99c55c2645942..40375ac1bcb73 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/BuildSystemRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/BuildSystemRunner.java @@ -15,6 +15,7 @@ import io.quarkus.cli.common.DevOptions; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.OutputOptionMixin; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.RegistryClientMixin; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.project.BuildTool; @@ -88,9 +89,11 @@ Integer listExtensions(RunModeOption runMode, ListFormatOptions format, boolean Integer removeExtension(RunModeOption runMode, Set extensions) throws Exception; - BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption runMode, List params); + BuildCommandArgs prepareBuild(BuildOptions buildOptions, PropertiesOptions propertiesOptions, RunModeOption runMode, + List params); - BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debugOptions, List params); + BuildCommandArgs prepareDevMode(DevOptions devOptions, PropertiesOptions propertiesOptions, DebugOptions debugOptions, + List params); Path getProjectRoot(); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java index 3a7906ddcb4cd..87c60d333e3b2 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/GradleRunner.java @@ -11,6 +11,7 @@ import io.quarkus.cli.common.DevOptions; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.OutputOptionMixin; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.project.BuildTool; import io.quarkus.registry.config.RegistriesConfigLocator; @@ -97,7 +98,8 @@ public Integer removeExtension(RunModeOption runMode, Set extensions) { } @Override - public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption runMode, List params) { + public BuildCommandArgs prepareBuild(BuildOptions buildOptions, PropertiesOptions propertiesOptions, RunModeOption runMode, + List params) { ArrayDeque args = new ArrayDeque<>(); setGradleProperties(args, runMode.isBatchMode()); @@ -116,7 +118,7 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru } // add any other discovered properties - args.addAll(flattenMappedProperties(buildOptions.properties)); + args.addAll(flattenMappedProperties(propertiesOptions.properties)); // Add any other unmatched arguments args.addAll(params); @@ -124,7 +126,8 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru } @Override - public BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debugOptions, List params) { + public BuildCommandArgs prepareDevMode(DevOptions devOptions, PropertiesOptions propertiesOptions, + DebugOptions debugOptions, List params) { ArrayDeque args = new ArrayDeque<>(); setGradleProperties(args, false); @@ -140,7 +143,7 @@ public BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debug //TODO: addDebugArguments(args, debugOptions); // add any other discovered properties - args.addAll(flattenMappedProperties(devOptions.properties)); + args.addAll(flattenMappedProperties(propertiesOptions.properties)); // Add any other unmatched arguments args.addAll(params); return prependExecutable(args); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java index 2cea8ce57d985..49e4212fea710 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/JBangRunner.java @@ -11,6 +11,7 @@ import io.quarkus.cli.common.DevOptions; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.OutputOptionMixin; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.RegistryClientMixin; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.project.BuildTool; @@ -58,7 +59,8 @@ public Integer removeExtension(RunModeOption runMode, Set extensions) { } @Override - public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption runMode, List params) { + public BuildCommandArgs prepareBuild(BuildOptions buildOptions, PropertiesOptions propertiesOptions, RunModeOption runMode, + List params) { ArrayDeque args = new ArrayDeque<>(); if (buildOptions.offline) { @@ -77,7 +79,8 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru } @Override - public BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debugOptions, List params) { + public BuildCommandArgs prepareDevMode(DevOptions devOptions, PropertiesOptions propertiesOptions, + DebugOptions debugOptions, List params) { throw new UnsupportedOperationException("Not there yet. ;)"); } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java b/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java index b91b748d00b57..ac4ebd77821ea 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/build/MavenRunner.java @@ -12,6 +12,7 @@ import io.quarkus.cli.common.DevOptions; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.OutputOptionMixin; +import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.RunModeOption; import io.quarkus.devtools.commands.AddExtensions; import io.quarkus.devtools.commands.ListExtensions; @@ -97,7 +98,8 @@ public Integer removeExtension(RunModeOption runMode, Set extensions) th } @Override - public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption runMode, List params) { + public BuildCommandArgs prepareBuild(BuildOptions buildOptions, PropertiesOptions propertiesOptions, RunModeOption runMode, + List params) { ArrayDeque args = new ArrayDeque<>(); setMavenProperties(args, runMode.isBatchMode()); @@ -122,7 +124,7 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru } // add any other discovered properties - args.addAll(flattenMappedProperties(buildOptions.properties)); + args.addAll(flattenMappedProperties(propertiesOptions.properties)); // Add any other unmatched arguments args.addAll(params); @@ -130,7 +132,8 @@ public BuildCommandArgs prepareBuild(BuildOptions buildOptions, RunModeOption ru } @Override - public BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debugOptions, List params) { + public BuildCommandArgs prepareDevMode(DevOptions devOptions, PropertiesOptions propertiesOptions, + DebugOptions debugOptions, List params) { ArrayDeque args = new ArrayDeque<>(); setMavenProperties(args, false); @@ -145,7 +148,7 @@ public BuildCommandArgs prepareDevMode(DevOptions devOptions, DebugOptions debug //TODO: addDebugArguments(args, debugOptions); - args.addAll(flattenMappedProperties(devOptions.properties)); + args.addAll(flattenMappedProperties(propertiesOptions.properties)); // Add any other unmatched arguments args.addAll(params); return prependExecutable(args); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/BuildOptions.java b/devtools/cli/src/main/java/io/quarkus/cli/common/BuildOptions.java index c80dbf9d9d4db..7aa0807587b6f 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/BuildOptions.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/BuildOptions.java @@ -1,13 +1,8 @@ package io.quarkus.cli.common; -import java.util.HashMap; -import java.util.Map; - import picocli.CommandLine; public class BuildOptions { - public Map properties = new HashMap<>(); - @CommandLine.Option(order = 3, names = { "--clean" }, description = "Perform clean as part of build. False by default.", negatable = true) public boolean clean = false; @@ -21,11 +16,6 @@ public class BuildOptions { @CommandLine.Option(order = 6, names = { "--tests" }, description = "Run tests.", negatable = true) public boolean runTests = true; - @CommandLine.Option(order = 7, names = "-D", mapFallbackValue = "", description = "Additional Java properties.") - void setProperty(Map props) { - this.properties = props; - } - public boolean skipTests() { return !runTests; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/DevOptions.java b/devtools/cli/src/main/java/io/quarkus/cli/common/DevOptions.java index 888a8db2191ca..4a0741ea6f448 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/DevOptions.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/DevOptions.java @@ -1,15 +1,14 @@ package io.quarkus.cli.common; -import java.util.HashMap; -import java.util.Map; - import picocli.CommandLine; public class DevOptions { - public Map properties = new HashMap<>(); + @CommandLine.Option(order = 2, names = { "--dry-run" }, description = "Show actions that would be taken.") + boolean dryRun = false; - @CommandLine.Option(order = 2, names = { "--dryrun" }, description = "Show actions that would be taken.") - public boolean dryRun = false; + // Allow the option variant, but don't crowd help + @CommandLine.Option(names = { "--dryrun" }, hidden = true) + boolean dryRun2 = false; @CommandLine.Option(order = 3, names = { "--clean" }, description = "Perform clean as part of build. False by default.", negatable = true) @@ -19,18 +18,16 @@ public class DevOptions { "--no-tests" }, description = "Toggle continuous testing mode. Enabled by default.", negatable = true, hidden = true) public boolean runTests = true; // TODO: does this make sense re: continuous test? - @CommandLine.Option(order = 5, names = "-D", mapFallbackValue = "", description = "Java properties") - void setProperty(Map props) { - this.properties = props; - } - public boolean skipTests() { return !runTests; } + public boolean isDryRun() { + return dryRun || dryRun2; + } + @Override public String toString() { - return "DevOptions [clean=" + clean + ", properties=" + properties + ", tests=" + runTests - + "]"; + return "DevOptions [clean=" + clean + ", tests=" + runTests + "]"; } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java index 9a556d47ccf2b..347ca1dd4c998 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/OutputOptionMixin.java @@ -4,6 +4,8 @@ import static io.quarkus.devtools.messagewriter.MessageIcons.WARN_ICON; import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import io.quarkus.devtools.messagewriter.MessageWriter; @@ -26,6 +28,14 @@ public class OutputOptionMixin implements MessageWriter { "--cli-test" }, hidden = true, description = "Manually set output streams for unit test purposes.") boolean cliTestMode; + Path testProjectRoot; + + @CommandLine.Option(names = { "--cli-test-dir" }, hidden = true) + void setTestProjectRoot(String path) { + // Allow the starting/project directory to be specified. Used during test. + testProjectRoot = Paths.get(path).toAbsolutePath(); + } + @Spec(Spec.Target.MIXEE) CommandSpec mixee; @@ -91,6 +101,13 @@ public void printStackTrace(Exception ex) { } } + public Path getTestDirectory() { + if (isCliTest()) { + return testProjectRoot; + } + return null; + } + @Override public void info(String msg) { out().println(colorScheme().ansi().new Text(msg, colorScheme())); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/PropertiesOptions.java b/devtools/cli/src/main/java/io/quarkus/cli/common/PropertiesOptions.java new file mode 100644 index 0000000000000..401677babf2de --- /dev/null +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/PropertiesOptions.java @@ -0,0 +1,20 @@ +package io.quarkus.cli.common; + +import java.util.HashMap; +import java.util.Map; + +import picocli.CommandLine; + +public class PropertiesOptions { + public Map properties = new HashMap<>(); + + @CommandLine.Option(order = 5, names = "-D", mapFallbackValue = "", description = "Java properties") + void setProperty(Map props) { + this.properties = props; + } + + @Override + public String toString() { + return properties.toString(); + } +} diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java index 57b0eb7abd5f6..f7af45abcbf41 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java @@ -25,7 +25,6 @@ import picocli.CommandLine.Spec; public class CreateProjectMixin { - Map values = new HashMap<>(); Path outputPath; Path projectRootPath; @@ -41,6 +40,12 @@ public class CreateProjectMixin { @Mixin RegistryClientMixin registryClient; + public void setTestOutputDirectory(Path testOutputDirectory) { + if (testOutputDirectory != null && targetDirectory == null) { + outputPath = testOutputDirectory; + } + } + public Path outputDirectory() { if (outputPath == null) { outputPath = CreateProjectHelper.createOutputDirectory(targetDirectory); @@ -88,7 +93,7 @@ public void setValue(String name, Object value) { } public QuarkusCommandInvocation build(BuildTool buildTool, TargetQuarkusVersionGroup targetVersion, - OutputOptionMixin log) + OutputOptionMixin log, Map properties) throws RegistryResolutionException { // TODO: Allow this to be configured? infer from active Java version? @@ -104,6 +109,16 @@ public QuarkusCommandInvocation build(BuildTool buildTool, TargetQuarkusVersionG } QuarkusProject qp = registryClient.createQuarkusProject(projectRoot(), targetVersion, buildTool, log); + + properties.entrySet().forEach(x -> { + if (x.getValue().length() > 0) { + System.setProperty(x.getKey(), x.getValue()); + log.info("property: %s=%s", x.getKey(), x.getValue()); + } else { + System.setProperty(x.getKey(), ""); + log.info("property: %s", x.getKey()); + } + }); return new QuarkusCommandInvocation(qp, values); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java index d29ff2327f03b..733e1f4edbbe0 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java @@ -20,7 +20,7 @@ public class CliDriver { static final PrintStream stdout = System.out; static final PrintStream stderr = System.err; - public static Result executeArbitraryCommand(String... args) throws Exception { + public static Result executeArbitraryCommand(Path startingDir, String... args) throws Exception { System.out.println("$ " + String.join(" ", args)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -34,6 +34,7 @@ public static Result executeArbitraryCommand(String... args) throws Exception { Result result = new Result(); try { ProcessBuilder pb = new ProcessBuilder(args); + pb.directory(startingDir.toFile()); pb.redirectError(ProcessBuilder.Redirect.INHERIT); pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); @@ -50,9 +51,11 @@ public static Result executeArbitraryCommand(String... args) throws Exception { return result; } - public static Result execute(String... args) throws Exception { - String newArgs[] = Arrays.copyOf(args, args.length + 1); + public static Result execute(Path startingDir, String... args) throws Exception { + String newArgs[] = Arrays.copyOf(args, args.length + 3); newArgs[args.length] = "--cli-test"; + newArgs[args.length + 1] = "--cli-test-dir"; + newArgs[args.length + 2] = startingDir.toString(); System.out.println("$ quarkus " + String.join(" ", newArgs)); @@ -125,70 +128,70 @@ public static void deleteDir(Path path) throws Exception { Assertions.assertFalse(path.toFile().exists()); } - public static String readFileAsString(Path path) throws Exception { + public static String readFileAsString(Path projectRoot, Path path) throws Exception { return new String(Files.readAllBytes(path)); } - public static void valdiateGeneratedSourcePackage(Path project, String name) { - Path packagePath = project.resolve("src/main/java/" + name); + public static void valdiateGeneratedSourcePackage(Path projectRoot, String name) { + Path packagePath = projectRoot.resolve("src/main/java/" + name); Assertions.assertTrue(packagePath.toFile().exists(), "Package directory should exist: " + packagePath.toAbsolutePath().toString()); Assertions.assertTrue(packagePath.toFile().isDirectory(), "Package directory should be a directory: " + packagePath.toAbsolutePath().toString()); } - public static Result invokeValidateExtensionList() throws Exception { - Result result = execute("extension", "list", "-e", "-B", "--verbose"); + public static Result invokeValidateExtensionList(Path projectRoot) throws Exception { + Result result = execute(projectRoot, "extension", "list", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); return result; } - public static Result invokeExtensionAddQute(Path file) throws Exception { + public static Result invokeExtensionAddQute(Path projectRoot, Path file) throws Exception { // add the qute extension - Result result = execute("extension", "add", "qute", "-e", "-B", "--verbose"); + Result result = execute(projectRoot, "extension", "add", "qute", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // list all extensions, make sure qute is present - result = invokeValidateExtensionList(); + result = invokeValidateExtensionList(projectRoot); Assertions.assertTrue(result.stdout.contains("quarkus-qute"), "Expected quarkus-qute to be in the list of extensions. Result:\n" + result); - String content = readFileAsString(file); + String content = readFileAsString(projectRoot, file); Assertions.assertTrue(content.contains("quarkus-qute"), "quarkus-qute should be listed as a dependency. Result:\n" + content); return result; } - public static Result invokeExtensionRemoveQute(Path file) throws Exception { + public static Result invokeExtensionRemoveQute(Path projectRoot, Path file) throws Exception { // remove the qute extension - Result result = execute("extension", "remove", "qute", "-e", "-B", "--verbose"); + Result result = execute(projectRoot, "extension", "remove", "qute", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // list all extensions, make sure qute is present - result = invokeValidateExtensionList(); + result = invokeValidateExtensionList(projectRoot); Assertions.assertFalse(result.stdout.contains("quarkus-qute"), "Expected quarkus-qute to be missing from the list of extensions. Result:\n" + result); - String content = readFileAsString(file); + String content = readFileAsString(projectRoot, file); Assertions.assertFalse(content.contains("quarkus-qute"), "quarkus-qute should not be listed as a dependency. Result:\n" + content); return result; } - public static Result invokeExtensionAddMultiple(Path file) throws Exception { + public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) throws Exception { // add the qute extension - Result result = execute("extension", "add", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); + Result result = execute(projectRoot, "extension", "add", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // list all extensions, make sure all are present - result = invokeValidateExtensionList(); + result = invokeValidateExtensionList(projectRoot); Assertions.assertTrue(result.stdout.contains("quarkus-qute"), "Expected quarkus-qute to be in the list of extensions. Result:\n" + result); Assertions.assertTrue(result.stdout.contains("quarkus-amazon-lambda-http"), @@ -196,7 +199,7 @@ public static Result invokeExtensionAddMultiple(Path file) throws Exception { Assertions.assertTrue(result.stdout.contains("quarkus-jackson"), "Expected quarkus-jackson to be in the list of extensions. Result:\n" + result); - String content = CliDriver.readFileAsString(file); + String content = CliDriver.readFileAsString(projectRoot, file); Assertions.assertTrue(content.contains("quarkus-qute"), "quarkus-qute should still be listed as a dependency. Result:\n" + content); Assertions.assertTrue(content.contains("quarkus-amazon-lambda-http"), @@ -207,14 +210,14 @@ public static Result invokeExtensionAddMultiple(Path file) throws Exception { return result; } - public static Result invokeExtensionRemoveMultiple(Path file) throws Exception { + public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) throws Exception { // add the qute extension - Result result = execute("extension", "remove", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); + Result result = execute(projectRoot, "extension", "remove", "amazon-lambda-http", "jackson", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // list all extensions, make sure all are present - result = invokeValidateExtensionList(); + result = invokeValidateExtensionList(projectRoot); Assertions.assertFalse(result.stdout.contains("quarkus-qute"), "quarkus-qute should not be in the list of extensions. Result:\n" + result); Assertions.assertFalse(result.stdout.contains("quarkus-amazon-lambda-http"), @@ -222,7 +225,7 @@ public static Result invokeExtensionRemoveMultiple(Path file) throws Exception { Assertions.assertFalse(result.stdout.contains("quarkus-jackson"), "quarkus-jackson should not be in the list of extensions. Result:\n" + result); - String content = CliDriver.readFileAsString(file); + String content = CliDriver.readFileAsString(projectRoot, file); Assertions.assertFalse(content.contains("quarkus-qute"), "quarkus-qute should not be listed as a dependency. Result:\n" + content); Assertions.assertFalse(content.contains("quarkus-amazon-lambda-http"), @@ -233,8 +236,8 @@ public static Result invokeExtensionRemoveMultiple(Path file) throws Exception { return result; } - public static Result invokeExtensionListInstallable() throws Exception { - Result result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i"); + public static Result invokeExtensionListInstallable(Path projectRoot) throws Exception { + Result result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); Assertions.assertTrue(result.stdout.contains("quarkus-hibernate-orm"), @@ -245,8 +248,8 @@ public static Result invokeExtensionListInstallable() throws Exception { return result; } - public static Result invokeExtensionListInstallableSearch() throws Exception { - Result result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i", "--search=vertx-*"); + public static Result invokeExtensionListInstallableSearch(Path projectRoot) throws Exception { + Result result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i", "--search=vertx-*"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -258,8 +261,8 @@ public static Result invokeExtensionListInstallableSearch() throws Exception { return result; } - public static void invokeExtensionListFormatting() throws Exception { - Result result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i", "--concise"); + public static void invokeExtensionListFormatting(Path projectRoot) throws Exception { + Result result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i", "--concise"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); Assertions.assertTrue(result.stdout.contains("quarkus-vertx-web"), @@ -267,38 +270,38 @@ public static void invokeExtensionListFormatting() throws Exception { Assertions.assertTrue(result.stdout.contains("Reactive Routes"), "'Reactive Routes' descriptive name should be returned in results. Found:\n" + result); - result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i", "--full"); + result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i", "--full"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // TODO - result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i", "--origins"); + result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i", "--origins"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); // TODO // Two different output options can not be specified together - result = CliDriver.execute("extension", "list", "-e", "-B", "--verbose", "-i", "--origins", "--name"); + result = CliDriver.execute(projectRoot, "extension", "list", "-e", "-B", "--verbose", "-i", "--origins", "--name"); Assertions.assertEquals(CommandLine.ExitCode.USAGE, result.exitCode, "Expected OK return code. Result:\n" + result); // TODO } - public static Result invokeExtensionAddRedundantQute() throws Exception { - Result result = execute("extension", "add", "-e", "-B", "--verbose", "qute"); + public static Result invokeExtensionAddRedundantQute(Path projectRoot) throws Exception { + Result result = execute(projectRoot, "extension", "add", "-e", "-B", "--verbose", "qute"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); return result; } - public static Result invokeExtensionRemoveNonexistent() throws Exception { - Result result = execute("extension", "remove", "-e", "-B", "--verbose", "nonexistent"); + public static Result invokeExtensionRemoveNonexistent(Path projectRoot) throws Exception { + Result result = execute(projectRoot, "extension", "remove", "-e", "-B", "--verbose", "nonexistent"); System.out.println(result); return result; } - public static Result invokeValidateDryRunBuild(Path project) throws Exception { - Result result = execute("build", "-e", "-B", "--clean", "--dryrun", + public static Result invokeValidateDryRunBuild(Path projectRoot) throws Exception { + Result result = execute(projectRoot, "build", "-e", "-B", "--clean", "--dryrun", "-Dproperty=value1", "-Dproperty2=value2"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); @@ -306,19 +309,19 @@ public static Result invokeValidateDryRunBuild(Path project) throws Exception { return result; } - public static Result invokeValidateBuild(Path project) throws Exception { - Result result = execute("build", "-e", "-B", "--clean", + public static Result invokeValidateBuild(Path projectRoot) throws Exception { + Result result = execute(projectRoot, "build", "-e", "-B", "--clean", "-Dproperty=value1", "-Dproperty2=value2"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); return result; } - public static void validateApplicationProperties(Path project, List configs) throws Exception { - Path properties = project.resolve("src/main/resources/application.properties"); + public static void validateApplicationProperties(Path projectRoot, List configs) throws Exception { + Path properties = projectRoot.resolve("src/main/resources/application.properties"); Assertions.assertTrue(properties.toFile().exists(), "application.properties should exist: " + properties.toAbsolutePath().toString()); - String propertiesFile = CliDriver.readFileAsString(properties); + String propertiesFile = CliDriver.readFileAsString(projectRoot, properties); configs.forEach(conf -> Assertions.assertTrue(propertiesFile.contains(conf), "Properties file should contain " + conf + ". Found:\n" + propertiesFile)); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliHelpTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliHelpTest.java index 14762c346a643..4f53e6bd3b191 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliHelpTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliHelpTest.java @@ -1,5 +1,8 @@ package io.quarkus.cli; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; @@ -7,19 +10,23 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import io.quarkus.devtools.messagewriter.MessageIcons; +import io.quarkus.devtools.messagewriter.MessageWriter; + /** * This is ordered to make output easier to view (as it effectively dumps help) */ @TestMethodOrder(OrderAnnotation.class) public class CliHelpTest { + static Path workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath().resolve("target/test-project"); @Test @Order(1) public void testCommandHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "--help"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute(); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot); Assertions.assertEquals(result.stdout, result2.stdout, "Invoking the base command should show usage help"); CliDriver.println("-- same as above\n\n"); } @@ -27,21 +34,21 @@ public void testCommandHelp() throws Exception { @Test @Order(20) public void testCreateHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "--help"); result.echoSystemOut(); } @Test @Order(21) public void testCreateAppHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--help"); result.echoSystemOut(); } @Test @Order(22) public void testCreateCliHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "cli", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "cli", "--help"); result.echoSystemOut(); } @@ -49,31 +56,31 @@ public void testCreateCliHelp() throws Exception { @Order(23) @Disabled public void testCreateExtensionHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "extension", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "extension", "--help"); result.echoSystemOut(); } @Test @Order(30) public void testBuildHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("build", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "build", "--help"); result.echoSystemOut(); } @Test @Order(40) public void testDevHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("dev", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "dev", "--help"); result.echoSystemOut(); } @Test @Order(50) public void testExtHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("ext", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "--help"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute("extension", "--help"); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot, "extension", "--help"); Assertions.assertEquals(result.stdout, result2.stdout, "Help output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); } @@ -81,17 +88,17 @@ public void testExtHelp() throws Exception { @Test @Order(51) public void testExtAddHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("ext", "add", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "add", "--help"); result.echoSystemOut(); } @Test @Order(52) public void testExtListHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("ext", "ls", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "ls", "--help"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute("ext", "list", "--help"); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot, "ext", "list", "--help"); Assertions.assertEquals(result.stdout, result2.stdout, "Help output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); } @@ -99,10 +106,10 @@ public void testExtListHelp() throws Exception { @Test @Order(53) public void testExtRemoveHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("ext", "rm", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "rm", "--help"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute("ext", "remove", "--help"); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot, "ext", "remove", "--help"); Assertions.assertEquals(result.stdout, result2.stdout, "Help output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); } @@ -110,14 +117,26 @@ public void testExtRemoveHelp() throws Exception { @Order(60) @Test public void testGenerateCompletionHelp() throws Exception { - CliDriver.Result result = CliDriver.execute("generate-completion", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "generate-completion", "--help"); result.echoSystemOut(); } @Test @Order(70) public void testCommandVersion() throws Exception { - CliDriver.Result result = CliDriver.execute("version", "--help"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "version", "--help"); result.echoSystemOut(); } + + @Test + @Order(80) + public void testMessageFlags() throws Exception { + MessageWriter writer = MessageWriter.debug(); + writer.error("error"); // has emoji + writer.warn("warn"); // has emoji + writer.info(MessageIcons.NOOP_ICON + " info"); + writer.info(MessageIcons.OK_ICON + " info"); + writer.info(MessageIcons.NOK_ICON + " info"); + writer.debug("debug"); + } } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java index b6e7205b3646c..2c440393f2ced 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java @@ -5,7 +5,6 @@ import java.nio.file.Paths; import java.util.Arrays; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -14,21 +13,18 @@ import picocli.CommandLine; public class CliNonProjectTest { - static String startingDir; static Path workspaceRoot; @BeforeAll public static void initial() throws Exception { - startingDir = System.getProperty("user.dir"); - workspaceRoot = Paths.get(startingDir).toAbsolutePath().resolve("target/test-project/CliNonProjectTest"); - System.setProperty("user.dir", workspaceRoot.toFile().getAbsolutePath()); + workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath() + .resolve("target/test-project/CliNonProjectTest"); CliDriver.deleteDir(workspaceRoot); Files.createDirectories(workspaceRoot); } @AfterEach public void verifyEmptyDirectory() throws Exception { - System.setProperty("user.dir", workspaceRoot.toFile().getAbsolutePath()); String[] files = workspaceRoot.toFile().list(); Assertions.assertNotNull(files, "Directory list operation should succeed"); @@ -36,14 +32,9 @@ public void verifyEmptyDirectory() throws Exception { "Directory should be empty. Found: " + Arrays.toString(files)); } - @AfterAll - public static void allDone() { - System.setProperty("user.dir", startingDir); - } - @Test public void testListOutsideOfProject() throws Exception { - CliDriver.Result result = CliDriver.execute("ext", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "-e"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("Jackson"), @@ -53,7 +44,7 @@ public void testListOutsideOfProject() throws Exception { @Test public void testListPlatformExtensions() throws Exception { // List extensions of a specified platform version - CliDriver.Result result = CliDriver.execute("ext", "list", "-p=io.quarkus:quarkus-bom:2.0.0.CR3", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "list", "-p=io.quarkus:quarkus-bom:2.0.0.CR3", "-e"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("Jackson"), @@ -64,7 +55,7 @@ public void testListPlatformExtensions() throws Exception { @Test public void testBuildOutsideOfProject() throws Exception { - CliDriver.Result result = CliDriver.execute("build", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "build", "-e"); Assertions.assertEquals(CommandLine.ExitCode.USAGE, result.exitCode, "'quarkus build' should fail outside of a quarkus project directory:\n" + result); System.out.println(result); @@ -72,7 +63,7 @@ public void testBuildOutsideOfProject() throws Exception { @Test public void testDevOutsideOfProject() throws Exception { - CliDriver.Result result = CliDriver.execute("dev", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "dev", "-e"); Assertions.assertEquals(CommandLine.ExitCode.USAGE, result.exitCode, "'quarkus dev' should fail outside of a quarkus project directory:\n" + result); System.out.println(result); @@ -81,13 +72,13 @@ public void testDevOutsideOfProject() throws Exception { @Test public void testCreateAppDryRun() throws Exception { // A dry run of create should not create any files or directories - CliDriver.Result result = CliDriver.execute("create", "--dry-run", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "--dry-run", "-e"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("project would have been created"), "Should contain 'project would have been created', found: " + result.stdout); - CliDriver.Result result2 = CliDriver.execute("create", "--dryrun", "-e"); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot, "create", "--dryrun", "-e"); Assertions.assertEquals(result.stdout, result2.stdout, "Invoking the command with --dryrun should produce the same result"); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java index 8871b9a3dc317..4ca3fd5a889e7 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java @@ -7,10 +7,8 @@ import java.util.Arrays; import java.util.List; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -25,29 +23,17 @@ */ @Tag("failsOnJDK16") public class CliProjectGradleTest { - static String startingDir; - static Path workspaceRoot; + static Path workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath() + .resolve("target/test-project/CliProjectGradleTest"); Path project; File gradle; - @BeforeAll - public static void initial() throws Exception { - startingDir = System.getProperty("user.dir"); - workspaceRoot = Paths.get(startingDir).toAbsolutePath().resolve("target/test-project/CliProjectGradleTest"); - } - @BeforeEach public void setupTestDirectories() throws Exception { - System.setProperty("user.dir", workspaceRoot.toFile().getAbsolutePath()); CliDriver.deleteDir(workspaceRoot); project = workspaceRoot.resolve("code-with-quarkus"); } - @AfterAll - public static void allDone() { - System.setProperty("user.dir", startingDir); - } - void startGradleDaemon(boolean useWrapper) throws Exception { if (useWrapper) { gradle = ExecuteUtil.findWrapper(project, GradleRunner.windowsWrapper, GradleRunner.otherWrapper); @@ -66,7 +52,7 @@ void startGradleDaemon(boolean useWrapper) throws Exception { args.add("-Dmaven.repo.local=" + localMavenRepo); } - CliDriver.Result result = CliDriver.executeArbitraryCommand(args.toArray(new String[0])); + CliDriver.Result result = CliDriver.executeArbitraryCommand(project, args.toArray(new String[0])); Assertions.assertEquals(0, result.exitCode, "Gradle daemon should start properly"); } @@ -84,14 +70,14 @@ void stopGradleDaemon() throws Exception { args.add("-Dmaven.repo.local=" + localMavenRepo); } - CliDriver.Result result = CliDriver.executeArbitraryCommand(args.toArray(new String[0])); + CliDriver.Result result = CliDriver.executeArbitraryCommand(project, args.toArray(new String[0])); Assertions.assertEquals(0, result.exitCode, "Gradle daemon should stop properly"); } } @Test public void testCreateAppDefaults() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "--gradle", "--verbose", "-e", "-B"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--gradle", "--verbose", "-e", "-B"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("SUCCESS"), "Expected confirmation that the project has been created." + result); @@ -106,21 +92,21 @@ public void testCreateAppDefaults() throws Exception { CliDriver.valdiateGeneratedSourcePackage(project, "org/acme"); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); startGradleDaemon(true); CliDriver.invokeValidateBuild(project); } @Test public void testCreateAppOverrides() throws Exception { - project = workspaceRoot.resolve("nested/my-project"); + Path nested = workspaceRoot.resolve("cli-nested"); + project = nested.resolve("my-project"); List configs = Arrays.asList("custom.app.config1=val1", "custom.app.config2=val2", "lib.config=val3"); - CliDriver.Result result = CliDriver.execute("create", "app", "--gradle", "--verbose", "-e", "-B", + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--gradle", "--verbose", "-e", "-B", "--package-name=custom.pkg", - "--output-directory=nested", + "--output-directory=" + nested, "--group-id=silly", "--artifact-id=my-project", "--version=0.1.0", "--app-config=" + String.join(",", configs), "resteasy-reactive"); @@ -140,7 +126,6 @@ public void testCreateAppOverrides() throws Exception { CliDriver.valdiateGeneratedSourcePackage(project, "custom/pkg"); CliDriver.validateApplicationProperties(project, configs); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); startGradleDaemon(true); result = CliDriver.invokeValidateDryRunBuild(project); @@ -152,39 +137,41 @@ public void testCreateAppOverrides() throws Exception { @Test public void testExtensionList() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "--gradle", "--verbose", "-e", "-B"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--gradle", "--verbose", "-e", "-B"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); startGradleDaemon(true); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); Path buildGradle = project.resolve("build.gradle"); - String buildGradleContent = CliDriver.readFileAsString(buildGradle); + String buildGradleContent = CliDriver.readFileAsString(project, buildGradle); Assertions.assertFalse(buildGradleContent.contains("quarkus-qute"), "Dependencies should not contain qute extension by default. Found:\n" + buildGradleContent); - CliDriver.invokeExtensionAddQute(buildGradle); - CliDriver.invokeExtensionAddRedundantQute(); - CliDriver.invokeExtensionListInstallable(); - CliDriver.invokeExtensionAddMultiple(buildGradle); - CliDriver.invokeExtensionRemoveQute(buildGradle); - CliDriver.invokeExtensionRemoveMultiple(buildGradle); + CliDriver.invokeExtensionAddQute(project, buildGradle); + CliDriver.invokeExtensionAddRedundantQute(project); + CliDriver.invokeExtensionListInstallable(project); + CliDriver.invokeExtensionAddMultiple(project, buildGradle); + CliDriver.invokeExtensionRemoveQute(project, buildGradle); + CliDriver.invokeExtensionRemoveMultiple(project, buildGradle); - CliDriver.invokeExtensionListInstallableSearch(); - CliDriver.invokeExtensionListFormatting(); + CliDriver.invokeExtensionListInstallableSearch(project); + CliDriver.invokeExtensionListFormatting(project); // TODO: Maven and Gradle give different return codes - result = CliDriver.invokeExtensionRemoveNonexistent(); + result = CliDriver.invokeExtensionRemoveNonexistent(project); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. Result:\n" + result); } @Test public void testCreateArgPassthrough() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "--gradle", + Path nested = workspaceRoot.resolve("cli-nested"); + project = nested.resolve("my-project"); + + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "--gradle", "--verbose", "-e", "-B", "--dryrun", "--no-wrapper", "--package-name=custom.pkg", - "--output-directory=nested", + "--output-directory=" + nested, "--group-id=silly", "--artifact-id=my-project", "--version=0.1.0"); // We don't need to retest this, just need to make sure all of the arguments were passed through @@ -211,7 +198,7 @@ String validateBasicIdentifiers(Path project, String group, String artifact, Str Assertions.assertTrue(buildGradle.toFile().exists(), "build.gradle should exist: " + buildGradle.toAbsolutePath().toString()); - String buildContent = CliDriver.readFileAsString(buildGradle); + String buildContent = CliDriver.readFileAsString(project, buildGradle); Assertions.assertTrue(buildContent.contains("group '" + group + "'"), "build.gradle should include the group id:\n" + buildContent); Assertions.assertTrue(buildContent.contains("version '" + version + "'"), @@ -220,7 +207,7 @@ String validateBasicIdentifiers(Path project, String group, String artifact, Str Path settings = project.resolve("settings.gradle"); Assertions.assertTrue(settings.toFile().exists(), "settings.gradle should exist: " + settings.toAbsolutePath().toString()); - String settingsContent = CliDriver.readFileAsString(settings); + String settingsContent = CliDriver.readFileAsString(project, settings); Assertions.assertTrue(settingsContent.contains(artifact), "settings.gradle should include the artifact id:\n" + settingsContent); diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java index 835b51b1b7d16..e6882fba480ad 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java @@ -3,9 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,31 +11,20 @@ import picocli.CommandLine; public class CliProjectJBangTest { - static String startingDir; - static Path workspaceRoot; - Path project; + static Path workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath() + .resolve("target/test-project/CliProjectJBangTest"); - @BeforeAll - public static void initial() throws Exception { - startingDir = System.getProperty("user.dir"); - workspaceRoot = Paths.get(startingDir).toAbsolutePath().resolve("target/test-project/CliProjectJBangTest"); - } + Path project; @BeforeEach public void setupTestDirectories() throws Exception { - System.setProperty("user.dir", workspaceRoot.toFile().getAbsolutePath()); CliDriver.deleteDir(workspaceRoot); project = workspaceRoot.resolve("code-with-quarkus"); } - @AfterAll - public static void allDone() { - System.setProperty("user.dir", startingDir); - } - @Test public void testCreateAppDefaults() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "--jbang", "--verbose", "-e", "-B"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--jbang", "--verbose", "-e", "-B"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("SUCCESS"), "Expected confirmation that the project has been created." + result); @@ -51,11 +38,10 @@ public void testCreateAppDefaults() throws Exception { Path javaMain = valdiateJBangSourcePackage(project, ""); // no package name - String source = CliDriver.readFileAsString(javaMain); + String source = CliDriver.readFileAsString(project, javaMain); Assertions.assertTrue(source.contains("quarkus-resteasy"), "Generated source should reference resteasy. Found:\n" + source); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); result = CliDriver.invokeValidateDryRunBuild(project); CliDriver.invokeValidateBuild(project); @@ -63,11 +49,12 @@ public void testCreateAppDefaults() throws Exception { @Test public void testCreateAppOverrides() throws Exception { - project = workspaceRoot.resolve("nested/my-project"); + Path nested = workspaceRoot.resolve("cli-nested"); + project = nested.resolve("my-project"); - CliDriver.Result result = CliDriver.execute("create", "app", "--jbang", "--verbose", "-e", "-B", + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--jbang", "--verbose", "-e", "-B", "--package-name=custom.pkg", - "--output-directory=nested", + "--output-directory=" + nested, "--group-id=silly", "--artifact-id=my-project", "--version=0.1.0", "vertx-web"); @@ -81,11 +68,10 @@ public void testCreateAppOverrides() throws Exception { validateBasicIdentifiers(project, "silly", "my-project", "0.1.0"); Path javaMain = valdiateJBangSourcePackage(project, ""); - String source = CliDriver.readFileAsString(javaMain); + String source = CliDriver.readFileAsString(project, javaMain); Assertions.assertTrue(source.contains("quarkus-vertx-web"), "Generated source should reference vertx-web. Found:\n" + source); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); result = CliDriver.invokeValidateDryRunBuild(project); CliDriver.invokeValidateBuild(project); diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java index 00dd55ded104b..ac649dd1d76d4 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java @@ -5,9 +5,7 @@ import java.util.Arrays; import java.util.List; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,31 +16,19 @@ * Similar to CliProjectGradleTest .. */ public class CliProjectMavenTest { - static String startingDir; - static Path workspaceRoot; + static Path workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath() + .resolve("target/test-project/CliProjectMavenTest"); Path project; - @BeforeAll - public static void initial() throws Exception { - startingDir = System.getProperty("user.dir"); - workspaceRoot = Paths.get(startingDir).toAbsolutePath().resolve("target/test-project/CliProjectMavenTest"); - } - @BeforeEach public void setupTestDirectories() throws Exception { - System.setProperty("user.dir", workspaceRoot.toFile().getAbsolutePath()); CliDriver.deleteDir(workspaceRoot); project = workspaceRoot.resolve("code-with-quarkus"); } - @AfterAll - public static void allDone() { - System.setProperty("user.dir", startingDir); - } - @Test public void testCreateAppDefaults() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "-e", "-B", "--verbose"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("SUCCESS"), "Expected confirmation that the project has been created." + result); @@ -57,7 +43,6 @@ public void testCreateAppDefaults() throws Exception { CliDriver.valdiateGeneratedSourcePackage(project, "org/acme"); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); result = CliDriver.invokeValidateDryRunBuild(project); CliDriver.invokeValidateBuild(project); @@ -65,14 +50,15 @@ public void testCreateAppDefaults() throws Exception { @Test public void testCreateAppOverrides() throws Exception { - project = workspaceRoot.resolve("nested/my-project"); + Path nested = workspaceRoot.resolve("cli-nested"); + project = nested.resolve("my-project"); List configs = Arrays.asList("custom.app.config1=val1", "custom.app.config2=val2", "lib.config=val3"); - CliDriver.Result result = CliDriver.execute("create", "app", "--verbose", "-e", "-B", + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--verbose", "-e", "-B", "--no-wrapper", "--package-name=custom.pkg", - "--output-directory=nested", + "--output-directory=" + nested, "--group-id=silly", "--artifact-id=my-project", "--version=0.1.0", "--app-config=" + String.join(",", configs), "resteasy-reactive"); @@ -90,7 +76,6 @@ public void testCreateAppOverrides() throws Exception { CliDriver.valdiateGeneratedSourcePackage(project, "custom/pkg"); CliDriver.validateApplicationProperties(project, configs); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); result = CliDriver.invokeValidateDryRunBuild(project); Assertions.assertTrue(result.stdout.contains("-Dproperty=value1 -Dproperty2=value2"), "result should contain '-Dproperty=value1 -Dproperty2=value2':\n" + result.stdout); @@ -100,38 +85,39 @@ public void testCreateAppOverrides() throws Exception { @Test public void testExtensionList() throws Exception { - CliDriver.Result result = CliDriver.execute("create", "app", "-e", "-B", "--verbose"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "-e", "-B", "--verbose"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); - System.setProperty("user.dir", project.toFile().getAbsolutePath()); - Path pom = project.resolve("pom.xml"); - String pomContent = CliDriver.readFileAsString(pom); + String pomContent = CliDriver.readFileAsString(project, pom); Assertions.assertFalse(pomContent.contains("quarkus-qute"), "Dependencies should not contain qute extension by default. Found:\n" + pomContent); - CliDriver.invokeExtensionAddQute(pom); - CliDriver.invokeExtensionAddRedundantQute(); - CliDriver.invokeExtensionListInstallable(); - CliDriver.invokeExtensionAddMultiple(pom); - CliDriver.invokeExtensionRemoveQute(pom); - CliDriver.invokeExtensionRemoveMultiple(pom); + CliDriver.invokeExtensionAddQute(project, pom); + CliDriver.invokeExtensionAddRedundantQute(project); + CliDriver.invokeExtensionListInstallable(project); + CliDriver.invokeExtensionAddMultiple(project, pom); + CliDriver.invokeExtensionRemoveQute(project, pom); + CliDriver.invokeExtensionRemoveMultiple(project, pom); - CliDriver.invokeExtensionListInstallableSearch(); - CliDriver.invokeExtensionListFormatting(); + CliDriver.invokeExtensionListInstallableSearch(project); + CliDriver.invokeExtensionListFormatting(project); // TODO: Maven and Gradle give different return codes - result = CliDriver.invokeExtensionRemoveNonexistent(); + result = CliDriver.invokeExtensionRemoveNonexistent(project); Assertions.assertEquals(CommandLine.ExitCode.SOFTWARE, result.exitCode, "Expected error return code. Result:\n" + result); } @Test public void testCreateArgPassthrough() throws Exception { - CliDriver.Result result = CliDriver.execute("create", + Path nested = workspaceRoot.resolve("cli-nested"); + project = nested.resolve("my-project"); + + CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "--verbose", "-e", "-B", "--dryrun", "--no-wrapper", "--package-name=custom.pkg", - "--output-directory=nested", + "--output-directory=" + nested, "--group-id=silly", "--artifact-id=my-project", "--version=0.1.0"); // We don't need to retest this, just need to make sure all of the arguments were passed through @@ -158,7 +144,7 @@ String validateBasicIdentifiers(String group, String artifact, String version) t Assertions.assertTrue(pom.toFile().exists(), "pom.xml should exist: " + pom.toAbsolutePath().toString()); - String pomContent = CliDriver.readFileAsString(pom); + String pomContent = CliDriver.readFileAsString(project, pom); Assertions.assertTrue(pomContent.contains("" + group + ""), "pom.xml should contain group id:\n" + pomContent); Assertions.assertTrue(pomContent.contains("" + artifact + ""), diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java index 55c3f05315b5c..ba0fbb38d32d8 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliVersionTest.java @@ -1,19 +1,24 @@ package io.quarkus.cli; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class CliVersionTest { + static Path workspaceRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath().resolve("target/test-project"); + @Test public void testCommandVersion() throws Exception { - CliDriver.Result result = CliDriver.execute("version"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "version"); result.echoSystemOut(); - CliDriver.Result result2 = CliDriver.execute("-v"); + CliDriver.Result result2 = CliDriver.execute(workspaceRoot, "-v"); Assertions.assertEquals(result.stdout, result2.stdout, "Version output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); - CliDriver.Result result3 = CliDriver.execute("--version"); + CliDriver.Result result3 = CliDriver.execute(workspaceRoot, "--version"); Assertions.assertEquals(result.stdout, result3.stdout, "Version output for command aliases should be the same."); CliDriver.println("-- same as above\n\n"); } From dbdd6ca7d542df27f006a2231ea8b4288ae53cdc Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Fri, 18 Jun 2021 16:27:48 -0400 Subject: [PATCH 13/24] CLI: Use -S & -P; error on --stream --no-registry-client (cherry picked from commit aa4ba75b24ceb6f1f6a96f83d58658bde4ec627f) --- devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java | 2 +- devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java | 2 +- .../cli/src/main/java/io/quarkus/cli/CreateExtension.java | 2 +- .../main/java/io/quarkus/cli/ProjectExtensionsList.java | 2 +- .../java/io/quarkus/cli/common/RegistryClientMixin.java | 7 ++++++- .../cli/{create => common}/TargetQuarkusVersionGroup.java | 4 ++-- .../java/io/quarkus/cli/create/CreateProjectMixin.java | 1 + .../src/test/java/io/quarkus/cli/CliNonProjectTest.java | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) rename devtools/cli/src/main/java/io/quarkus/cli/{create => common}/TargetQuarkusVersionGroup.java (98%) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index d2fe8eca2aa45..9d85dda5715b8 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -4,13 +4,13 @@ import java.util.Set; import io.quarkus.cli.common.PropertiesOptions; +import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.cli.create.BaseCreateCommand; import io.quarkus.cli.create.CodeGenerationGroup; import io.quarkus.cli.create.CreateProjectMixin; import io.quarkus.cli.create.TargetBuildToolGroup; import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; -import io.quarkus.cli.create.TargetQuarkusVersionGroup; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index 5b24e8217a99f..be929703a247d 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -4,13 +4,13 @@ import java.util.Set; import io.quarkus.cli.common.PropertiesOptions; +import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.cli.create.BaseCreateCommand; import io.quarkus.cli.create.CodeGenerationGroup; import io.quarkus.cli.create.CreateProjectMixin; import io.quarkus.cli.create.TargetBuildToolGroup; import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; -import io.quarkus.cli.create.TargetQuarkusVersionGroup; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; import io.quarkus.devtools.commands.handlers.CreateProjectCommandHandler; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateExtension.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateExtension.java index f3e12dfe0539a..a1b9e539486f5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateExtension.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateExtension.java @@ -1,11 +1,11 @@ package io.quarkus.cli; +import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.cli.create.BaseCreateCommand; import io.quarkus.cli.create.CreateProjectMixin; import io.quarkus.cli.create.ExtensionNameGenerationGroup; import io.quarkus.cli.create.ExtensionTargetGVGroup; import io.quarkus.cli.create.ExtensionTestGenerationGroup; -import io.quarkus.cli.create.TargetQuarkusVersionGroup; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.codegen.SourceType; import picocli.CommandLine; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java index ef77e63664d38..3c3d7ef6fdb7d 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java @@ -8,7 +8,7 @@ import io.quarkus.cli.build.BuildSystemRunner; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.RunModeOption; -import io.quarkus.cli.create.TargetQuarkusVersionGroup; +import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.devtools.commands.ListExtensions; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java index 7f6d0088b0c71..9040116d857b8 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java @@ -3,7 +3,6 @@ import java.nio.file.Path; import io.quarkus.cli.Version; -import io.quarkus.cli.create.TargetQuarkusVersionGroup; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; @@ -32,6 +31,12 @@ ExtensionCatalog getExtensionCatalog(TargetQuarkusVersionGroup targetVersion, Ou log.debug("Resolving Quarkus extension catalog for " + targetVersion); QuarkusProjectHelper.setMessageWriter(log); + if (targetVersion.isStreamSpecified() && !enableRegistryClient) { + throw new UnsupportedOperationException( + "Specifying a stream (--stream) requires the registry client to resolve resources. " + + "Please try again with the registry client enabled (--registry-client)"); + } + if (targetVersion.isPlatformSpecified()) { ArtifactCoords coords = targetVersion.getPlatformBom(); return ToolsUtils.resolvePlatformDescriptorDirectly(coords.getGroupId(), coords.getArtifactId(), diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java similarity index 98% rename from devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java rename to devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java index 1e1617daeab38..cfb99a5dc9764 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/TargetQuarkusVersionGroup.java @@ -1,4 +1,4 @@ -package io.quarkus.cli.create; +package io.quarkus.cli.common; import io.quarkus.maven.ArtifactCoords; import io.quarkus.maven.StreamCoords; @@ -31,7 +31,7 @@ void setStream(String stream) { } } - @CommandLine.Option(paramLabel = "groupId:artifactId:version", names = { "-p", + @CommandLine.Option(paramLabel = "groupId:artifactId:version", names = { "-P", "--platform-bom" }, description = "A specific Quarkus platform BOM, for example:%n io.quarkus:quarkus-bom:1.13.4.Final") void setPlatformBom(String bom) { bom = bom.trim(); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java index f7af45abcbf41..c7d0377b8ac5e 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java @@ -10,6 +10,7 @@ import io.quarkus.cli.common.OutputOptionMixin; import io.quarkus.cli.common.RegistryClientMixin; +import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.devtools.commands.CreateProject; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.project.BuildTool; diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java index 2c440393f2ced..100136eae4cfe 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java @@ -44,7 +44,7 @@ public void testListOutsideOfProject() throws Exception { @Test public void testListPlatformExtensions() throws Exception { // List extensions of a specified platform version - CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "list", "-p=io.quarkus:quarkus-bom:2.0.0.CR3", "-e"); + CliDriver.Result result = CliDriver.execute(workspaceRoot, "ext", "list", "-P=io.quarkus:quarkus-bom:2.0.0.CR3", "-e"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("Jackson"), From 8374ade42ddc52bb9c75b04b8c60fa70fb9b5d05 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Fri, 18 Jun 2021 16:28:21 -0400 Subject: [PATCH 14/24] consistent message emoji (cherry picked from commit fc4cbe71380dfa7154df6dcf204bc523c03e28be) --- docs/src/main/asciidoc/cli-tooling.adoc | 12 ++++----- .../devtools/codestarts/CodestartType.java | 26 +++++-------------- .../devtools/messagewriter/MessageIcons.java | 24 +++++++++++++---- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/docs/src/main/asciidoc/cli-tooling.adoc b/docs/src/main/asciidoc/cli-tooling.adoc index 91938e051b7d6..b6e43a946d470 100644 --- a/docs/src/main/asciidoc/cli-tooling.adoc +++ b/docs/src/main/asciidoc/cli-tooling.adoc @@ -95,7 +95,7 @@ applying codestarts... 📝 config-properties 🔧 dockerfiles 🔧 maven-wrapper -📄 resteasy-codestart +🚀 resteasy-codestart ----------- [SUCCESS] ✅ quarkus project has been successfully generated in: @@ -123,7 +123,7 @@ applying codestarts... 📝 config-properties 🔧 dockerfiles 🔧 maven-wrapper -📄 resteasy-codestart +🚀 resteasy-codestart ----------- [SUCCESS] ✅ quarkus project has been successfully generated in: @@ -225,16 +225,16 @@ $ quarkus build [INFO] Total time: 8.331 s [INFO] Finished at: 2021-05-27T10:13:28-04:00 [INFO] ------------------------------------------------------------------------ ---- +---- -Note: Output will vary depending on the build tool your project is using (maven, gradle, or jbang). +Note: Output will vary depending on the build tool your project is using (`maven`, `gradle`, or `jbang`). == Development mode [source,shell] ---- $ quarkus dev --help ---- +---- To start dev mode from the Quarkus CLI do: @@ -261,6 +261,6 @@ __ ____ __ _____ ___ __ ____ ______ Tests paused, press [r] to resume ---- -Note: Output will vary depending on the build tool your project is using (maven, gradle, or jbang). +Note: Output will vary depending on the build tool your project is using (`maven`, `gradle`, or `jbang`). diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartType.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartType.java index bb0bd22f6544a..3da26f89dd980 100644 --- a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartType.java +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartType.java @@ -1,14 +1,15 @@ package io.quarkus.devtools.codestarts; +import io.quarkus.devtools.messagewriter.MessageIcons; import io.smallrye.common.os.OS; public enum CodestartType { - LANGUAGE(true, 1, toEmoji("U+1F4DA")), - BUILDTOOL(true, 2, toEmoji("U+1F528")), - PROJECT(true, 3, toEmoji("U+1F4E6")), - CONFIG(true, 4, toEmoji("U+1F4DD")), - TOOLING(false, 5, toEmoji("U+1F527")), - CODE(false, 6, toEmoji("U+1F4C4")), + LANGUAGE(true, 1, MessageIcons.toEmoji("U+1F4DA")), + BUILDTOOL(true, 2, MessageIcons.toEmoji("U+1F528")), + PROJECT(true, 3, MessageIcons.toEmoji("U+1F4E6")), + CONFIG(true, 4, MessageIcons.toEmoji("U+1F4DD")), + TOOLING(false, 5, MessageIcons.toEmoji("U+1F527")), + CODE(false, 6, MessageIcons.toEmoji("U+1F680")), ; private final boolean base; @@ -33,17 +34,4 @@ public int getProcessingOrder() { return processingOrder; } - // Simplify life so we can just understand what emojis we are using - static String toEmoji(String text) { - String[] codes = text.replace("U+", "0x").split(" "); - final StringBuilder stringBuilder = new StringBuilder(); - for (String code : codes) { - final Integer intCode = Integer.decode(code.trim()); - for (Character character : Character.toChars(intCode)) { - stringBuilder.append(character); - } - } - stringBuilder.append(' '); - return stringBuilder.toString(); - } } diff --git a/independent-projects/tools/message-writer/src/main/java/io/quarkus/devtools/messagewriter/MessageIcons.java b/independent-projects/tools/message-writer/src/main/java/io/quarkus/devtools/messagewriter/MessageIcons.java index e6c9f2e4cb563..7588d072039ae 100644 --- a/independent-projects/tools/message-writer/src/main/java/io/quarkus/devtools/messagewriter/MessageIcons.java +++ b/independent-projects/tools/message-writer/src/main/java/io/quarkus/devtools/messagewriter/MessageIcons.java @@ -4,11 +4,11 @@ public enum MessageIcons { - OK_ICON("\u2705", "[SUCCESS]"), - NOK_ICON("\u274c", "[FAILURE]"), - NOOP_ICON("\uD83D\uDC4D", ""), - WARN_ICON("\uD83D\uDD25", "[WARN]"), - ERROR_ICON("\u2757", "[ERROR]"); + OK_ICON(toEmoji("U+2705"), "[SUCCESS]"), + NOK_ICON(toEmoji("U+274C"), "[FAILURE]"), + NOOP_ICON(toEmoji("U+1F44D"), ""), + WARN_ICON(toEmoji("U+1F525"), "[WARN]"), + ERROR_ICON(toEmoji("U+2757"), "[ERROR]"); private String icon; private String messageCode; @@ -18,6 +18,20 @@ public enum MessageIcons { this.messageCode = messageCode; } + // Simplify life so we can just understand what emojis we are using + public static String toEmoji(String text) { + String[] codes = text.replace("U+", "0x").split(" "); + final StringBuilder stringBuilder = new StringBuilder(); + for (String code : codes) { + final Integer intCode = Integer.decode(code.trim()); + for (Character character : Character.toChars(intCode)) { + stringBuilder.append(character); + } + } + stringBuilder.append(' '); + return stringBuilder.toString(); + } + @Override public String toString() { return OS.WINDOWS.isCurrent() ? messageCode : String.format("%s %s", messageCode, icon); From 332d304e49300849fe2aa0a1eabefb3d9fe6be51 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 21 Jun 2021 11:04:39 +1000 Subject: [PATCH 15/24] Continuous testing fixes - Add extra 'compile error' display, so test compile errors don't overwrite results. - Add extra 'test results' display, so current results are always visible. - Fix multiple bugs with pausing/resuming tests - Change yellow to blue, as ANSI yellow is very close to ANSI green in some terminals. (cherry picked from commit 2c2784aa34449442a50d837decce9828d4c73b9f) --- .../deployment/dev/console/AeshConsole.java | 100 ++++++++++-------- .../dev/testing/TestConsoleHandler.java | 53 ++++++---- .../deployment/dev/testing/TestListener.java | 4 + .../deployment/dev/testing/TestRunner.java | 7 +- .../deployment/dev/testing/TestSupport.java | 2 +- .../io/quarkus/dev/console/BasicConsole.java | 20 ++++ .../io/quarkus/dev/console/InputHandler.java | 4 + .../quarkus/dev/console/QuarkusConsole.java | 28 ++++- 8 files changed, 145 insertions(+), 73 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/console/AeshConsole.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/console/AeshConsole.java index 561aab966edea..8e5304be7be9f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/console/AeshConsole.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/console/AeshConsole.java @@ -19,8 +19,7 @@ public class AeshConsole extends QuarkusConsole { private Size size; private Attributes attributes; - private String statusMessage; - private String promptMessage; + private String[] messages = new String[0]; private int totalStatusLines = 0; private int lastWriteCursorX; /** @@ -60,27 +59,6 @@ public void run() { }, "Console Shutdown Hoot")); } - private AeshConsole setStatusMessage(String statusMessage) { - synchronized (this) { - StringBuilder buffer = new StringBuilder(); - clearStatusMessages(buffer); - int newLines = countLines(statusMessage) + countLines(promptMessage); - if (statusMessage == null) { - if (promptMessage != null) { - newLines += 2; - } - } else if (promptMessage == null) { - newLines += 2; - } else { - newLines += 3; - } - this.statusMessage = statusMessage; - updatePromptOnChange(buffer, newLines); - } - deadlockSafeWrite(); - return this; - } - private void updatePromptOnChange(StringBuilder buffer, int newLines) { if (newLines > totalStatusLines) { StringBuilder nb = new StringBuilder(); @@ -106,29 +84,41 @@ public AeshInputHolder createHolder(InputHandler inputHandler) { } private AeshConsole setPromptMessage(String promptMessage) { + if (!inputSupport) { + return this; + } + setMessage(0, promptMessage); + return this; + } + + private AeshConsole setMessage(int position, String message) { synchronized (this) { - if (!inputSupport) { - return this; + if (messages.length <= position) { + String[] old = messages; + messages = new String[position + 1]; + System.arraycopy(old, 0, this.messages, 0, old.length); } + messages[position] = message; + int newLines = countTotalStatusLines(); StringBuilder buffer = new StringBuilder(); clearStatusMessages(buffer); - int newLines = countLines(statusMessage) + countLines(promptMessage); - if (statusMessage == null) { - if (promptMessage != null) { - newLines += 2; - } - } else if (promptMessage == null) { - newLines += 2; - } else { - newLines += 3; - } - this.promptMessage = promptMessage; updatePromptOnChange(buffer, newLines); } deadlockSafeWrite(); return this; } + private int countTotalStatusLines() { + int total = 0; + for (String i : messages) { + if (i != null) { + total++; + total += countLines(i); + } + } + return total == 0 ? total : total + 1; + } + private void end(Connection conn) { conn.setAttributes(attributes); StringBuilder sb = new StringBuilder(); @@ -227,15 +217,21 @@ private void printStatusAndPrompt(StringBuilder buffer) { bottomBlankSpace = 0; } buffer.append("\n--\n"); - if (statusMessage != null) { - buffer.append(statusMessage); - if (promptMessage != null) { - buffer.append("\n"); + for (int i = messages.length - 1; i >= 0; --i) { + String msg = messages[i]; + if (msg != null) { + buffer.append(msg); + if (i > 0) { + //if there is any more messages to print we add a newline + for (int j = 0; j < i; ++j) { + if (messages[j] != null) { + buffer.append("\n"); + break; + } + } + } } } - if (promptMessage != null) { - buffer.append(promptMessage); - } } private void clearStatusMessages(StringBuilder buffer) { @@ -352,13 +348,27 @@ protected AeshInputHolder(InputHandler handler) { @Override protected void setPromptMessage(String prompt) { - AeshConsole.this.setPromptMessage(prompt); + if (!inputSupport) { + return; + } + setMessage(0, prompt); + } + + @Override + protected void setResultsMessage(String results) { + setMessage(1, results); + } + + @Override + protected void setCompileErrorMessage(String results) { + setMessage(3, results); } @Override protected void setStatusMessage(String status) { - AeshConsole.this.setStatusMessage(status); + setMessage(2, status); } + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java index 8b38b9de93b66..207a02082db64 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java @@ -1,9 +1,9 @@ package io.quarkus.deployment.dev.testing; +import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.BLUE; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.GREEN; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.RED; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.RESET; -import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.YELLOW; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.helpOption; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.statusFooter; import static io.quarkus.deployment.dev.testing.TestConsoleHandler.MessageFormat.statusHeader; @@ -34,10 +34,12 @@ public class TestConsoleHandler implements TestListener { private static final Logger log = Logger.getLogger("io.quarkus.test"); - public static final String PAUSED_PROMPT = YELLOW + "Tests paused, press [r] to resume, [h] for more options>" + RESET; - public static final String FIRST_RUN_PROMPT = YELLOW + "Running Tests for the first time" + RESET; - public static final String RUNNING_PROMPT = "Press [r] to re-run, [v] to view full results, [p] to pause, [h] for more options>"; - public static final String ABORTED_PROMPT = "Test run aborted."; + public static final String PAUSED_PROMPT = "Tests paused, press [" + BLUE + "r" + RESET + "] to resume, [" + BLUE + "h" + + RESET + "] for more options>" + RESET; + public static final String FIRST_RUN_PROMPT = BLUE + "Running Tests for the first time" + RESET; + public static final String RUNNING_PROMPT = "Press [" + BLUE + "r" + RESET + "] to re-run, [" + BLUE + + "v" + RESET + "] to view full results, [" + BLUE + "p" + RESET + "] to pause, [" + BLUE + + "h" + RESET + "] for more options>"; final DevModeType devModeType; @@ -46,7 +48,7 @@ public class TestConsoleHandler implements TestListener { boolean currentlyFailing = false; volatile InputHandler.ConsoleStatus promptHandler; volatile TestController testController; - private String lastStatus; + private String lastResults; public TestConsoleHandler(DevModeType devModeType) { this.devModeType = devModeType; @@ -93,7 +95,7 @@ public void run() { } else { if (disabled) { if (k == 'r') { - promptHandler.setStatus(YELLOW + "Starting tests" + RESET); + promptHandler.setStatus(BLUE + "Starting tests" + RESET); TestSupport.instance().get().start(); } } else if (!firstRun) { @@ -158,10 +160,12 @@ public void testsEnabled() { disabled = false; if (firstRun) { promptHandler.setStatus(null); + promptHandler.setResults(null); promptHandler.setPrompt(FIRST_RUN_PROMPT); } else { promptHandler.setPrompt(RUNNING_PROMPT); - promptHandler.setStatus(lastStatus); + promptHandler.setResults(lastResults); + promptHandler.setStatus(null); } } @@ -170,11 +174,17 @@ public void testsDisabled() { disabled = true; promptHandler.setPrompt(PAUSED_PROMPT); promptHandler.setStatus(null); + promptHandler.setResults(null); } @Override public void testCompileFailed(String message) { - promptHandler.setStatus(message); + promptHandler.setCompileError(message); + } + + @Override + public void testCompileSucceeded() { + promptHandler.setCompileError(null); } @Override @@ -226,15 +236,15 @@ public void runComplete(TestRunResults results) { end = end + "."; } if (results.getTotalCount() == 0) { - lastStatus = YELLOW + "No tests found" + RESET; + lastResults = BLUE + "No tests found" + RESET; } else if (results.getFailedCount() == 0 && results.getPassedCount() == 0) { - lastStatus = String.format(YELLOW + "All %d tests were skipped" + RESET, results.getSkippedCount()); + lastResults = String.format(BLUE + "All %d tests were skipped" + RESET, results.getSkippedCount()); } else if (results.getCurrentFailing().isEmpty()) { if (currentlyFailing) { log.info(GREEN + "All tests are now passing" + RESET); } currentlyFailing = false; - lastStatus = String.format( + lastResults = String.format( GREEN + "All %d tests are passing (%d skipped), %d tests were run in %dms." + end + RESET, results.getPassedCount(), results.getSkippedCount(), @@ -252,15 +262,18 @@ public void runComplete(TestRunResults results) { } log.error( statusFooter(RED + results.getCurrentFailedCount() + " TESTS FAILED")); - lastStatus = String.format( - RED + "%d tests failed" + RESET + " (" + GREEN + "%d passing" + RESET + ", " + YELLOW + "%d skipped" - + RESET + "), %d tests were run in %dms." + end + RESET, + lastResults = String.format( + RED + "%d tests failed" + RESET + " (" + GREEN + "%d passing" + RESET + ", " + BLUE + "%d skipped" + + RESET + ")" + RED + ", %d tests were run in %dms." + end + RESET, results.getCurrentFailedCount(), results.getPassedCount(), results.getSkippedCount(), results.getCurrentTotalCount(), results.getTotalTime()); } //this will re-print when using the basic console - promptHandler.setPrompt(RUNNING_PROMPT); - promptHandler.setStatus(lastStatus); + if (!disabled) { + promptHandler.setPrompt(RUNNING_PROMPT); + promptHandler.setResults(lastResults); + promptHandler.setStatus(null); + } } @Override @@ -270,8 +283,6 @@ public void noTests(TestRunResults results) { @Override public void runAborted() { - promptHandler.setStatus(ABORTED_PROMPT); - promptHandler.setPrompt(RUNNING_PROMPT); firstRun = false; } @@ -296,7 +307,7 @@ static class MessageFormat { public static final String RED = "\u001B[91m"; public static final String GREEN = "\u001b[32m"; - public static final String YELLOW = "\u001b[33m"; + public static final String BLUE = "\u001b[34m"; public static final String RESET = "\u001b[0m"; private MessageFormat() { @@ -315,7 +326,7 @@ public static String toggleStatus(boolean enabled) { } public static String helpOption(String key, String description) { - return "[" + GREEN + key + RESET + "] - " + description; + return "[" + BLUE + key + RESET + "] - " + description; } public static String helpOption(String key, String description, boolean enabled) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestListener.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestListener.java index bfac67a31c795..f8f8540654a3f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestListener.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestListener.java @@ -34,4 +34,8 @@ default void setInstrumentationBasedReload(boolean ibr) { default void testCompileFailed(String message) { } + + default void testCompileSucceeded() { + + } } 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 3a8deabcdb244..d3b5ffb01f182 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 @@ -20,7 +20,6 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.PostDiscoveryFilter; -import org.opentest4j.TestAbortedException; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.deployment.dev.ClassScanResult; @@ -348,9 +347,6 @@ public void waitTillResumed() { throw new RuntimeException(e); } } - if (disabled) { - throw new TestAbortedException("Tests are disabled"); - } } } @@ -366,6 +362,9 @@ public void testCompileFailed(Throwable e) { public synchronized void testCompileSucceeded() { compileProblem = null; + for (TestListener i : testSupport.testListeners) { + i.testCompileSucceeded(); + } } public boolean isRunning() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index 84c5b9cca19d2..436150fe1b9c9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -98,10 +98,10 @@ public void start() { if (context.getApplicationRoot().getTest().isPresent()) { started = true; init(); + testRunner.enable(); for (TestListener i : testListeners) { i.testsEnabled(); } - testRunner.enable(); } } catch (Exception e) { log.error("Failed to create compiler, runtime compilation will be unavailable", e); diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java index 893d97dc77c5b..b9fb3feff3464 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/BasicConsole.java @@ -71,6 +71,26 @@ protected void setPromptMessage(String prompt) { } } + @Override + protected void setResultsMessage(String results) { + DISABLE_FILTER.set(true); + try { + statusLogger.info(results); + } finally { + DISABLE_FILTER.set(false); + } + } + + @Override + protected void setCompileErrorMessage(String results) { + DISABLE_FILTER.set(true); + try { + statusLogger.info(results); + } finally { + DISABLE_FILTER.set(false); + } + } + @Override protected void setStatusMessage(String status) { if (status == null) { diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/InputHandler.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/InputHandler.java index 191ae5cb72b46..e9d88a61f84f3 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/InputHandler.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/InputHandler.java @@ -10,5 +10,9 @@ interface ConsoleStatus { void setPrompt(String prompt); void setStatus(String status); + + void setResults(String results); + + void setCompileError(String compileError); } } diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java index eb22b834fa06d..93d07fd806546 100644 --- a/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/console/QuarkusConsole.java @@ -119,6 +119,8 @@ protected static abstract class InputHolder implements InputHandler.ConsoleStatu volatile boolean enabled; String prompt; String status; + String results; + String compileError; protected InputHolder(InputHandler handler) { this.handler = handler; @@ -129,6 +131,8 @@ public InputHolder setEnabled(boolean enabled) { if (enabled) { setStatus(status); setPrompt(prompt); + setResults(results); + setCompileError(compileError); } return this; } @@ -141,8 +145,6 @@ public void setPrompt(String prompt) { } } - protected abstract void setPromptMessage(String prompt); - @Override public void setStatus(String status) { this.status = status; @@ -151,6 +153,28 @@ public void setStatus(String status) { } } + @Override + public void setResults(String results) { + this.results = results; + if (enabled) { + setResultsMessage(results); + } + } + + @Override + public void setCompileError(String compileError) { + this.compileError = compileError; + if (enabled) { + setCompileErrorMessage(compileError); + } + } + protected abstract void setStatusMessage(String status); + + protected abstract void setPromptMessage(String prompt); + + protected abstract void setResultsMessage(String results); + + protected abstract void setCompileErrorMessage(String results); } } From 37d5f8447f6d3fa9e27965ee4aee9a2981d4d6dd Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 18 Jun 2021 11:35:22 +0300 Subject: [PATCH 16/24] Ensure @QuarkusTest works with @Nested and test lifecycle methods Fixes: #17975 (cherry picked from commit 4a612833d76fe96404801cfbd85571276782b0fa) --- .../java/io/quarkus/it/main/JaxbTestCase.java | 18 +++++ .../test/junit/QuarkusTestExtension.java | 70 ++++++++++++------- 2 files changed, 63 insertions(+), 25 deletions(-) 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 4e23e989055e7..62110d4baeb4a 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,6 +2,10 @@ 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; @@ -11,12 +15,26 @@ @QuarkusTest public class JaxbTestCase { + private static final AtomicInteger count = new AtomicInteger(0); + + @BeforeEach + public void beforeInEnclosing() { + count.incrementAndGet(); + } + @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/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 4122ebbad410d..1bae13a1216b3 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 @@ -919,34 +919,19 @@ public void interceptAfterAllMethod(Invocation invocation, ReflectiveInvoc private Object runExtensionMethod(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { resetHangTimeout(); - Method newMethod = null; ClassLoader old = setCCL(runningQuarkusApplication.getClassLoader()); try { - Class c = Class.forName(extensionContext.getRequiredTestClass().getName(), true, + Class testClassFromTCCL = Class.forName(extensionContext.getRequiredTestClass().getName(), true, Thread.currentThread().getContextClassLoader()); - while (c != Object.class) { - if (c.getName().equals(invocationContext.getExecutable().getDeclaringClass().getName())) { - try { - Class[] originalParameterTypes = invocationContext.getExecutable().getParameterTypes(); - List> parameterTypesFromTccl = new ArrayList<>(originalParameterTypes.length); - for (Class type : originalParameterTypes) { - if (type.isPrimitive()) { - parameterTypesFromTccl.add(type); - } else { - parameterTypesFromTccl - .add(Class.forName(type.getName(), true, - Thread.currentThread().getContextClassLoader())); - } - } - newMethod = c.getDeclaredMethod(invocationContext.getExecutable().getName(), - parameterTypesFromTccl.toArray(new Class[0])); - break; - } catch (NoSuchMethodException ignored) { - - } - } - c = c.getSuperclass(); + Method newMethod = determineTCCLExtensionMethod(invocationContext, testClassFromTCCL); + boolean methodFromEnclosing = false; + // this is needed to support before*** and after*** methods that are part of class that encloses the test class + // (the test class is in this case a @Nested test) + if ((newMethod == null) && (testClassFromTCCL.getEnclosingClass() != null)) { + testClassFromTCCL = testClassFromTCCL.getEnclosingClass(); + newMethod = determineTCCLExtensionMethod(invocationContext, testClassFromTCCL); + methodFromEnclosing = true; } if (newMethod == null) { throw new RuntimeException("Could not find method " + invocationContext.getExecutable() + " on test class"); @@ -990,7 +975,13 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation } } - return newMethod.invoke(actualTestInstance, argumentsFromTccl.toArray(new Object[0])); + Object effectiveTestInstance = actualTestInstance; + if (methodFromEnclosing) { + // TODO: this is a little dodgy, ideally we would need to use the same constructor that was used for the original object + // but it's unlikely(?) we will run into this combo + effectiveTestInstance = testClassFromTCCL.getConstructor().newInstance(); + } + return newMethod.invoke(effectiveTestInstance, argumentsFromTccl.toArray(new Object[0])); } catch (InvocationTargetException e) { throw e.getCause(); } catch (IllegalAccessException | ClassNotFoundException e) { @@ -1000,6 +991,35 @@ private Object runExtensionMethod(ReflectiveInvocationContext invocation } } + private Method determineTCCLExtensionMethod(ReflectiveInvocationContext invocationContext, Class c) + throws ClassNotFoundException { + Method newMethod = null; + while (c != Object.class) { + if (c.getName().equals(invocationContext.getExecutable().getDeclaringClass().getName())) { + try { + Class[] originalParameterTypes = invocationContext.getExecutable().getParameterTypes(); + List> parameterTypesFromTccl = new ArrayList<>(originalParameterTypes.length); + for (Class type : originalParameterTypes) { + if (type.isPrimitive()) { + parameterTypesFromTccl.add(type); + } else { + parameterTypesFromTccl + .add(Class.forName(type.getName(), true, + Thread.currentThread().getContextClassLoader())); + } + } + newMethod = c.getDeclaredMethod(invocationContext.getExecutable().getName(), + parameterTypesFromTccl.toArray(new Class[0])); + break; + } catch (NoSuchMethodException ignored) { + + } + } + c = c.getSuperclass(); + } + return newMethod; + } + @Override public void afterAll(ExtensionContext context) throws Exception { resetHangTimeout(); From 0397af8c5490ba450a0b4212d4a2441c67428140 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 22 Jun 2021 08:11:35 +0200 Subject: [PATCH 17/24] Scheduler - fix a regression in validation - if a scheduled method has a config-based value in every() or cron() the rest of the values is not validated at build time (cherry picked from commit c38991f938bddc46791253a55b5b2c3a0a49c37f) --- .../deployment/SchedulerProcessor.java | 62 +++++++++---------- .../test/InvalidDelayedExpressionTest.java | 4 +- 2 files changed, 31 insertions(+), 35 deletions(-) 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 ad34c15f83748..10d9c0a7a5005 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 @@ -331,33 +331,30 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu AnnotationValue everyValue = schedule.value("every"); if (cronValue != null && !cronValue.asString().trim().isEmpty()) { String cron = cronValue.asString().trim(); - if (SchedulerUtils.isConfigValue(cron)) { - // Don't validate config property - return null; - } - try { - parser.parse(cron).validate(); - } catch (IllegalArgumentException e) { - return new IllegalStateException("Invalid cron() expression on: " + schedule, e); - } - if (everyValue != null && !everyValue.asString().trim().isEmpty()) { - LOGGER.warnf( - "%s declared on %s#%s() defines both cron() and every() - the cron expression takes precedence", - schedule, method.declaringClass().name(), method.name()); + if (!SchedulerUtils.isConfigValue(cron)) { + try { + parser.parse(cron).validate(); + } catch (IllegalArgumentException e) { + return new IllegalStateException("Invalid cron() expression on: " + schedule, e); + } + if (everyValue != null && !everyValue.asString().trim().isEmpty()) { + LOGGER.warnf( + "%s declared on %s#%s() defines both cron() and every() - the cron expression takes precedence", + schedule, method.declaringClass().name(), method.name()); + } } } else { if (everyValue != null && !everyValue.asString().trim().isEmpty()) { String every = everyValue.asString().trim(); - if (SchedulerUtils.isConfigValue(every)) { - return null; - } - if (Character.isDigit(every.charAt(0))) { - every = "PT" + every; - } - try { - Duration.parse(every); - } catch (Exception e) { - return new IllegalStateException("Invalid every() expression on: " + schedule, e); + if (!SchedulerUtils.isConfigValue(every)) { + if (Character.isDigit(every.charAt(0))) { + every = "PT" + every; + } + try { + Duration.parse(every); + } catch (Exception e) { + return new IllegalStateException("Invalid every() expression on: " + schedule, e); + } } } else { return new IllegalStateException("@Scheduled must declare either cron() or every(): " + schedule); @@ -368,16 +365,15 @@ private Throwable validateScheduled(CronParser parser, AnnotationInstance schedu if (delay == null || delay.asLong() <= 0) { if (delayedValue != null && !delayedValue.asString().trim().isEmpty()) { String delayed = delayedValue.asString().trim(); - if (SchedulerUtils.isConfigValue(delayed)) { - return null; - } - if (Character.isDigit(delayed.charAt(0))) { - delayed = "PT" + delayed; - } - try { - Duration.parse(delayed); - } catch (Exception e) { - return new IllegalStateException("Invalid delayed() expression on: " + schedule, e); + if (!SchedulerUtils.isConfigValue(delayed)) { + if (Character.isDigit(delayed.charAt(0))) { + delayed = "PT" + delayed; + } + try { + Duration.parse(delayed); + } catch (Exception e) { + return new IllegalStateException("Invalid delayed() expression on: " + schedule, e); + } } } } else { diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/InvalidDelayedExpressionTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/InvalidDelayedExpressionTest.java index 2cf49f2e77325..e42bfb30cc65a 100644 --- a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/InvalidDelayedExpressionTest.java +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/InvalidDelayedExpressionTest.java @@ -16,7 +16,7 @@ public class InvalidDelayedExpressionTest { static final QuarkusUnitTest test = new QuarkusUnitTest() .setExpectedException(DeploymentException.class) .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(InvalidDelayedExpressionTest.InvalidBean.class)); + .addClasses(InvalidBean.class)); @Test public void test() throws InterruptedException { @@ -24,7 +24,7 @@ public void test() throws InterruptedException { static class InvalidBean { - @Scheduled(delayed = "for 10 seconds") + @Scheduled(every = "${my.every.expr:off}", delayed = "for 10 seconds") void wrong() { } From fd5f4900efe6609e86d7e17dd6c4c033a3688c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 22 Jun 2021 09:29:47 +0200 Subject: [PATCH 18/24] Register the fields of the ChangeStreamDocument for reflection Fixes #17878 (cherry picked from commit 732b2a781b1659715905b9c2121be2a6f050d84f) --- .../quarkus/mongodb/deployment/MongoClientProcessor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java index 8e39a69b41746..3eb4fd9546eb4 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java @@ -135,11 +135,13 @@ List addExtensionPointsToNative(CodecProviderBuildItem reflectiveClassNames.addAll(propertyCodecProviders.getPropertyCodecProviderClassNames()); reflectiveClassNames.addAll(bsonDiscriminators.getBsonDiscriminatorClassNames()); reflectiveClassNames.addAll(commandListeners.getCommandListenerClassNames()); - reflectiveClassNames.add(ChangeStreamDocument.class.getName()); - return reflectiveClassNames.stream() + List reflectiveClass = reflectiveClassNames.stream() .map(s -> new ReflectiveClassBuildItem(true, true, false, s)) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(() -> new ArrayList<>())); + // ChangeStreamDocument needs to be registered for reflection with its fields. + reflectiveClass.add(new ReflectiveClassBuildItem(true, true, true, ChangeStreamDocument.class.getName())); + return reflectiveClass; } @BuildStep From 6b02a4cd42ec5f175b46be22baa5f218fbae7953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Wed, 16 Jun 2021 11:51:44 +0200 Subject: [PATCH 19/24] gRPC: fix request context propagation (cherry picked from commit 8b9c8b2107a2aa3c2394779d940f7d9e5175ef58) --- .../quarkus/grpc/deployment/GrpcDotNames.java | 2 - .../grpc/deployment/GrpcServerProcessor.java | 18 +-- .../grpc/runtime/GrpcServerRecorder.java | 2 +- .../runtime/health/GrpcHealthEndpoint.java | 4 +- .../grpc/runtime/supports/Channels.java | 4 +- .../blocking/BlockingExecutionHandler.java | 48 +++++++ .../BlockingServerInterceptor.java | 112 +++++++-------- .../DevModeBlockingExecutionHandler.java | 26 ++++ .../context/GrpcEnableRequestContext.java | 16 --- .../GrpcRequestContextCdiInterceptor.java | 42 ------ .../GrpcRequestContextGrpcInterceptor.java | 130 ++++++++++++----- .../context/GrpcRequestContextHolder.java | 21 --- .../BlockingServerInterceptorTest.java | 20 ++- .../grpc-hibernate-reactive/pom.xml | 135 ++++++++++++++++++ .../com/example/reactive/ContextChecker.java | 29 ++++ .../main/java/com/example/reactive/Item.java | 10 ++ .../com/example/reactive/ReactiveService.java | 62 ++++++++ .../example/reactive/RequestScopeBean.java | 21 +++ .../src/main/proto/test.proto | 18 +++ .../src/main/resources/application.properties | 1 + .../example/reactive/ReactiveServiceTest.java | 49 +++++++ integration-tests/grpc-hibernate/pom.xml | 113 +++++++++++++++ .../grpc/hibernate/ContextChecker.java | 29 ++++ .../java/com/example/grpc/hibernate/Item.java | 14 ++ .../com/example/grpc/hibernate/ItemDao.java | 18 +++ .../grpc/hibernate/RawTestService.java | 112 +++++++++++++++ .../grpc/hibernate/RequestScopeBean.java | 21 +++ .../example/grpc/hibernate/TestService.java | 78 ++++++++++ .../grpc-hibernate/src/main/proto/test.proto | 25 ++++ .../src/main/resources/application.properties | 5 + .../grpc/hibernate/BlockingMutinyTest.java | 95 ++++++++++++ .../grpc/hibernate/BlockingRawTest.java | 99 +++++++++++++ .../example/grpc/hibernate/TestResources.java | 8 ++ integration-tests/pom.xml | 2 + 34 files changed, 1185 insertions(+), 204 deletions(-) create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingExecutionHandler.java rename extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/{ => blocking}/BlockingServerInterceptor.java (63%) create mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/DevModeBlockingExecutionHandler.java delete mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcEnableRequestContext.java delete mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextCdiInterceptor.java delete mode 100644 extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextHolder.java create mode 100644 integration-tests/grpc-hibernate-reactive/pom.xml create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ContextChecker.java create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/Item.java create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ReactiveService.java create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/RequestScopeBean.java create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/proto/test.proto create mode 100644 integration-tests/grpc-hibernate-reactive/src/main/resources/application.properties create mode 100644 integration-tests/grpc-hibernate-reactive/src/test/java/com/example/reactive/ReactiveServiceTest.java create mode 100644 integration-tests/grpc-hibernate/pom.xml create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ContextChecker.java create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/Item.java create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ItemDao.java create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RawTestService.java create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RequestScopeBean.java create mode 100644 integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/TestService.java create mode 100644 integration-tests/grpc-hibernate/src/main/proto/test.proto create mode 100644 integration-tests/grpc-hibernate/src/main/resources/application.properties create mode 100644 integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingMutinyTest.java create mode 100644 integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingRawTest.java create mode 100644 integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/TestResources.java diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcDotNames.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcDotNames.java index fcb50ee773935..a9c69da3eac72 100644 --- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcDotNames.java +++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcDotNames.java @@ -18,7 +18,6 @@ import io.quarkus.grpc.runtime.MutinyStub; import io.quarkus.grpc.runtime.supports.Channels; import io.quarkus.grpc.runtime.supports.GrpcClientConfigProvider; -import io.quarkus.grpc.runtime.supports.context.GrpcEnableRequestContext; import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; @@ -28,7 +27,6 @@ public class GrpcDotNames { public static final DotName CHANNEL = DotName.createSimple(Channel.class.getName()); public static final DotName GRPC_CLIENT = DotName.createSimple(GrpcClient.class.getName()); public static final DotName GRPC_SERVICE = DotName.createSimple(GrpcService.class.getName()); - public static final DotName GRPC_ENABLE_REQUEST_CONTEXT = DotName.createSimple(GrpcEnableRequestContext.class.getName()); public static final DotName BLOCKING = DotName.createSimple(Blocking.class.getName()); public static final DotName NON_BLOCKING = DotName.createSimple(NonBlocking.class.getName()); 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 bd2b6a848dcc3..0b9ed47add0fc 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 @@ -34,7 +34,6 @@ import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuiltinScope; -import io.quarkus.arc.processor.Transformation; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; @@ -60,8 +59,6 @@ import io.quarkus.grpc.runtime.config.GrpcServerBuildTimeConfig; import io.quarkus.grpc.runtime.health.GrpcHealthEndpoint; import io.quarkus.grpc.runtime.health.GrpcHealthStorage; -import io.quarkus.grpc.runtime.supports.context.GrpcEnableRequestContext; -import io.quarkus.grpc.runtime.supports.context.GrpcRequestContextCdiInterceptor; import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; import io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem; import io.quarkus.runtime.LaunchMode; @@ -240,14 +237,11 @@ public boolean appliesTo(Kind kind) { @Override public void transform(TransformationContext context) { ClassInfo clazz = context.getTarget().asClass(); - if (userDefinedServices.contains(clazz.name())) { - // Add @GrpcEnableRequestContext to activate the request context during each call - Transformation transform = context.transform().add(GrpcDotNames.GRPC_ENABLE_REQUEST_CONTEXT); - if (!customScopes.isScopeDeclaredOn(clazz)) { - // Add @Singleton to make it a bean - transform.add(BuiltinScope.SINGLETON.getName()); - } - transform.done(); + if (userDefinedServices.contains(clazz.name()) && !customScopes.isScopeDeclaredOn(clazz)) { + // Add @Singleton to make it a bean + context.transform() + .add(BuiltinScope.SINGLETON.getName()) + .done(); } } }); @@ -303,8 +297,6 @@ void registerBeans(BuildProducer beans, List bindables, BuildProducer features) { // @GrpcService is a CDI qualifier beans.produce(new AdditionalBeanBuildItem(GrpcService.class)); - beans.produce(new AdditionalBeanBuildItem(GrpcRequestContextCdiInterceptor.class)); - beans.produce(new AdditionalBeanBuildItem(GrpcEnableRequestContext.class)); if (!bindables.isEmpty() || LaunchMode.current() == LaunchMode.DEVELOPMENT) { beans.produce(AdditionalBeanBuildItem.unremovableOf(GrpcContainer.class)); diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java index d9fa84bcc36de..d7ed2c0dd396f 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcServerRecorder.java @@ -41,8 +41,8 @@ import io.quarkus.grpc.runtime.devmode.GrpcServerReloader; import io.quarkus.grpc.runtime.health.GrpcHealthStorage; import io.quarkus.grpc.runtime.reflection.ReflectionService; -import io.quarkus.grpc.runtime.supports.BlockingServerInterceptor; import io.quarkus.grpc.runtime.supports.CompressionInterceptor; +import io.quarkus.grpc.runtime.supports.blocking.BlockingServerInterceptor; import io.quarkus.grpc.runtime.supports.context.GrpcRequestContextGrpcInterceptor; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/health/GrpcHealthEndpoint.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/health/GrpcHealthEndpoint.java index 3a22108856d73..f76df3363ebb7 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/health/GrpcHealthEndpoint.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/health/GrpcHealthEndpoint.java @@ -10,14 +10,12 @@ import grpc.health.v1.HealthOuterClass.HealthCheckResponse.ServingStatus; import grpc.health.v1.MutinyHealthGrpc; import io.quarkus.grpc.GrpcService; -import io.quarkus.grpc.runtime.supports.context.GrpcEnableRequestContext; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; -// Note that we need to add the scope and interceptor binding explicitly because this class is not part of the index +// Note that we need to add the scope explicitly because this class is not part of the index @Singleton -@GrpcEnableRequestContext @GrpcService public class GrpcHealthEndpoint extends MutinyHealthGrpc.HealthImplBase { diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java index 853da64e73cb0..c8e3eaa77346c 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java @@ -59,6 +59,9 @@ public static Channel createChannel(String name) throws SSLException { GrpcClientConfiguration config = configProvider.getConfiguration(name); if (config == null && LaunchMode.current() == LaunchMode.TEST) { + LOGGER.infof( + "gRPC client %s created without configuration. We are assuming that it's created to test your gRPC services.", + name); config = testConfig(configProvider.getServerConfiguration()); } @@ -164,7 +167,6 @@ public static Channel createChannel(String name) throws SSLException { } private static GrpcClientConfiguration testConfig(GrpcServerConfiguration serverConfiguration) { - LOGGER.info("gRPC client created without configuration. We are assuming that it's created to test your gRPC services."); GrpcClientConfiguration config = new GrpcClientConfiguration(); config.port = serverConfiguration.testPort; config.host = serverConfiguration.host; diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingExecutionHandler.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingExecutionHandler.java new file mode 100644 index 0000000000000..7b92ac0f6d5c6 --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingExecutionHandler.java @@ -0,0 +1,48 @@ +package io.quarkus.grpc.runtime.supports.blocking; + +import java.util.function.Consumer; + +import io.grpc.Context; +import io.grpc.ServerCall; +import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.ManagedContext; +import io.vertx.core.Handler; +import io.vertx.core.Promise; + +class BlockingExecutionHandler implements Handler> { + private final ServerCall.Listener delegate; + private final Context grpcContext; + private final Consumer> consumer; + private final InjectableContext.ContextState state; + private final ManagedContext requestContext; + + public BlockingExecutionHandler(Consumer> consumer, Context grpcContext, + ServerCall.Listener delegate, InjectableContext.ContextState state, + ManagedContext requestContext) { + this.consumer = consumer; + this.grpcContext = grpcContext; + this.delegate = delegate; + this.state = state; + this.requestContext = requestContext; + } + + @Override + public void handle(Promise event) { + final Context previous = Context.current(); + grpcContext.attach(); + try { + requestContext.activate(state); + try { + consumer.accept(delegate); + } catch (Throwable any) { + event.fail(any); + return; + } finally { + requestContext.deactivate(); + } + event.complete(); + } finally { + grpcContext.detach(previous); + } + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptor.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingServerInterceptor.java similarity index 63% rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptor.java rename to extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingServerInterceptor.java index 47c68974b8a04..01d6784800e23 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptor.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/BlockingServerInterceptor.java @@ -1,8 +1,7 @@ -package io.quarkus.grpc.runtime.supports; +package io.quarkus.grpc.runtime.supports.blocking; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -13,12 +12,15 @@ import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableContext.ContextState; +import io.quarkus.arc.ManagedContext; import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.Vertx; /** - * gRPC Server interceptor offloading the execution of the gRPC method on a wroker thread if the method is annotated + * gRPC Server interceptor offloading the execution of the gRPC method on a worker thread if the method is annotated * with {@link io.smallrye.common.annotation.Blocking}. * * For non-annotated methods, the interceptor acts as a pass-through. @@ -62,13 +64,23 @@ public ServerCall.Listener interceptCall(ServerCall replay = new ReplayListener<>(); - + final ManagedContext requestContext = getRequestContext(); + // context should always be active here + // it is initialized by io.quarkus.grpc.runtime.supports.context.GrpcRequestContextGrpcInterceptor + // that should always be called before this interceptor + ContextState state = requestContext.getState(); + ReplayListener replay = new ReplayListener<>(state); vertx.executeBlocking(new Handler>() { @Override public void handle(Promise f) { - ServerCall.Listener listener = next.startCall(call, headers); - replay.setDelegate(listener); + ServerCall.Listener listener; + try { + requestContext.activate(state); + listener = next.startCall(call, headers); + } finally { + requestContext.deactivate(); + } + replay.setDelegate(listener, requestContext); f.complete(null); } }, null); @@ -87,30 +99,46 @@ public void handle(Promise f) { */ private class ReplayListener extends ServerCall.Listener { private ServerCall.Listener delegate; - private final List>> incomingEvents = new LinkedList<>(); + private final List>> incomingEvents = new ArrayList<>(); + private final ContextState requestContextState; + + private ReplayListener(ContextState requestContextState) { + this.requestContextState = requestContextState; + } - synchronized void setDelegate(ServerCall.Listener delegate) { + synchronized void setDelegate(ServerCall.Listener delegate, + ManagedContext requestContext) { this.delegate = delegate; - for (Consumer> event : incomingEvents) { - event.accept(delegate); + requestContext.activate(requestContextState); + try { + for (Consumer> event : incomingEvents) { + event.accept(delegate); + } + } finally { + requestContext.deactivate(); } incomingEvents.clear(); } private synchronized void executeOnContextOrEnqueue(Consumer> consumer) { if (this.delegate != null) { - final Context grpcContext = Context.current(); - Handler> blockingHandler = new BlockingExecutionHandler<>(consumer, grpcContext, delegate); - if (devMode) { - blockingHandler = new DevModeBlockingExecutionHandler(Thread.currentThread().getContextClassLoader(), - blockingHandler); - } - vertx.executeBlocking(blockingHandler, true, null); + executeBlockingWithRequestContext(consumer); } else { incomingEvents.add(consumer); } } + private void executeBlockingWithRequestContext(Consumer> consumer) { + final Context grpcContext = Context.current(); + Handler> blockingHandler = new BlockingExecutionHandler<>(consumer, grpcContext, delegate, + requestContextState, getRequestContext()); + if (devMode) { + blockingHandler = new DevModeBlockingExecutionHandler(Thread.currentThread().getContextClassLoader(), + blockingHandler); + } + vertx.executeBlocking(blockingHandler, true, null); + } + @Override public void onMessage(ReqT message) { executeOnContextOrEnqueue(new Consumer>() { @@ -142,50 +170,8 @@ public void onReady() { } } - private static class DevModeBlockingExecutionHandler implements Handler> { - - final ClassLoader tccl; - final Handler> delegate; - - public DevModeBlockingExecutionHandler(ClassLoader tccl, Handler> delegate) { - this.tccl = tccl; - this.delegate = delegate; - } - - @Override - public void handle(Promise event) { - ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(tccl); - try { - delegate.handle(event); - } finally { - Thread.currentThread().setContextClassLoader(originalTccl); - } - } - } - - private static class BlockingExecutionHandler implements Handler> { - private final ServerCall.Listener delegate; - private final Context grpcContext; - private final Consumer> consumer; - - public BlockingExecutionHandler(Consumer> consumer, Context grpcContext, - ServerCall.Listener delegate) { - this.consumer = consumer; - this.grpcContext = grpcContext; - this.delegate = delegate; - } - - @Override - public void handle(Promise event) { - final Context previous = Context.current(); - grpcContext.attach(); - try { - consumer.accept(delegate); - event.complete(); - } finally { - grpcContext.detach(previous); - } - } + // protected for tests + protected ManagedContext getRequestContext() { + return Arc.container().requestContext(); } } diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/DevModeBlockingExecutionHandler.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/DevModeBlockingExecutionHandler.java new file mode 100644 index 0000000000000..b6b08ab3100d4 --- /dev/null +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/blocking/DevModeBlockingExecutionHandler.java @@ -0,0 +1,26 @@ +package io.quarkus.grpc.runtime.supports.blocking; + +import io.vertx.core.Handler; +import io.vertx.core.Promise; + +class DevModeBlockingExecutionHandler implements Handler> { + + final ClassLoader tccl; + final Handler> delegate; + + public DevModeBlockingExecutionHandler(ClassLoader tccl, Handler> delegate) { + this.tccl = tccl; + this.delegate = delegate; + } + + @Override + public void handle(Promise event) { + ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(tccl); + try { + delegate.handle(event); + } finally { + Thread.currentThread().setContextClassLoader(originalTccl); + } + } +} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcEnableRequestContext.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcEnableRequestContext.java deleted file mode 100644 index b304025ae982b..0000000000000 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcEnableRequestContext.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.grpc.runtime.supports.context; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import javax.interceptor.InterceptorBinding; - -@Inherited -@InterceptorBinding -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface GrpcEnableRequestContext { -} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextCdiInterceptor.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextCdiInterceptor.java deleted file mode 100644 index 52915fd4d7ef9..0000000000000 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextCdiInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.quarkus.grpc.runtime.supports.context; - -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; - -import io.quarkus.arc.Arc; -import io.quarkus.arc.ManagedContext; -import io.vertx.core.Context; -import io.vertx.core.Vertx; - -@Interceptor -@GrpcEnableRequestContext -@Priority(Interceptor.Priority.PLATFORM_BEFORE) -public class GrpcRequestContextCdiInterceptor { - - @AroundInvoke - public Object cleanUpContext(InvocationContext invocationContext) throws Exception { - boolean cleanUp = false; - ManagedContext requestContext = Arc.container().requestContext(); - if (!requestContext.isActive()) { - Context context = Vertx.currentContext(); - - if (context != null) { - cleanUp = true; - requestContext.activate(); - GrpcRequestContextHolder contextHolder = GrpcRequestContextHolder.get(context); - if (contextHolder != null) { - contextHolder.state = requestContext.getState(); - } - } - } - try { - return invocationContext.proceed(); - } finally { - if (cleanUp) { - requestContext.deactivate(); - } - } - } -} diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextGrpcInterceptor.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextGrpcInterceptor.java index 31a408e11327d..7841ae58a2899 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextGrpcInterceptor.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextGrpcInterceptor.java @@ -1,23 +1,22 @@ package io.quarkus.grpc.runtime.supports.context; -import org.jboss.logmanager.Logger; +import org.jboss.logging.Logger; -import io.grpc.ForwardingServerCall; import io.grpc.ForwardingServerCallListener; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; -import io.grpc.Status; import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableContext; import io.quarkus.arc.ManagedContext; import io.vertx.core.Context; import io.vertx.core.Vertx; public class GrpcRequestContextGrpcInterceptor implements ServerInterceptor { + private static final Logger log = Logger.getLogger(GrpcRequestContextGrpcInterceptor.class.getName()); private final ManagedContext reqContext; - private static final Logger LOGGER = Logger.getLogger(GrpcRequestContextGrpcInterceptor.class.getName()); public GrpcRequestContextGrpcInterceptor() { reqContext = Arc.container().requestContext(); @@ -31,50 +30,105 @@ public ServerCall.Listener interceptCall(ServerCall delegate = next - .startCall(new ForwardingServerCall.SimpleForwardingServerCall(call) { - - @Override - public void close(Status status, Metadata trailers) { - super.close(status, trailers); - if (contextHolder.state != null) { - reqContext.destroy(contextHolder.state); - } - } - }, headers); + InjectableContext.ContextState state; + if (!reqContext.isActive()) { + reqContext.activate(); + state = reqContext.getState(); + } else { + state = null; + log.warn("Request context already active when gRPC request started"); + } // a gRPC service can return a StreamObserver and instead of doing the work // directly in the method body, do stuff that requires a request context in StreamObserver's methods // let's propagate the request context to these methods: - return new ForwardingServerCallListener.SimpleForwardingServerCallListener(delegate) { + try { + return new ForwardingServerCallListener.SimpleForwardingServerCallListener( + next.startCall(call, headers)) { - @Override - public void onMessage(ReqT message) { - activateContext(); - super.onMessage(message); - } + @Override + public void onMessage(ReqT message) { + boolean activated = activateContext(); + try { + super.onMessage(message); + } finally { + if (activated) { + deactivateContext(); + } + } + } - @Override - public void onReady() { - activateContext(); - super.onReady(); - } + @Override + public void onReady() { + boolean activated = activateContext(); + try { + super.onReady(); + } finally { + if (activated) { + deactivateContext(); + } + } + } - @Override - public void onComplete() { - activateContext(); - super.onComplete(); - } + @Override + public void onHalfClose() { + boolean activated = activateContext(); + try { + super.onHalfClose(); + } finally { + if (activated) { + deactivateContext(); + } + } + } - private void activateContext() { - if (contextHolder.state != null && !reqContext.isActive()) { - reqContext.activate(contextHolder.state); + @Override + public void onCancel() { + boolean activated = activateContext(); + try { + super.onCancel(); + } finally { + if (activated) { + deactivateContext(); + } + if (state != null) { + reqContext.destroy(state); + } + } + } + + @Override + public void onComplete() { + boolean activated = activateContext(); + try { + super.onComplete(); + } finally { + if (activated) { + deactivateContext(); + } + if (state != null) { + reqContext.destroy(state); + } + } + } + + private void deactivateContext() { + reqContext.deactivate(); + } + + private boolean activateContext() { + if (state != null && !reqContext.isActive()) { + reqContext.activate(state); + return true; + } + return false; } - } - }; + }; + } finally { + reqContext.deactivate(); + } } else { - LOGGER.warning("Unable to activate the request scope - interceptor not called on the Vert.x event loop"); + log.warn("Unable to activate the request scope - interceptor not called on the Vert.x event loop"); return next.startCall(call, headers); } } diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextHolder.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextHolder.java deleted file mode 100644 index bfd5e6753b817..0000000000000 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcRequestContextHolder.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.quarkus.grpc.runtime.supports.context; - -import io.quarkus.arc.InjectableContext; -import io.vertx.core.Context; - -public class GrpcRequestContextHolder { - - private static final String GRPC_REQUEST_CONTEXT_STATE = "GRPC_REQUEST_CONTEXT_STATE"; - - volatile InjectableContext.ContextState state; - - public static GrpcRequestContextHolder initialize(Context vertxContext) { - GrpcRequestContextHolder contextHolder = new GrpcRequestContextHolder(); - vertxContext.put(GRPC_REQUEST_CONTEXT_STATE, contextHolder); - return contextHolder; - } - - public static GrpcRequestContextHolder get(Context vertxContext) { - return vertxContext.get(GRPC_REQUEST_CONTEXT_STATE); - } -} diff --git a/extensions/grpc/runtime/src/test/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptorTest.java b/extensions/grpc/runtime/src/test/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptorTest.java index 6c759e8b10287..96a27e392e6eb 100644 --- a/extensions/grpc/runtime/src/test/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptorTest.java +++ b/extensions/grpc/runtime/src/test/java/io/quarkus/grpc/runtime/supports/BlockingServerInterceptorTest.java @@ -4,7 +4,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.Arrays; +import java.util.Collections; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.BeforeEach; @@ -16,8 +16,12 @@ import io.grpc.MethodDescriptor; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; +import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.ManagedContext; +import io.quarkus.grpc.runtime.supports.blocking.BlockingServerInterceptor; import io.vertx.core.Vertx; +@SuppressWarnings({ "rawtypes", "unchecked" }) class BlockingServerInterceptorTest { public static final Context.Key USERNAME = Context.key("username"); @@ -27,7 +31,15 @@ class BlockingServerInterceptorTest { @BeforeEach void setup() { vertx = Vertx.vertx(); - blockingServerInterceptor = new BlockingServerInterceptor(vertx, Arrays.asList("blocking"), false); + InjectableContext.ContextState contextState = mock(InjectableContext.ContextState.class); + ManagedContext requestContext = mock(ManagedContext.class); + when(requestContext.getState()).thenReturn(contextState); + blockingServerInterceptor = new BlockingServerInterceptor(vertx, Collections.singletonList("blocking"), false) { + @Override + protected ManagedContext getRequestContext() { + return requestContext; + } + }; } @Test @@ -61,8 +73,8 @@ void testContextPropagation() throws Exception { static class BlockingServerCallHandler implements ServerCallHandler { String threadName; String contextUserName; - private CountDownLatch latch = new CountDownLatch(1); - private CountDownLatch setupLatch = new CountDownLatch(1); + private final CountDownLatch latch = new CountDownLatch(1); + private final CountDownLatch setupLatch = new CountDownLatch(1); @Override public ServerCall.Listener startCall(ServerCall serverCall, Metadata metadata) { diff --git a/integration-tests/grpc-hibernate-reactive/pom.xml b/integration-tests/grpc-hibernate-reactive/pom.xml new file mode 100644 index 0000000000000..5e7ebb74449a7 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + quarkus-integration-test-grpc-hibernate-reactive + Quarkus - Integration Tests - gRPC - Hibernate Reactive + + + + io.quarkus + quarkus-grpc + + + io.quarkus + quarkus-hibernate-reactive-panache + + + io.quarkus + quarkus-reactive-pg-client + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-grpc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-reactive-panache-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-reactive-pg-client-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + generate-code + build + + + + + + maven-surefire-plugin + + true + + + + + + + + devservices-postgresql + + + start-containers + + + + + + maven-surefire-plugin + + false + + + + + + + + diff --git a/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ContextChecker.java b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ContextChecker.java new file mode 100644 index 0000000000000..3ab8366bd47b0 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ContextChecker.java @@ -0,0 +1,29 @@ +package com.example.reactive; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@ApplicationScoped +public class ContextChecker { + private final Map requestContexts = new ConcurrentHashMap<>(); + + @Inject + RequestScopeBean requestScopeBean; + + int newContextId(String caller) { + String original; + int contextId = requestScopeBean.getId(); + if ((original = requestContexts.put(contextId, caller)) != null) { + throw new RuntimeException( + "request context reused from a different call, original usage: " + original + ", duplicate: " + caller); + } + return contextId; + } + + public int requestContextId() { + return requestScopeBean.getId(); + } +} diff --git a/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/Item.java b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/Item.java new file mode 100644 index 0000000000000..667250ca300cc --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/Item.java @@ -0,0 +1,10 @@ +package com.example.reactive; + +import javax.persistence.Entity; + +import io.quarkus.hibernate.reactive.panache.PanacheEntity; + +@Entity +public class Item extends PanacheEntity { + public String text; +} diff --git a/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ReactiveService.java b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ReactiveService.java new file mode 100644 index 0000000000000..d76b9a1aef9e5 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/ReactiveService.java @@ -0,0 +1,62 @@ +package com.example.reactive; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ManagedContext; +import io.quarkus.grpc.GrpcService; +import io.quarkus.hibernate.reactive.panache.Panache; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; + +@GrpcService +public class ReactiveService implements ReactiveTest { + + BroadcastProcessor broadcast = BroadcastProcessor.create(); + + @Inject + ContextChecker contextChecker; + + ManagedContext requestContext; + + @PostConstruct + public void setUp() { + requestContext = Arc.container().requestContext(); + } + + @Override + public Uni add(Test.Item request) { + contextChecker.newContextId("ReactiveService#add"); + return Panache.withTransaction(() -> { + Item newItem = new Item(); + newItem.text = request.getText(); + return Item.persist(newItem) + .replaceWith(newItem); + }).onItem().invoke(newItem -> broadcast.onNext(newItem)) + .replaceWith(Test.Empty.getDefaultInstance()); + } + + @Override + public Multi watch(Test.Empty request) { + int contextId = contextChecker.newContextId("ReactiveService#watch"); + Multi existing = Item. streamAll() + .map(item -> Test.Item.newBuilder().setText(item.text).build()); + return Multi.createBy().concatenating() + .streams(existing, broadcast.map(i -> i.text) + .map(Test.Item.newBuilder()::setText) + .map(Test.Item.Builder::build)) + .onItem().invoke( + () -> { + if (contextChecker.requestContextId() != contextId) { + throw new RuntimeException("Different context for `onItem` and `ReactiveService#watch` method"); + } + if (!requestContext.isActive()) { + throw new RuntimeException( + "Request context not active for `onItem` in `ReactiveService#watch`"); + } + }) + .onCancellation().invoke(() -> System.out.println("canceled")); + } +} diff --git a/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/RequestScopeBean.java b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/RequestScopeBean.java new file mode 100644 index 0000000000000..fd4a742c47617 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/java/com/example/reactive/RequestScopeBean.java @@ -0,0 +1,21 @@ +package com.example.reactive; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; + +@RequestScoped +public class RequestScopeBean { + private static final AtomicInteger idSequence = new AtomicInteger(); + private int id; + + @PostConstruct + public void setUp() { + id = idSequence.getAndIncrement(); + } + + public int getId() { + return id; + } +} diff --git a/integration-tests/grpc-hibernate-reactive/src/main/proto/test.proto b/integration-tests/grpc-hibernate-reactive/src/main/proto/test.proto new file mode 100644 index 0000000000000..6b1159a5de25b --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/proto/test.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package example; + +option java_package = "com.example.reactive"; + +service ReactiveTest { + rpc watch(Empty) returns (stream Item); + rpc add(Item) returns (Empty); +} + +message Empty { + +} + +message Item { + string text = 1; +} diff --git a/integration-tests/grpc-hibernate-reactive/src/main/resources/application.properties b/integration-tests/grpc-hibernate-reactive/src/main/resources/application.properties new file mode 100644 index 0000000000000..f42b38adfeac7 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.hibernate-orm.database.generation=drop-and-create \ No newline at end of file diff --git a/integration-tests/grpc-hibernate-reactive/src/test/java/com/example/reactive/ReactiveServiceTest.java b/integration-tests/grpc-hibernate-reactive/src/test/java/com/example/reactive/ReactiveServiceTest.java new file mode 100644 index 0000000000000..e57748f9bed87 --- /dev/null +++ b/integration-tests/grpc-hibernate-reactive/src/test/java/com/example/reactive/ReactiveServiceTest.java @@ -0,0 +1,49 @@ +package com.example.reactive; + +import static org.awaitility.Awaitility.await; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import io.quarkus.grpc.GrpcClient; +import io.quarkus.test.junit.QuarkusTest; +import io.smallrye.mutiny.subscription.Cancellable; + +@QuarkusTest +public class ReactiveServiceTest { + + public static final int TIMEOUT = 60; + + @GrpcClient + ReactiveTest client; + + @Test + @Timeout(TIMEOUT) + void shouldWatchAndAddMultipleTimes() { + List collected = new CopyOnWriteArrayList<>(); + + Cancellable watch = client.watch(com.example.reactive.Test.Empty.getDefaultInstance()) + .onFailure().invoke(Throwable::printStackTrace) + .subscribe().with(item -> collected.add(item.getText())); + List expected = Collections.synchronizedList(new ArrayList<>()); + + for (int i = 0; i < 10; i++) { + String text = "hello world" + i; + expected.add(text); + client.add(com.example.reactive.Test.Item.newBuilder().setText(text).build()) + .onFailure().invoke(Throwable::printStackTrace) + .await().atMost(Duration.ofSeconds(TIMEOUT / 6)); + } + + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> collected.containsAll(expected)); + + watch.cancel(); + } +} diff --git a/integration-tests/grpc-hibernate/pom.xml b/integration-tests/grpc-hibernate/pom.xml new file mode 100644 index 0000000000000..44ea85097e4fb --- /dev/null +++ b/integration-tests/grpc-hibernate/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + quarkus-integration-test-grpc-hibernate + Quarkus - Integration Tests - gRPC - Hibernate + + + + io.quarkus + quarkus-grpc + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-h2 + + + io.quarkus + quarkus-test-h2 + test + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + test + + + + + io.quarkus + quarkus-grpc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + generate-code + build + + + + + + + + diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ContextChecker.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ContextChecker.java new file mode 100644 index 0000000000000..44f4e121e7124 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ContextChecker.java @@ -0,0 +1,29 @@ +package com.example.grpc.hibernate; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@ApplicationScoped +public class ContextChecker { + private final Map requestContexts = new ConcurrentHashMap<>(); + + @Inject + RequestScopeBean requestScopeBean; + + int newContextId(String caller) { + String original; + int contextId = requestScopeBean.getId(); + if ((original = requestContexts.put(contextId, caller)) != null) { + throw new RuntimeException( + "request context reused from a different call, original usage: " + original + ", duplicate: " + caller); + } + return contextId; + } + + public int requestContextId() { + return requestScopeBean.getId(); + } +} diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/Item.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/Item.java new file mode 100644 index 0000000000000..b59f0303f3f41 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/Item.java @@ -0,0 +1,14 @@ +package com.example.grpc.hibernate; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity(name = "Item") +public class Item { + @Id + @GeneratedValue + public Long id; + + public String text; +} \ No newline at end of file diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ItemDao.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ItemDao.java new file mode 100644 index 0000000000000..c3b63700cd91d --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/ItemDao.java @@ -0,0 +1,18 @@ +package com.example.grpc.hibernate; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +@ApplicationScoped +public class ItemDao { + + @Inject + EntityManager entityManager; + + @Transactional + public void add(Item newItem) { + entityManager.persist(newItem); + } +} diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RawTestService.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RawTestService.java new file mode 100644 index 0000000000000..406675b93728d --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RawTestService.java @@ -0,0 +1,112 @@ +package com.example.grpc.hibernate; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.jboss.logging.Logger; + +import com.example.test.TestOuterClass; +import com.example.test.TestRawGrpc; + +import io.grpc.stub.StreamObserver; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ManagedContext; +import io.quarkus.grpc.GrpcService; +import io.smallrye.common.annotation.Blocking; + +@GrpcService +@Blocking +public class RawTestService extends TestRawGrpc.TestRawImplBase { + + private static final Logger log = Logger.getLogger(RawTestService.class); + private static final TestOuterClass.Empty EMPTY = TestOuterClass.Empty.getDefaultInstance(); + + @Inject + EntityManager entityManager; + + @Inject + ItemDao dao; + + @Inject + ContextChecker contextChecker; + + ManagedContext requestContext; + + @PostConstruct + public void setUp() { + requestContext = Arc.container().requestContext(); + } + + @Override + @Transactional + public void add(TestOuterClass.Item request, StreamObserver responseObserver) { + contextChecker.newContextId("RawTestService#add"); + Item item = new Item(); + item.text = request.getText(); + entityManager.persist(item); + responseObserver.onNext(EMPTY); + responseObserver.onCompleted(); + } + + @Override + @Blocking + @Transactional + public void clear(TestOuterClass.Empty request, StreamObserver responseObserver) { + contextChecker.newContextId("RawTestService#clear"); + entityManager.createQuery("DELETE from Item") + .executeUpdate(); + responseObserver.onNext(EMPTY); + responseObserver.onCompleted(); + } + + @Override + public void getAll(TestOuterClass.Empty request, StreamObserver responseObserver) { + contextChecker.newContextId("RawTestService#getAll"); + List items = entityManager.createQuery("from Item", Item.class) + .getResultList(); + for (Item item : items) { + responseObserver.onNext(TestOuterClass.Item.newBuilder().setText(item.text).build()); + } + responseObserver.onCompleted(); + } + + @Override + public StreamObserver bidi(StreamObserver responseObserver) { + int contextId = contextChecker.newContextId("RawTestService#bidi"); + + return new StreamObserver() { + @Override + public void onNext(TestOuterClass.Item value) { + if (contextChecker.requestContextId() != contextId) { + throw new RuntimeException("Different context for onNext and RawTestService#bidi method"); + } + Item newItem = new Item(); + newItem.text = value.getText(); + dao.add(newItem); + + responseObserver.onNext(value); + } + + @Override + public void onError(Throwable t) { + log.error("bidi onError", t); + } + + @Override + public void onCompleted() { + if (contextChecker.requestContextId() != contextId) { + throw new RuntimeException("Different context for onCompleted and RawTestService#bidi method"); + } + if (!requestContext.isActive()) { + throw new RuntimeException("Request context not active for `onCompleted`"); + } + responseObserver.onCompleted(); + } + }; + } + +} diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RequestScopeBean.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RequestScopeBean.java new file mode 100644 index 0000000000000..7666a7868d7d6 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/RequestScopeBean.java @@ -0,0 +1,21 @@ +package com.example.grpc.hibernate; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; + +@RequestScoped +public class RequestScopeBean { + private static final AtomicInteger idSequence = new AtomicInteger(); + private int id; + + @PostConstruct + public void setUp() { + id = idSequence.getAndIncrement(); + } + + public int getId() { + return id; + } +} diff --git a/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/TestService.java b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/TestService.java new file mode 100644 index 0000000000000..dfc465f903534 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/java/com/example/grpc/hibernate/TestService.java @@ -0,0 +1,78 @@ +package com.example.grpc.hibernate; + +import java.util.List; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import com.example.test.Test; +import com.example.test.TestOuterClass; + +import io.quarkus.grpc.GrpcService; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +@GrpcService +public class TestService implements Test { + private static final TestOuterClass.Empty EMPTY = TestOuterClass.Empty.getDefaultInstance(); + + @Inject + EntityManager entityManager; + + @Inject + ItemDao dao; + + @Inject + ContextChecker contextChecker; + + @Override + @Blocking + @Transactional + public Uni add(TestOuterClass.Item request) { + contextChecker.newContextId("TestService#add"); + Item item = new Item(); + item.text = request.getText(); + entityManager.persist(item); + return Uni.createFrom().item(EMPTY); + } + + @Override + @Blocking + @Transactional + public Uni clear(TestOuterClass.Empty request) { + contextChecker.newContextId("TestService#clear"); + entityManager.createQuery("DELETE from Item") + .executeUpdate(); + return Uni.createFrom().item(EMPTY); + } + + @Override + @Blocking + public Multi getAll(TestOuterClass.Empty request) { + contextChecker.newContextId("TestService#getAll"); + List items = entityManager.createQuery("from Item", Item.class) + .getResultList(); + return Multi.createFrom().iterable(items) + .map(i -> TestOuterClass.Item.newBuilder().setText(i.text).build()); + } + + @Override + @Blocking + public Multi bidi(Multi request) { + int contextId = contextChecker.newContextId("TestService#bidi"); + return Multi.createFrom().emitter( + emitter -> request.subscribe().with( + item -> { + if (contextChecker.requestContextId() != contextId) { + throw new RuntimeException("Different context for subscriber and TestService#bidi method"); + } + Item newItem = new Item(); + newItem.text = item.getText(); + dao.add(newItem); + emitter.emit(item); + })); + } + +} diff --git a/integration-tests/grpc-hibernate/src/main/proto/test.proto b/integration-tests/grpc-hibernate/src/main/proto/test.proto new file mode 100644 index 0000000000000..db7b07b26e7e0 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/proto/test.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package test; + +option java_package = "com.example.test"; + +service Test { + rpc Add(Item) returns (Empty); + rpc getAll(Empty) returns (stream Item); + rpc clear(Empty) returns (Empty); + rpc bidi(stream Item) returns (stream Item); +} + +service TestRaw { + rpc Add(Item) returns (Empty); + rpc getAll(Empty) returns (stream Item); + rpc clear(Empty) returns (Empty); + rpc bidi(stream Item) returns (stream Item); +} + +message Empty { +} +message Item { + string text = 1; +} diff --git a/integration-tests/grpc-hibernate/src/main/resources/application.properties b/integration-tests/grpc-hibernate/src/main/resources/application.properties new file mode 100644 index 0000000000000..a78788facf306 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/main/resources/application.properties @@ -0,0 +1,5 @@ +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create diff --git a/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingMutinyTest.java b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingMutinyTest.java new file mode 100644 index 0000000000000..d4549ed068330 --- /dev/null +++ b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingMutinyTest.java @@ -0,0 +1,95 @@ +package com.example.grpc.hibernate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import com.example.test.Test; +import com.example.test.TestOuterClass; + +import io.quarkus.grpc.GrpcClient; +import io.quarkus.test.junit.QuarkusTest; +import io.smallrye.mutiny.Multi; + +@QuarkusTest +public class BlockingMutinyTest { + + public static final int NO_OF_ELTS = 100; + public static final int TIMEOUT = 60; + public static final TestOuterClass.Empty EMPTY = TestOuterClass.Empty.getDefaultInstance(); + @GrpcClient + Test client; + + @BeforeEach + void clear() { + client.clear(EMPTY).onFailure().invoke(e -> { + throw new RuntimeException("Failed to clear items", e); + }).await().atMost(Duration.ofSeconds(20)); + } + + @org.junit.jupiter.api.Test + @Timeout(TIMEOUT) + void shouldAddItems() { + List expected = new ArrayList<>(); + for (int i = 0; i < NO_OF_ELTS; i++) { + String text = "text " + i; + expected.add(text); + final int attempt = i; + client.add(TestOuterClass.Item.newBuilder().setText(text).build()) + .onFailure().invoke(e -> { + throw new RuntimeException("Failed to add on attempt " + attempt, e); + }) + .await().atMost(Duration.ofSeconds(5)); + } + + List actual = new ArrayList<>(); + Multi all = client.getAll(EMPTY) + .onFailure().invoke(th -> { + System.out.println("Failed to read"); + th.printStackTrace(); + }); + all.subscribe().with(item -> actual.add(item.getText())); + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> actual.size() == NO_OF_ELTS); + assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); + } + + @org.junit.jupiter.api.Test + @Timeout(TIMEOUT) + void shouldAddViaBidi() { + List expected = new ArrayList<>(); + List echoed = new ArrayList<>(); + List actual = new ArrayList<>(); + + Multi request = Multi.createFrom().emitter( + m -> { + for (int i = 0; i < NO_OF_ELTS; i++) { + String text = "text " + i; + expected.add(text); + m.emit(TestOuterClass.Item.newBuilder().setText(text).build()); + } + m.complete(); + }); + client.bidi(request).subscribe().with(item -> echoed.add(item.getText())); + + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> echoed.size() == NO_OF_ELTS); + assertThat(echoed).containsExactlyInAnyOrderElementsOf(expected); + + Multi all = client.getAll(EMPTY) + .onFailure().invoke(th -> { + System.out.println("Failed to read"); + th.printStackTrace(); + }); + all.subscribe().with(item -> actual.add(item.getText())); + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> actual.size() == NO_OF_ELTS); + assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingRawTest.java b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingRawTest.java new file mode 100644 index 0000000000000..1d732a476598a --- /dev/null +++ b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/BlockingRawTest.java @@ -0,0 +1,99 @@ +package com.example.grpc.hibernate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import com.example.test.Test; +import com.example.test.TestOuterClass; +import com.example.test.TestRaw; + +import io.quarkus.grpc.GrpcClient; +import io.quarkus.test.junit.QuarkusTest; +import io.smallrye.mutiny.Multi; + +@QuarkusTest +public class BlockingRawTest { + + public static final int NO_OF_ELTS = 100; + public static final int TIMEOUT = 60; + public static final TestOuterClass.Empty EMPTY = TestOuterClass.Empty.getDefaultInstance(); + @GrpcClient + TestRaw client; + + @BeforeEach + void clear() { + client.clear(EMPTY).onFailure().invoke(e -> { + throw new RuntimeException("Failed to clear items", e); + }).await().atMost(Duration.ofSeconds(20)); + } + + @org.junit.jupiter.api.Test + @Timeout(TIMEOUT) + void shouldAdd() { + List expected = new ArrayList<>(); + for (int i = 0; i < NO_OF_ELTS; i++) { + String text = "text " + i; + expected.add(text); + final int attempt = i; + client.add(TestOuterClass.Item.newBuilder().setText(text).build()) + .onFailure().invoke(e -> { + throw new RuntimeException("Failed to add on attempt " + attempt, e); + }) + .await().atMost(Duration.ofSeconds(5)); + } + + List actual = new ArrayList<>(); + Multi all = client.getAll(EMPTY) + .onFailure().invoke(th -> { + System.out.println("Failed to read"); + th.printStackTrace(); + }); + all.subscribe().with(item -> actual.add(item.getText())); + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> actual.size() == NO_OF_ELTS); + assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); + } + + @org.junit.jupiter.api.Test + @Timeout(TIMEOUT) + void shouldAddViaBidi() { + List expected = new ArrayList<>(); + List echoed = new ArrayList<>(); + List actual = new ArrayList<>(); + + Multi request = Multi.createFrom().emitter( + m -> { + for (int i = 0; i < NO_OF_ELTS; i++) { + String text = "text " + i; + expected.add(text); + m.emit(TestOuterClass.Item.newBuilder().setText(text).build()); + } + m.complete(); + }); + client.bidi(request).subscribe().with(item -> echoed.add(item.getText())); + + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> echoed.size() == NO_OF_ELTS); + assertThat(echoed).containsExactlyInAnyOrderElementsOf(expected); + + Multi all = client.getAll(EMPTY) + .onFailure().invoke(th -> { + System.out.println("Failed to read"); + th.printStackTrace(); + }); + all.subscribe().with(item -> actual.add(item.getText())); + await().atMost(Duration.ofSeconds(TIMEOUT / 2)) + .until(() -> { + System.out.println("no of elements: " + actual.size()); + return actual.size() == NO_OF_ELTS; + }); + assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/TestResources.java b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/TestResources.java new file mode 100644 index 0000000000000..37b0f78e27deb --- /dev/null +++ b/integration-tests/grpc-hibernate/src/test/java/com/example/grpc/hibernate/TestResources.java @@ -0,0 +1,8 @@ +package com.example.grpc.hibernate; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 84e805b9928df..25f3ec437fe8a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -186,6 +186,8 @@ grpc-interceptors grpc-proto-v2 grpc-health + grpc-hibernate + grpc-hibernate-reactive google-cloud-functions-http google-cloud-functions From 4420b9ddb7c3f6a593a6c874e635e66eafb963a9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 22 Jun 2021 10:28:45 +0200 Subject: [PATCH 20/24] Scala 2.12 does not support a JDK 11 target And in any case, it will need to be -target:11 when we switch to 2.13. (cherry picked from commit 34abbf194ab0e0e71a6037b3c55881eb8d215044) --- .../scala/src/test/resources/projects/classic-scala/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml b/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml index 707ad3601dedc..d13c1561e686f 100644 --- a/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml +++ b/integration-tests/scala/src/test/resources/projects/classic-scala/pom.xml @@ -102,7 +102,7 @@ -deprecation -feature -explaintypes - -target:jvm-11 + -target:jvm-1.8 -Ypartial-unification From 1ff213a974d64c343d2a9a2f88bc4b51860a7d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 22 Jun 2021 13:28:20 +0200 Subject: [PATCH 21/24] Add a resource exemple to the MongoDB and Hibernate OMR guides Fixes #10927 (cherry picked from commit c3073c7e4becc2058ce8e08e5384d8a266b492fb) --- .../main/asciidoc/hibernate-orm-panache.adoc | 74 +++++++++++++++++++ docs/src/main/asciidoc/mongodb-panache.adoc | 59 +++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index df2673a40114b..143877075b1e3 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -407,6 +407,80 @@ NOTE: The rest of the documentation show usages based on the active record patte but keep in mind that they can be performed with the repository pattern as well. The repository pattern examples have been omitted for brevity. +== Writing a JAX-RS resource + +First, include one of the RESTEasy extensions to enable JAX-RS endpoints, for example, add the `io.quarkus:quarkus-resteasy-jackson` dependency for JAX-RS and JSON support. + +Then, you can create the following resource to create/read/update/delete your Person entity: + +[source,java] +---- +@Path("/persons") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class PersonResource { + + @GET + public List list() { + return Person.listAll(); + } + + @GET + @Path("/{id}") + public Person get(@PathParam("id") Long id) { + return Person.findById(id); + } + + @POST + @Transactional + public Response create(Person person) { + person.persist(); + return Response.created(URI.create("/persons/" + person.id)).build(); + } + + @PUT + @Path("/{id}") + @Transactional + public Person update(@PathParam("id") Long id, Person person) { + Person entity = Person.findById(id); + if(entity == null) { + throw new NotFoundException(); + } + + // map all fields from the person parameter to the existing entity + entity.name = person.name; + + return entity; + } + + @DELETE + @Path("/{id}") + @Transactional + public void delete(@PathParam("id") Long id) { + Person entity = Person.findById(id); + if(entity == null) { + throw new NotFoundException(); + } + entity.delete(); + } + + @GET + @Path("/search/{name}") + public Person search(@PathParam("name") String name) { + return Person.findByName(name); + } + + @GET + @Path("/count") + public Long count() { + return Person.count(); + } +} +---- + +NOTE: Be careful to use the `@Transactional` annotation on the operations that modify the database, +you can add the annotation at the class level for simplicity purpose. + == Advanced Query === Paging diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index ca4159c76be9a..36a568b20ba56 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -414,6 +414,65 @@ NOTE: The rest of the documentation show usages based on the active record patte but keep in mind that they can be performed with the repository pattern as well. The repository pattern examples have been omitted for brevity. +== Writing a JAX-RS resource + +First, include one of the RESTEasy extensions to enable JAX-RS endpoints, for example, add the `io.quarkus:quarkus-resteasy-jackson` dependency for JAX-RS and JSON support. + +Then, you can create the following resource to create/read/update/delete your Person entity: + +[source,java] +---- +@Path("/persons") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class PersonResource { + + @GET + public List list() { + return Person.listAll(); + } + + @GET + @Path("/{id}") + public Person get(@PathParam("id") String id) { + return Person.findById(new ObjectId(id)); + } + + @POST + public Response create(Person person) { + person.persist(); + return Response.created(URI.create("/persons/" + person.id)).build(); + } + + @PUT + @Path("/{id}") + public void update(@PathParam("id") String id, Person person) { + person.update(); + } + + @DELETE + @Path("/{id}") + public void delete(@PathParam("id") String id) { + Person person = Person.findById(new ObjectId(id)); + if(person == null) { + throw new NotFoundException(); + } + person.delete(); + } + + @GET + @Path("/search/{name}") + public Person search(@PathParam("name") String name) { + return Person.findByName(name); + } + + @GET + @Path("/count") + public Long count() { + return Person.count(); + } +} +---- == Advanced Query From 42e459ccf76dc5f571e05f999f68c34818cdf169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 22 Jun 2021 14:26:25 +0200 Subject: [PATCH 22/24] Document the limitation of log message indexing Fixes #15370 WIP Update docs/src/main/asciidoc/centralized-log-management.adoc (cherry picked from commit 3e89e284249f3c5dd28ef0adbd8afcd3e4b10022) --- .../asciidoc/centralized-log-management.adoc | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/src/main/asciidoc/centralized-log-management.adoc b/docs/src/main/asciidoc/centralized-log-management.adoc index 222ab990dd6fd..b63c8516ab4df 100644 --- a/docs/src/main/asciidoc/centralized-log-management.adoc +++ b/docs/src/main/asciidoc/centralized-log-management.adoc @@ -404,6 +404,28 @@ quarkus.log.syslog.hostname=quarkus-test Launch your application, you should see your logs arriving inside EFK: you can use Kibana available at http://localhost:5601/ to access them. +== Elasticsearch indexing consideration + +Be careful that, by default, Elasticsearch will automatically map unknown fields (if not disabled in the index settings) by detecting their type. +This can become tricky if you use log parameters (which are included by default), or if you enable MDC inclusion (disabled by default), +as the first log will define the type of the message parameter (or MDC parameter) field inside the index. + +Imagine the following case: + +[source, java] +---- +LOG.info("some {} message {} with {} param", 1, 2, 3); +LOG.info("other {} message {} with {} param", true, true, true); +---- + +With log message parameters enabled, the first log message sent to Elasticsearch will have a `MessageParam0` parameter with an `int` type; +this will configure the index with a field of type `integer`. +When the second message will arrive to Elasticsearch, it will have a `MessageParam0` parameter with the boolean value `true`, and this will generate an indexing error. + +To work around this limitation, you can disable sending log message parameters via `logging-gelf` by configuring `quarkus.log.handler.gelf.include-log-message-parameters=false`, +or you can configure your Elasticsearch index to store those fields as text or keyword, Elasticsearch will then automatically make the translation from int/boolean to a String. + +See the following documentation for Graylog (but the same issue exists for the other central logging stacks): link:https://docs.graylog.org/en/3.2/pages/configuration/elasticsearch.html#custom-index-mappings[Custom Index Mappings]. [[configuration-reference]] == Configuration Reference From bfb72b3be55a85bbc02a7d24cc12ba722e5a2074 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 18 Jun 2021 08:53:56 +1000 Subject: [PATCH 23/24] Exclude JUnit 4 This adds a junit4-mock artifact which contains the one interface testcontainers depends on. (cherry picked from commit cc5322dae1d7806984784514761728bb75ae4bb4) --- bom/application/pom.xml | 5 +++++ build-parent/pom.xml | 2 ++ core/junit4-mock/pom.xml | 19 +++++++++++++++++++ .../org/junit/rules/ExternalResource.java | 7 +++++++ .../main/java/org/junit/rules/TestRule.java | 8 ++++++++ .../java/org/junit/runner/Description.java | 4 ++++ .../org/junit/runners/model/Statement.java | 8 ++++++++ core/pom.xml | 1 + core/runtime/pom.xml | 3 +++ .../apicurio-registry-avro/deployment/pom.xml | 10 ++++++++++ extensions/devservices/db2/pom.xml | 10 ++++++++++ extensions/devservices/mariadb/pom.xml | 10 ++++++++++ extensions/devservices/mssql/pom.xml | 10 ++++++++++ extensions/devservices/mysql/pom.xml | 10 ++++++++++ extensions/devservices/postgresql/pom.xml | 10 ++++++++++ .../jdbc/jdbc-postgresql/deployment/pom.xml | 4 ---- extensions/kafka-client/deployment/pom.xml | 10 ++++++++++ extensions/mongodb-client/deployment/pom.xml | 10 ++++++++++ extensions/redis-client/deployment/pom.xml | 10 ++++++++++ extensions/vault/deployment/pom.xml | 8 ++++++++ 20 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 core/junit4-mock/pom.xml create mode 100644 core/junit4-mock/src/main/java/org/junit/rules/ExternalResource.java create mode 100644 core/junit4-mock/src/main/java/org/junit/rules/TestRule.java create mode 100644 core/junit4-mock/src/main/java/org/junit/runner/Description.java create mode 100644 core/junit4-mock/src/main/java/org/junit/runners/model/Statement.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 8252ca01fcd72..47d4645cbaf97 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -371,6 +371,11 @@ quarkus-development-mode-spi ${project.version} + + io.quarkus + quarkus-junit4-mock + ${project.version} + io.quarkus quarkus-class-change-agent diff --git a/build-parent/pom.xml b/build-parent/pom.xml index b5a28f282a508..b8d0d6354d458 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -494,11 +494,13 @@ io.quarkus:quarkus-test-* io.rest-assured:* org.assertj:* + junit:junit io.quarkus:quarkus-test-*:*:*:test io.rest-assured:*:*:*:test org.assertj:*:*:*:test + junit:junit:*:*:test Found test dependencies with wrong scope: diff --git a/core/junit4-mock/pom.xml b/core/junit4-mock/pom.xml new file mode 100644 index 0000000000000..4e94d0989ff92 --- /dev/null +++ b/core/junit4-mock/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + + io.quarkus + quarkus-core-parent + 999-SNAPSHOT + + + quarkus-junit4-mock + Quarkus - JUnit 4 Mock + Module with some empty JUnit4 classes to allow Testcontainers + to run without needing to include JUnit4 on the class path + + + diff --git a/core/junit4-mock/src/main/java/org/junit/rules/ExternalResource.java b/core/junit4-mock/src/main/java/org/junit/rules/ExternalResource.java new file mode 100644 index 0000000000000..fde4eb436f3f9 --- /dev/null +++ b/core/junit4-mock/src/main/java/org/junit/rules/ExternalResource.java @@ -0,0 +1,7 @@ +package org.junit.rules; + +public class ExternalResource { + protected void after() { + + } +} diff --git a/core/junit4-mock/src/main/java/org/junit/rules/TestRule.java b/core/junit4-mock/src/main/java/org/junit/rules/TestRule.java new file mode 100644 index 0000000000000..ac06c7dcca136 --- /dev/null +++ b/core/junit4-mock/src/main/java/org/junit/rules/TestRule.java @@ -0,0 +1,8 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public interface TestRule { + Statement apply(Statement var1, Description var2); +} diff --git a/core/junit4-mock/src/main/java/org/junit/runner/Description.java b/core/junit4-mock/src/main/java/org/junit/runner/Description.java new file mode 100644 index 0000000000000..fcda478237136 --- /dev/null +++ b/core/junit4-mock/src/main/java/org/junit/runner/Description.java @@ -0,0 +1,4 @@ +package org.junit.runner; + +public class Description { +} diff --git a/core/junit4-mock/src/main/java/org/junit/runners/model/Statement.java b/core/junit4-mock/src/main/java/org/junit/runners/model/Statement.java new file mode 100644 index 0000000000000..09507a6991d67 --- /dev/null +++ b/core/junit4-mock/src/main/java/org/junit/runners/model/Statement.java @@ -0,0 +1,8 @@ +package org.junit.runners.model; + +public abstract class Statement { + public Statement() { + } + + public abstract void evaluate() throws Throwable; +} diff --git a/core/pom.xml b/core/pom.xml index 8bff7ca665180..30a09f8df404c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,5 +22,6 @@ devmode-spi launcher class-change-agent + junit4-mock diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 341f3519f7328..1bf0fc35c4569 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -122,6 +122,9 @@ io.quarkus quarkus-bootstrap-maven-plugin + + io.quarkus:quarkus-junit4-mock + io.quarkus:quarkus-bootstrap-runner io.smallrye.common:smallrye-common-io diff --git a/extensions/apicurio-registry-avro/deployment/pom.xml b/extensions/apicurio-registry-avro/deployment/pom.xml index ba504cdff01d8..9d8355ee0a2db 100644 --- a/extensions/apicurio-registry-avro/deployment/pom.xml +++ b/extensions/apicurio-registry-avro/deployment/pom.xml @@ -35,6 +35,16 @@ org.testcontainers testcontainers + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock diff --git a/extensions/devservices/db2/pom.xml b/extensions/devservices/db2/pom.xml index a618290b07f9a..59a41f048f99f 100644 --- a/extensions/devservices/db2/pom.xml +++ b/extensions/devservices/db2/pom.xml @@ -19,6 +19,16 @@ org.testcontainers db2 + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock com.ibm.db2 diff --git a/extensions/devservices/mariadb/pom.xml b/extensions/devservices/mariadb/pom.xml index 9af0b64e35714..6bfcaa09a35bb 100644 --- a/extensions/devservices/mariadb/pom.xml +++ b/extensions/devservices/mariadb/pom.xml @@ -19,6 +19,16 @@ org.testcontainers mariadb + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock org.mariadb.jdbc diff --git a/extensions/devservices/mssql/pom.xml b/extensions/devservices/mssql/pom.xml index 7cf81235367f4..565cd4235fcd4 100644 --- a/extensions/devservices/mssql/pom.xml +++ b/extensions/devservices/mssql/pom.xml @@ -19,6 +19,16 @@ org.testcontainers mssqlserver + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock com.microsoft.sqlserver diff --git a/extensions/devservices/mysql/pom.xml b/extensions/devservices/mysql/pom.xml index 04058995766ea..3a30b3526524b 100644 --- a/extensions/devservices/mysql/pom.xml +++ b/extensions/devservices/mysql/pom.xml @@ -19,6 +19,16 @@ org.testcontainers mysql + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock org.junit.jupiter diff --git a/extensions/devservices/postgresql/pom.xml b/extensions/devservices/postgresql/pom.xml index 079f81cd25b29..21250495ba8e8 100644 --- a/extensions/devservices/postgresql/pom.xml +++ b/extensions/devservices/postgresql/pom.xml @@ -19,6 +19,16 @@ org.testcontainers postgresql + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock org.postgresql diff --git a/extensions/jdbc/jdbc-postgresql/deployment/pom.xml b/extensions/jdbc/jdbc-postgresql/deployment/pom.xml index d89e7523e1aa3..cbfd96e78561d 100644 --- a/extensions/jdbc/jdbc-postgresql/deployment/pom.xml +++ b/extensions/jdbc/jdbc-postgresql/deployment/pom.xml @@ -13,10 +13,6 @@ Quarkus - JDBC - PostgreSQL - Deployment - - org.testcontainers - postgresql - io.quarkus quarkus-arc-deployment diff --git a/extensions/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index b065abca2722c..0f7e820ce8c28 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -42,6 +42,16 @@ org.testcontainers testcontainers + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index a86c26769124a..cdbb613cfc6d9 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -37,6 +37,16 @@ org.testcontainers mongodb + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock io.quarkus diff --git a/extensions/redis-client/deployment/pom.xml b/extensions/redis-client/deployment/pom.xml index c3f7594f0fdaf..93badcfd0a3bc 100644 --- a/extensions/redis-client/deployment/pom.xml +++ b/extensions/redis-client/deployment/pom.xml @@ -30,6 +30,16 @@ org.testcontainers testcontainers + + + junit + junit + + + + + io.quarkus + quarkus-junit4-mock diff --git a/extensions/vault/deployment/pom.xml b/extensions/vault/deployment/pom.xml index 32e1fa58d0648..c34c42e900909 100644 --- a/extensions/vault/deployment/pom.xml +++ b/extensions/vault/deployment/pom.xml @@ -53,8 +53,16 @@ org.hamcrest hamcrest-core + + junit + junit + + + io.quarkus + quarkus-junit4-mock + io.quarkus quarkus-junit5 From 65ed15cad1191eff8054061b0eed2d38456f214c Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 17 Jun 2021 17:40:16 +0200 Subject: [PATCH 24/24] Generic signature reconstruction for methods The algorithm for reconstructing generic signature for methods in `AsmUtil` had some unimplemented parts, which are now completed. Gizmo is also updated to the latest version that has complete support for generating annotations. Finally, RestClient Reactive now properly copies the method generic signature when building RestClient interface implementation. (cherry picked from commit 0948e42c0522308bd81e5461f722b27e515a13d0) --- bom/application/pom.xml | 2 +- .../io/quarkus/deployment/util/AsmUtil.java | 228 ++++++++++++++--- ...hodGenericSignatureReconstructionTest.java | 121 +++++++++ .../quarkus/deployment/util/OuterParam.java | 63 +++++ .../deployment/util/OuterParamBound.java | 63 +++++ .../io/quarkus/deployment/util/OuterRaw.java | 63 +++++ .../RestClientReactiveProcessor.java | 12 +- .../reactive/common/processor/AsmUtil.java | 237 ++++++++++++++---- 8 files changed, 694 insertions(+), 95 deletions(-) create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/MethodGenericSignatureReconstructionTest.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 47d4645cbaf97..134c31a9d5a49 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -88,7 +88,7 @@ 2.1.0 21.1.0 - 1.0.8.Final + 1.0.9.Final 2.12.3 1.0.0.Final 3.12.0 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java index e02454a4ca58b..254438f8a301c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java @@ -12,6 +12,7 @@ import static org.objectweb.asm.Type.VOID_TYPE; import static org.objectweb.asm.Type.getType; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -19,6 +20,7 @@ import java.util.function.Function; import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; @@ -26,6 +28,8 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.jboss.jandex.WildcardType; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -68,49 +72,168 @@ public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { return WRAPPERS.get(primitive.getSort()); } + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method) { + return getSignatureIfRequired(method, ignored -> null); + } + + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @param typeArgMapper a mapping between type argument names and their bytecode signatures + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) { + if (!hasSignature(method)) { + return null; + } + + return getSignature(method, typeArgMapper); + } + + private static boolean hasSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A method signature for any method or constructor declaration which is either generic, + // or has a type variable or parameterized type as the return type or a formal parameter type, + // or has a type variable in a throws clause, or any combination thereof. + + if (!method.typeParameters().isEmpty()) { + return true; + } + + { + Type type = method.returnType(); + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + for (Type type : method.parameters()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + if (hasThrowsSignature(method)) { + return true; + } + + return false; + } + + private static boolean hasThrowsSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because + // + // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters: + // + // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable. + + for (Type type : method.exceptions()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) { + return true; + } + } + return false; + } + /** * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: - * + * *
      * {@code
      * public class Foo {
-     *  public  List method(int a, T t){...} 
+     *  public  List method(int a, T t){...}
      * }
      * }
      * 
- * + * * This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * + * * @param method the method you want the signature for. * @param typeArgMapper a mapping between type argument names and their bytecode signature. * @return a bytecode signature for that method. */ public static String getSignature(MethodInfo method, Function typeArgMapper) { - List parameters = method.parameters(); + // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures - StringBuilder signature = new StringBuilder(""); - for (TypeVariable typeVariable : method.typeParameters()) { - if (signature.length() == 0) - signature.append("<"); - else - signature.append(","); - signature.append(typeVariable.identifier()).append(":"); - // FIXME: only use the first bound - toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false); + StringBuilder signature = new StringBuilder(); + + if (!method.typeParameters().isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : method.typeParameters()) { + typeParameter(typeParameter, signature, typeArgMapper); + } + signature.append('>'); } - if (signature.length() > 0) - signature.append(">"); - signature.append("("); - for (Type type : parameters) { + + signature.append('('); + for (Type type : method.parameters()) { toSignature(signature, type, typeArgMapper, false); } - signature.append(")"); + signature.append(')'); + toSignature(signature, method.returnType(), typeArgMapper, false); + + if (hasThrowsSignature(method)) { + for (Type exception : method.exceptions()) { + signature.append('^'); + toSignature(signature, exception, typeArgMapper, false); + } + } + return signature.toString(); } + private static void typeParameter(TypeVariable typeParameter, StringBuilder result, + Function typeArgMapper) { + result.append(typeParameter.identifier()); + + if (hasImplicitObjectBound(typeParameter)) { + result.append(':'); + } + for (Type bound : typeParameter.bounds()) { + result.append(':'); + toSignature(result, bound, typeArgMapper, false); + } + } + + private static boolean hasImplicitObjectBound(TypeVariable typeParameter) { + // TODO is there a better way? :-/ + boolean result = false; + try { + Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound"); + method.setAccessible(true); + result = (Boolean) method.invoke(typeParameter); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return result; + } + /** * Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: @@ -176,30 +299,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments(); - for (int i = 0; i < arguments.size(); i++) { - Type argType = arguments.get(i); - toSignature(sb, argType, typeArgMapper, erased); + sb.append('<'); + for (Type argument : parameterizedType.arguments()) { + toSignature(sb, argument, typeArgMapper, erased); } - sb.append(">"); + sb.append('>'); } - sb.append(";"); + sb.append(';'); break; case PRIMITIVE: Primitive primitive = type.asPrimitiveType().primitive(); @@ -233,22 +361,40 @@ private static void toSignature(StringBuilder sb, Type type, Function;U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;Lio/quarkus/deployment/util/OuterRaw;)TT;"); + assertSignature(OuterRaw.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterRaw.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParam;)TT;"); + assertSignature(OuterRaw.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParamBound;)TT;^TV;"); + assertSignature(OuterRaw.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterRaw.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParam;)TT;"); + assertSignature(OuterRaw.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound;)TT;^TV;"); + assertSignature(OuterRaw.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound.DoubleInner;)TT;^TV;"); + + assertSignature(OuterParam.class, "aaa", + "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParam;)Ljava/lang/String;"); + assertSignature(OuterParam.class, "bbb", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParam;)TT;"); + assertSignature(OuterParam.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParam$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParam.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParam;)TT;"); + assertSignature(OuterParam.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParamBound;)TT;^TV;"); + assertSignature(OuterParam.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParam.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParam.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParam;)TT;"); + assertSignature(OuterParam.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound;)TT;^TV;"); + assertSignature(OuterParam.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound.DoubleInner;)TT;^TV;"); + + assertSignature(OuterParamBound.class, "aaa", + "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParamBound;)Ljava/lang/String;"); + assertSignature(OuterParamBound.class, "bbb", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParamBound;)TT;"); + assertSignature(OuterParamBound.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParamBound$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParamBound.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParam;)TT;"); + assertSignature(OuterParamBound.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParamBound;)TT;^TV;"); + assertSignature(OuterParamBound.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParamBound.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParam;)TT;"); + assertSignature(OuterParamBound.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound;)TT;^TV;"); + assertSignature(OuterParamBound.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound.DoubleInner;)TT;^TV;"); + } + + private static void assertSignature(Class clazz, String method, String expectedSignature) { + DotName name = DotName.createSimple(clazz.getName()); + for (MethodInfo methodInfo : index.getClassByName(name).methods()) { + if (method.equals(methodInfo.name())) { + String actualSignature = AsmUtil.getSignatureIfRequired(methodInfo); + assertEquals(expectedSignature, actualSignature); + return; + } + } + + fail("Couldn't find method " + clazz.getName() + "#" + method + " in test index"); + } + + private static Index index(Class... classes) { + Indexer indexer = new Indexer(); + for (Class clazz : classes) { + try { + try (InputStream stream = MethodGenericSignatureReconstructionTest.class.getClassLoader() + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { + indexer.index(stream); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return indexer.complete(); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java new file mode 100644 index 0000000000000..2a849c8e8120d --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterParam { + public String aaa(String arg, W arg2, OuterParam self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, W arg2, OuterParam self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java new file mode 100644 index 0000000000000..64097bc389245 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterParamBound> { + public String aaa(String arg, W arg2, OuterParamBound self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, W arg2, OuterParamBound self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java new file mode 100644 index 0000000000000..e78160420b0ca --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterRaw { + public String aaa(String arg, OuterRaw self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, OuterRaw self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 8894a15c756b1..626a5cc8a028f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -58,6 +58,7 @@ import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.util.AsmUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; @@ -307,21 +308,14 @@ void addRestClientBeans(Capabilities capabilities, // return ((InterfaceClass)this.getDelegate()).get(); // } MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.of(method)); + methodCreator.setSignature(AsmUtil.getSignatureIfRequired(method)); // copy method annotations, there can be interceptors bound to them: for (AnnotationInstance annotation : method.annotations()) { if (annotation.target().kind() == AnnotationTarget.Kind.METHOD && !BUILTIN_HTTP_ANNOTATIONS_TO_METHOD.containsKey(annotation.name()) && !ResteasyReactiveDotNames.PATH.equals(annotation.name())) { - AnnotationValue value = annotation.value(); - if (value != null && value.kind() == AnnotationValue.Kind.ARRAY - && value.componentKind() == AnnotationValue.Kind.NESTED) { - for (AnnotationInstance annotationInstance : value.asNestedArray()) { - methodCreator.addAnnotation(annotationInstance); - } - } else { - methodCreator.addAnnotation(annotation); - } + methodCreator.addAnnotation(annotation); } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java index dc66f3b6bb255..750b68ef5e3e6 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java @@ -4,6 +4,7 @@ import static org.objectweb.asm.Type.BOOLEAN_TYPE; import static org.objectweb.asm.Type.BYTE_TYPE; import static org.objectweb.asm.Type.CHAR_TYPE; +import static org.objectweb.asm.Type.DOUBLE_TYPE; import static org.objectweb.asm.Type.FLOAT_TYPE; import static org.objectweb.asm.Type.INT_TYPE; import static org.objectweb.asm.Type.LONG_TYPE; @@ -11,12 +12,14 @@ import static org.objectweb.asm.Type.VOID_TYPE; import static org.objectweb.asm.Type.getType; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; @@ -24,6 +27,8 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.jboss.jandex.WildcardType; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -42,7 +47,8 @@ public class AsmUtil { SHORT_TYPE, INT_TYPE, FLOAT_TYPE, - LONG_TYPE); + LONG_TYPE, + DOUBLE_TYPE); public static final List WRAPPERS = asList( getType(Void.class), getType(Boolean.class), @@ -51,7 +57,8 @@ public class AsmUtil { getType(Short.class), getType(Integer.class), getType(Float.class), - getType(Long.class)); + getType(Long.class), + getType(Double.class)); public static final Map WRAPPER_TO_PRIMITIVE = new HashMap<>(); static { @@ -64,49 +71,168 @@ public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { return WRAPPERS.get(primitive.getSort()); } + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method) { + return getSignatureIfRequired(method, ignored -> null); + } + + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @param typeArgMapper a mapping between type argument names and their bytecode signatures + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) { + if (!hasSignature(method)) { + return null; + } + + return getSignature(method, typeArgMapper); + } + + private static boolean hasSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A method signature for any method or constructor declaration which is either generic, + // or has a type variable or parameterized type as the return type or a formal parameter type, + // or has a type variable in a throws clause, or any combination thereof. + + if (!method.typeParameters().isEmpty()) { + return true; + } + + { + Type type = method.returnType(); + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + for (Type type : method.parameters()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + if (hasThrowsSignature(method)) { + return true; + } + + return false; + } + + private static boolean hasThrowsSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because + // + // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters: + // + // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable. + + for (Type type : method.exceptions()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) { + return true; + } + } + return false; + } + /** * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: - * + * *
      * {@code
      * public class Foo {
-     *  public  List method(int a, T t){...} 
+     *  public  List method(int a, T t){...}
      * }
      * }
      * 
- * + * * This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * + * * @param method the method you want the signature for. * @param typeArgMapper a mapping between type argument names and their bytecode signature. * @return a bytecode signature for that method. */ public static String getSignature(MethodInfo method, Function typeArgMapper) { - List parameters = method.parameters(); + // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures - StringBuilder signature = new StringBuilder(""); - for (TypeVariable typeVariable : method.typeParameters()) { - if (signature.length() == 0) - signature.append("<"); - else - signature.append(","); - signature.append(typeVariable.identifier()).append(":"); - // FIXME: only use the first bound - toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false); + StringBuilder signature = new StringBuilder(); + + if (!method.typeParameters().isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : method.typeParameters()) { + typeParameter(typeParameter, signature, typeArgMapper); + } + signature.append('>'); } - if (signature.length() > 0) - signature.append(">"); - signature.append("("); - for (Type type : parameters) { + + signature.append('('); + for (Type type : method.parameters()) { toSignature(signature, type, typeArgMapper, false); } - signature.append(")"); + signature.append(')'); + toSignature(signature, method.returnType(), typeArgMapper, false); + + if (hasThrowsSignature(method)) { + for (Type exception : method.exceptions()) { + signature.append('^'); + toSignature(signature, exception, typeArgMapper, false); + } + } + return signature.toString(); } + private static void typeParameter(TypeVariable typeParameter, StringBuilder result, + Function typeArgMapper) { + result.append(typeParameter.identifier()); + + if (hasImplicitObjectBound(typeParameter)) { + result.append(':'); + } + for (Type bound : typeParameter.bounds()) { + result.append(':'); + toSignature(result, bound, typeArgMapper, false); + } + } + + private static boolean hasImplicitObjectBound(TypeVariable typeParameter) { + // TODO is there a better way? :-/ + boolean result = false; + try { + Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound"); + method.setAccessible(true); + result = (Boolean) method.invoke(typeParameter); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return result; + } + /** * Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: @@ -172,30 +298,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments(); - for (int i = 0; i < arguments.size(); i++) { - Type argType = arguments.get(i); - toSignature(sb, argType, typeArgMapper, erased); + sb.append('<'); + for (Type argument : parameterizedType.arguments()) { + toSignature(sb, argument, typeArgMapper, erased); } - sb.append(">"); + sb.append('>'); } - sb.append(";"); + sb.append(';'); break; case PRIMITIVE: Primitive primitive = type.asPrimitiveType().primitive(); @@ -229,22 +360,40 @@ private static void toSignature(StringBuilder sb, Type type, FunctionIRETURN, LRETURN, FRETURN, DRETURN, RETURN for primitives/void, * and ARETURN otherwise; * - * @param typeDescriptor the return Jandex Type. + * @param jandexType the return Jandex Type. * @return the correct bytecode return instruction for that return type descriptor. */ public static int getReturnInstruction(Type jandexType) {