From eb27113c050b2c98a0ef046a84d366484adf6e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMA?= Date: Thu, 28 Nov 2019 10:22:42 +0100 Subject: [PATCH 001/602] Adds quarkus-agroal configuration to enable the use of GCP CloudSQL database --- extensions/agroal/deployment/pom.xml | 12 ++++ .../agroal/test/GcpDataSourceConfigTest.java | 59 +++++++++++++++++++ .../application-gcp-datasource.properties | 18 ++++++ .../runtime/AbstractDataSourceProducer.java | 16 +++++ .../runtime/DataSourceRuntimeConfig.java | 12 ++++ 5 files changed, 117 insertions(+) create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java create mode 100644 extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 03167b34a1efd..4d6654275ef55 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -46,6 +46,18 @@ quarkus-test-h2 test + + com.google.cloud.sql + postgres-socket-factory + 1.0.15 + test + + + org.postgresql + postgresql + 42.2.8 + test + diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java new file mode 100644 index 0000000000000..53d459593bc2f --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java @@ -0,0 +1,59 @@ +package io.quarkus.agroal.test; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.agroal.narayana.NarayanaTransactionIntegration; +import io.quarkus.test.QuarkusUnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import javax.inject.Inject; +import java.sql.SQLException; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GcpDataSourceConfigTest { + + //tag::injection[] + @Inject + AgroalDataSource defaultDataSource; + //end::injection[] + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-gcp-datasource.properties"); + + @Test + public void testGCPDataSourceInjection() throws SQLException { + testDataSource(defaultDataSource, "username-default", 3, 13, 7, Duration.ofSeconds(53), Duration.ofSeconds(54), + Duration.ofSeconds(55), Duration.ofSeconds(56), Duration.ofSeconds(57), + "create schema if not exists schema_default"); + } + + private static void testDataSource(AgroalDataSource dataSource, String username, int minSize, int maxSize, + int initialSize, Duration backgroundValidationInterval, Duration acquisitionTimeout, Duration leakDetectionInterval, + Duration idleRemovalInterval, Duration maxLifetime, String newConnectionSql) throws SQLException { + AgroalConnectionPoolConfiguration configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + AgroalConnectionFactoryConfiguration agroalConnectionFactoryConfiguration = configuration + .connectionFactoryConfiguration(); + assertEquals("jdbc:postgresql:///database-name?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=project:zone:db-name", agroalConnectionFactoryConfiguration.jdbcUrl()); + assertEquals(username, agroalConnectionFactoryConfiguration.principal().getName()); + assertEquals(minSize, configuration.minSize()); + assertEquals(maxSize, configuration.maxSize()); + assertEquals(initialSize, configuration.initialSize()); + assertEquals(backgroundValidationInterval, configuration.validationTimeout()); + assertEquals(acquisitionTimeout, configuration.acquisitionTimeout()); + assertEquals(leakDetectionInterval, configuration.leakTimeout()); + assertEquals(idleRemovalInterval, configuration.reapTimeout()); + assertEquals(maxLifetime, configuration.maxLifetime()); + assertTrue(configuration.transactionIntegration() instanceof NarayanaTransactionIntegration); + assertEquals(AgroalConnectionFactoryConfiguration.TransactionIsolation.SERIALIZABLE, + agroalConnectionFactoryConfiguration.jdbcTransactionIsolation()); + assertTrue(agroalConnectionFactoryConfiguration.trackJdbcResources()); + assertTrue(dataSource.getConfiguration().metricsEnabled()); + assertEquals(newConnectionSql, agroalConnectionFactoryConfiguration.initialSql()); + } +} diff --git a/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties b/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties new file mode 100644 index 0000000000000..29f86e9c94d02 --- /dev/null +++ b/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties @@ -0,0 +1,18 @@ +#tag::basic[] +quarkus.datasource.url = jdbc:postgresql:///database-name +quarkus.datasource.driver = org.postgresql.Driver +quarkus.datasource.username=username-default +quarkus.datasource.min-size=3 +quarkus.datasource.max-size=13 +quarkus.datasource.enable-metrics=true +#end::basic[] +quarkus.datasource.initial-size=7 +quarkus.datasource.background-validation-interval=53 +quarkus.datasource.acquisition-timeout=54 +quarkus.datasource.leak-detection-interval=55 +quarkus.datasource.idle-removal-interval=56 +quarkus.datasource.max-lifetime=57 +quarkus.datasource.transaction-isolation-level=serializable +quarkus.datasource.new-connection-sql=create schema if not exists schema_default +quarkus.datasource.use-gcp=true +quarkus.datasource.cloud-sql-instance=project:zone:db-name \ No newline at end of file diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java index f084f7937479a..33842e54cb3f0 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java @@ -3,6 +3,7 @@ import java.sql.Connection; import java.sql.Driver; import java.sql.Statement; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -195,6 +196,21 @@ public boolean isValid(Connection connection) { poolConfiguration.maxLifetime(dataSourceRuntimeConfig.maxLifetime.get()); } + // CloudSQL config + if (dataSourceRuntimeConfig.useGcp) { + disableSslSupport(); + String cloudSqlInstance = dataSourceRuntimeConfig.cloudSqlInstance.orElseThrow( + () -> new RuntimeException("Cloud sql instance property is mandatory to use Gcp Cloudsql")); + if (cloudSqlInstance.split(":").length != 3) { + throw new RuntimeException("Cloud sql instance should match the pattern project-id:zone:cloudSqlInstance"); + } + agroalConnectionFactoryConfigurationSupplier.jdbcUrl( + MessageFormat.format("{0}?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance={1}" + ,url + , cloudSqlInstance)); + + } + // SSL support: we should push the driver specific code to the driver extensions but it will have to do for now if (disableSslSupport) { switch (driverName) { diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceRuntimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceRuntimeConfig.java index ca46829b31454..6e88d2c915bfe 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceRuntimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSourceRuntimeConfig.java @@ -130,4 +130,16 @@ public class DataSourceRuntimeConfig { */ @ConfigItem public Optional validationQuerySql; + + /** + * When enabled jdbc url will be forged to enable google socket factory + */ + @ConfigItem(defaultValue = "false") + public boolean useGcp; + + /** + * Query executed to validate a connection. + */ + @ConfigItem + public Optional cloudSqlInstance; } From 18280ee018d82acef601fc022a5c6923bfb7a451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMA?= Date: Mon, 2 Dec 2019 09:39:40 +0100 Subject: [PATCH 002/602] Describes connecting to GCP CloudSQL postgresql instance thour service account --- .../asciidoc/deploying-to-google-cloud.adoc | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 docs/src/main/asciidoc/deploying-to-google-cloud.adoc diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc new file mode 100644 index 0000000000000..e953398b1b0fc --- /dev/null +++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc @@ -0,0 +1,109 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Deploying on Google cloud platform + +include::./attributes.adoc[] + +This guide covers: + +* Connecting to a CloudSQL postgresql instance with quarkus-agroal and service account using CloudRun + +== Prerequisites + +For this guide you need: + +* roughly 1 hour +* having access to an Google cloud plateform project with owner rights. + +This guide will take as input an application developed in the link:datasource.adoc[datasource guide]. + +Make sure you have the application at hand working locally. + + +== Solution + +We recommend to follow the instructions in the next sections and build the application step by step. + +== Connecting to CloudSQL + +Google CloudSQL managed service allows 4 kinds of connection : + +. Using public IP +. Using private IP +. Using Cloud SQL Proxy +. Using service account + +The first two don't need anymore details, simply connect to you database following the link:datasource.adoc[datasource guide]. +The third allows you to connect as if locally, but is not available for AppEngine or CloudRun where you only deploy one artefact. +Please check link:https://cloud.google.com/sql/docs/postgres/external-connection-methods?hl=en[Connection options for external applications]. + +This guide will help you through the fourth possibility : connecting using service account. + +== Adding necessary dependencies to your application. + +You will need to add the _Cloud SQL Postgres Socket Factory_ and postgresql driver dependencies. + +With maven : +[source,xml, subs="attributes"] +---- + + + com.google.cloud.sql + postgres-socket-factory + 1.0.15 + + + + org.postgresql + postgresql + 42.2.8 + +---- + +With Gradle : +[source,groovy, subs="attributes"] +---- +// https://mvnrepository.com/artifact/com.google.cloud.sql/postgres-socket-factory +compile group: 'com.google.cloud.sql', name: 'postgres-socket-factory', version: '1.0.15' +// https://mvnrepository.com/artifact/org.postgresql/postgresql +compile group: 'org.postgresql', name: 'postgresql', version: '42.2.8' +---- + +== Configuring the CloudSQL instance + +If you haven't already please follow the link::https://cloud.google.com/sql/docs/postgres/create-instance[Creating instances guide]. + +Ensure you have a service account with _Cloud SQL Client_ role (minimum necessary role), and get your instance connection name (`PROJECT_ID:REGION:INSTANCE_ID`). + +== Configuring the application + +Configure your quarkus application as follows : + +[source,properties] +-- +quarkus.datasource.url=jdbc:postgresql:///${DB_NAME}?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=${CLOUD_SQL_INSTANCE} +quarkus.datasource.driver=org.postgresql.Driver +quarkus.datasource.username = ${DB_USER} +quarkus.datasource.password = ${DB_PASSWORD} +-- + +Prefere the use of environment variables that will allow you to connect to all your env depending on your runtime values + +WARNING: Don't forget to add your service account key to the deployed image, and to accept requests on the expected port + +[source,docker] +-- +FROM gcr.io/distroless/java:11 +COPY target/*.jar /app/application-runner.jar +COPY .json /app +WORKDIR /app +CMD ["application-runner.jar","-Dquarkus.http.host=0.0.0.0", "-Dquarkus.http.port=$PORT"] +-- + +NOTE: `$PORT` value will be injected automatically by CloudRun and is to be used for HealthCheck + + +You should now be able to connect to your instance. From 9dd3533272b612a6bd93452ee16e8228a7f9c6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMA?= Date: Tue, 3 Dec 2019 09:08:24 +0100 Subject: [PATCH 003/602] Typo --- docs/src/main/asciidoc/deploying-to-google-cloud.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc index e953398b1b0fc..5c28064d138fc 100644 --- a/docs/src/main/asciidoc/deploying-to-google-cloud.adoc +++ b/docs/src/main/asciidoc/deploying-to-google-cloud.adoc @@ -16,7 +16,7 @@ This guide covers: For this guide you need: * roughly 1 hour -* having access to an Google cloud plateform project with owner rights. +* having access to an Google cloud platform project with owner rights. This guide will take as input an application developed in the link:datasource.adoc[datasource guide]. From 43f968d50b0623b2acca30fded579f9f25a4c404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMA?= Date: Thu, 5 Dec 2019 08:54:08 +0100 Subject: [PATCH 004/602] Eclipse formatting --- .../runtime/AbstractDataSourceProducer.java | 5 +- extensions/arc/runtime/pom.xml | 92 +++++++++---------- 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java index 33842e54cb3f0..d351ceb75ad05 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AbstractDataSourceProducer.java @@ -205,9 +205,8 @@ public boolean isValid(Connection connection) { throw new RuntimeException("Cloud sql instance should match the pattern project-id:zone:cloudSqlInstance"); } agroalConnectionFactoryConfigurationSupplier.jdbcUrl( - MessageFormat.format("{0}?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance={1}" - ,url - , cloudSqlInstance)); + MessageFormat.format("{0}?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance={1}", + url, cloudSqlInstance)); } diff --git a/extensions/arc/runtime/pom.xml b/extensions/arc/runtime/pom.xml index 8f0d2a3bc607d..bd99441c3cb12 100644 --- a/extensions/arc/runtime/pom.xml +++ b/extensions/arc/runtime/pom.xml @@ -1,52 +1,52 @@ - - quarkus-arc-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + quarkus-arc-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 - quarkus-arc - Quarkus - ArC - Runtime - Build time CDI dependency injection + quarkus-arc + Quarkus - ArC - Runtime + Build time CDI dependency injection - - - io.quarkus.arc - arc - - - io.quarkus - quarkus-core - - - org.eclipse.microprofile.context-propagation - microprofile-context-propagation-api - - + + + io.quarkus.arc + arc + + + io.quarkus + quarkus-core + + + org.eclipse.microprofile.context-propagation + microprofile-context-propagation-api + + - - - - io.quarkus - quarkus-bootstrap-maven-plugin - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + From 691303e72ba96971cce0b73849b8e0c7e4ca0998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMA?= Date: Mon, 9 Dec 2019 08:42:03 +0100 Subject: [PATCH 005/602] Removed added dependencies. They should be imported by the applications them selves --- extensions/agroal/deployment/pom.xml | 12 -------- .../agroal/test/GcpDataSourceConfigTest.java | 28 +++++++++++-------- .../application-gcp-datasource.properties | 4 +-- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 4d6654275ef55..03167b34a1efd 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -46,18 +46,6 @@ quarkus-test-h2 test - - com.google.cloud.sql - postgres-socket-factory - 1.0.15 - test - - - org.postgresql - postgresql - 42.2.8 - test - diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java index 53d459593bc2f..b7ed6ebbae26c 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/GcpDataSourceConfigTest.java @@ -1,19 +1,21 @@ package io.quarkus.agroal.test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.SQLException; +import java.time.Duration; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + import io.agroal.api.AgroalDataSource; import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; import io.agroal.narayana.NarayanaTransactionIntegration; import io.quarkus.test.QuarkusUnitTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import javax.inject.Inject; -import java.sql.SQLException; -import java.time.Duration; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; public class GcpDataSourceConfigTest { @@ -34,12 +36,14 @@ public void testGCPDataSourceInjection() throws SQLException { } private static void testDataSource(AgroalDataSource dataSource, String username, int minSize, int maxSize, - int initialSize, Duration backgroundValidationInterval, Duration acquisitionTimeout, Duration leakDetectionInterval, - Duration idleRemovalInterval, Duration maxLifetime, String newConnectionSql) throws SQLException { + int initialSize, Duration backgroundValidationInterval, Duration acquisitionTimeout, Duration leakDetectionInterval, + Duration idleRemovalInterval, Duration maxLifetime, String newConnectionSql) throws SQLException { AgroalConnectionPoolConfiguration configuration = dataSource.getConfiguration().connectionPoolConfiguration(); AgroalConnectionFactoryConfiguration agroalConnectionFactoryConfiguration = configuration .connectionFactoryConfiguration(); - assertEquals("jdbc:postgresql:///database-name?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=project:zone:db-name", agroalConnectionFactoryConfiguration.jdbcUrl()); + assertEquals( + "jdbc:h2:tcp://localhost/mem:default?socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=project:zone:db-name", + agroalConnectionFactoryConfiguration.jdbcUrl()); assertEquals(username, agroalConnectionFactoryConfiguration.principal().getName()); assertEquals(minSize, configuration.minSize()); assertEquals(maxSize, configuration.maxSize()); diff --git a/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties b/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties index 29f86e9c94d02..9ed2a363cea78 100644 --- a/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties +++ b/extensions/agroal/deployment/src/test/resources/application-gcp-datasource.properties @@ -1,6 +1,6 @@ #tag::basic[] -quarkus.datasource.url = jdbc:postgresql:///database-name -quarkus.datasource.driver = org.postgresql.Driver +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:default +quarkus.datasource.driver=org.h2.Driver quarkus.datasource.username=username-default quarkus.datasource.min-size=3 quarkus.datasource.max-size=13 From 3f64422616f07ac73197519bfed29a938ddf3e6a Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 21 Nov 2019 16:06:55 +0100 Subject: [PATCH 006/602] Make sure the extension deployment artifact versions picked for the build correspond to the runtime artifact versions when including extensions integrated into the platform --- .../resolver/BootstrapAppModelResolver.java | 7 +- .../maven/BuildDependencyGraphVisitor.java | 2 +- .../DeploymentInjectingDependencyVisitor.java | 109 ++++++++++-------- .../resolver/CollectDependenciesBase.java | 38 +++++- .../bootstrap/resolver/TsQuarkusExt.java | 4 +- .../ManagedReplacedDependencyTestCase.java | 57 +++++++++ 6 files changed, 163 insertions(+), 54 deletions(-) create mode 100644 independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/ManagedReplacedDependencyTestCase.java diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java index 3d89de5226241..9b145acff24bb 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java @@ -25,13 +25,14 @@ import org.eclipse.aether.util.graph.transformer.ConflictMarker; import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor; import org.eclipse.aether.version.Version; + +import io.quarkus.bootstrap.BootstrapDependencyProcessingException; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.maven.BuildDependencyGraphVisitor; import io.quarkus.bootstrap.resolver.maven.DeploymentInjectingDependencyVisitor; -import io.quarkus.bootstrap.resolver.maven.DeploymentInjectionException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.SimpleDependencyGraphTransformationContext; @@ -170,8 +171,8 @@ public boolean visitLeave(DependencyNode node) { final DeploymentInjectingDependencyVisitor deploymentInjector = new DeploymentInjectingDependencyVisitor(mvn, managedDeps, mvn.aggregateRepositories(managedRepos, mvn.newResolutionRepositories(mvn.resolveDescriptor(toAetherArtifact(appArtifact)).getRepositories()))); try { - resolvedDeps.accept(new TreeDependencyVisitor(deploymentInjector)); - } catch (DeploymentInjectionException e) { + deploymentInjector.injectDeploymentDependencies(resolvedDeps); + } catch (BootstrapDependencyProcessingException e) { throw new AppModelResolverException("Failed to inject extension deployment dependencies for " + resolvedDeps.getArtifact(), e.getCause()); } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java index 28ffcdfb21c6a..9bc93c8bededa 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java @@ -115,7 +115,7 @@ public void visit(DependencyNode node) { private void visitEnter(DependencyNode node) { final Dependency dep = node.getDependency(); if (deploymentNode == null) { - runtimeArtifact = DeploymentInjectingDependencyVisitor.getInjectedDependency(node); + runtimeArtifact = DeploymentInjectingDependencyVisitor.getRuntimeArtifact(node); if (runtimeArtifact != null) { deploymentNode = node; } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java index 5199430f4a419..1b6eaa720ed15 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java @@ -6,16 +6,16 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; - import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.graph.DependencyVisitor; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.util.artifact.JavaScopes; import org.jboss.logging.Logger; import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.BootstrapDependencyProcessingException; @@ -26,26 +26,29 @@ * * @author Alexey Loubyansky */ -public class DeploymentInjectingDependencyVisitor implements DependencyVisitor { +public class DeploymentInjectingDependencyVisitor { + private static final Logger log = Logger.getLogger(DeploymentInjectingDependencyVisitor.class); - static final String INJECTED_DEPENDENCY = "injected.dep"; + static final String QUARKUS_RUNTIME_ARTIFACT = "quarkus.runtime"; + private static final String QUARKUS_DEPLOYMENT_ARTIFACT = "quarkus.deployment"; - public static Artifact getInjectedDependency(DependencyNode dep) { - return (Artifact) dep.getData().get(DeploymentInjectingDependencyVisitor.INJECTED_DEPENDENCY); + public static Artifact getRuntimeArtifact(DependencyNode dep) { + return (Artifact) dep.getData().get(DeploymentInjectingDependencyVisitor.QUARKUS_RUNTIME_ARTIFACT); } private final MavenArtifactResolver resolver; private final List managedDeps; private final List mainRepos; - private DependencyNode node; boolean injectedDeps; + private List runtimeNodes = new ArrayList<>(); + public DeploymentInjectingDependencyVisitor(MavenArtifactResolver resolver, List managedDeps, List mainRepos) { this.resolver = resolver; - this.managedDeps = managedDeps; + this.managedDeps = managedDeps.isEmpty() ? new ArrayList<>() : managedDeps; this.mainRepos = mainRepos; } @@ -53,79 +56,93 @@ public boolean isInjectedDeps() { return injectedDeps; } - @Override - public boolean visitEnter(DependencyNode node) { + public void injectDeploymentDependencies(DependencyNode root) throws BootstrapDependencyProcessingException { + collectRuntimeExtensions(root.getChildren()); + // resolve and inject deployment dependencies + for(DependencyNode rtNode : runtimeNodes) { + replaceWith(rtNode, collectDependencies((Artifact)rtNode.getData().get(QUARKUS_DEPLOYMENT_ARTIFACT))); + } + } + + public void collectRuntimeExtensions(List list) { + if(list.isEmpty()) { + return; + } + int i = 0; + while (i < list.size()) { + collectRuntimeExtensions(list.get(i++)); + } + } + + private void collectRuntimeExtensions(DependencyNode node) { final Artifact artifact = node.getArtifact(); if(!artifact.getExtension().equals("jar")) { - return true; + return; } - this.node = node; - - boolean processChildren = true; final Path path = resolve(artifact); try { if (Files.isDirectory(path)) { - processChildren = !processMetaInfDir(path.resolve(BootstrapConstants.META_INF)); + processMetaInfDir(node, path.resolve(BootstrapConstants.META_INF)); } else { try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) { - processChildren = !processMetaInfDir(artifactFs.getPath(BootstrapConstants.META_INF)); + processMetaInfDir(node, artifactFs.getPath(BootstrapConstants.META_INF)); } } - } catch (Throwable t) { + } catch (Exception t) { throw new DeploymentInjectionException("Failed to inject extension deplpyment dependencies", t); } - return processChildren; - } - - @Override - public boolean visitLeave(DependencyNode node) { - return true; + collectRuntimeExtensions(node.getChildren()); } - private boolean processMetaInfDir(Path metaInfDir) throws BootstrapDependencyProcessingException { + private void processMetaInfDir(DependencyNode node, Path metaInfDir) throws BootstrapDependencyProcessingException { if (!Files.exists(metaInfDir)) { - return false; + return; } final Path p = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); if (!Files.exists(p)) { - return false; + return; } - processPlatformArtifact(p); - return true; + processPlatformArtifact(node, p); } - private void processPlatformArtifact(Path descriptor) throws BootstrapDependencyProcessingException { + private void processPlatformArtifact(DependencyNode node, Path descriptor) throws BootstrapDependencyProcessingException { final Properties rtProps = resolveDescriptor(descriptor); if(rtProps == null) { return; } - log.debugf("Processing Quarkus extension %s", node); - - String value = rtProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + final String value = rtProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + if(value == null) { + return; + } if(value != null) { - replaceWith(collectDependencies(toArtifact(value))); + Artifact deploymentArtifact = toArtifact(value); + if(deploymentArtifact.getVersion() == null || deploymentArtifact.getVersion().isEmpty()) { + deploymentArtifact = deploymentArtifact.setVersion(node.getArtifact().getVersion()); + } + node.setData(QUARKUS_DEPLOYMENT_ARTIFACT, deploymentArtifact); + runtimeNodes.add(node); + managedDeps.add(new Dependency(node.getArtifact(), JavaScopes.COMPILE)); + managedDeps.add(new Dependency(deploymentArtifact, JavaScopes.COMPILE)); } } - private void replaceWith(DependencyNode depNode) throws BootstrapDependencyProcessingException { - List children = depNode.getChildren(); + private void replaceWith(DependencyNode originalNode, DependencyNode newNode) throws BootstrapDependencyProcessingException { + List children = newNode.getChildren(); if (children.isEmpty()) { throw new BootstrapDependencyProcessingException( - "No dependencies collected for Quarkus extension deployment artifact " + depNode.getArtifact() - + " while at least the corresponding runtime artifact " + node.getArtifact() + " is expected"); - } - log.debugf("Injecting deployment dependency %s", depNode); - node.setData(INJECTED_DEPENDENCY, node.getArtifact()); - node.setArtifact(depNode.getArtifact()); - node.getDependency().setArtifact(depNode.getArtifact()); - node.setChildren(children); + "No dependencies collected for Quarkus extension deployment artifact " + newNode.getArtifact() + + " while at least the corresponding runtime artifact " + originalNode.getArtifact() + " is expected"); + } + log.debugf("Injecting deployment dependency %s", newNode); + + originalNode.setData(QUARKUS_RUNTIME_ARTIFACT, originalNode.getArtifact()); + originalNode.setArtifact(newNode.getArtifact()); + originalNode.getDependency().setArtifact(newNode.getArtifact()); + originalNode.setChildren(children); injectedDeps = true; } private DependencyNode collectDependencies(Artifact artifact) throws BootstrapDependencyProcessingException { - if(artifact.getVersion().isEmpty()) { - artifact = artifact.setVersion(node.getArtifact().getVersion()); - } try { return managedDeps.isEmpty() ? resolver.collectDependencies(artifact, Collections.emptyList(), mainRepos).getRoot() : resolver.collectManagedDependencies(artifact, Collections.emptyList(), managedDeps, mainRepos).getRoot(); diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java index 0c8fbb354a4d5..3a6258bc50bc2 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java @@ -19,6 +19,7 @@ public abstract class CollectDependenciesBase extends ResolverSetupCleanup { protected TsArtifact root; protected List expectedResult = Collections.emptyList(); + protected List deploymentDeps = Collections.emptyList(); @Override @BeforeEach @@ -33,8 +34,17 @@ public void setup() throws Exception { @Test public void testCollectedDependencies() throws Exception { install(root); + + List expected; + if(deploymentDeps.isEmpty()) { + expected = expectedResult; + } else { + expected = new ArrayList<>(expectedResult.size() + deploymentDeps.size()); + expected.addAll(expectedResult); + expected.addAll(deploymentDeps); + } final List resolvedDeps = resolver.resolveModel(root.toAppArtifact()).getAllDependencies(); - assertEquals(expectedResult, resolvedDeps); + assertEquals(expected, resolvedDeps); } protected TsArtifact install(TsArtifact dep, boolean collected) { @@ -57,6 +67,25 @@ protected TsArtifact install(TsArtifact dep, Path p, String collectedInScope) { return dep; } + protected void install(TsQuarkusExt ext) { + install(ext, true); + } + + protected void install(TsQuarkusExt ext, boolean collected) { + ext.install(repo); + if(collected) { + addCollectedDep(ext.getRuntime(), "compile", false); + addCollectedDeploymentDep(ext.getDeployment()); + } + } + + protected void installAsDep(TsQuarkusExt ext) { + ext.install(repo); + root.addDependency(ext); + addCollectedDep(ext.getRuntime(), "compile", false); + addCollectedDeploymentDep(ext.getDeployment()); + } + protected void installAsDep(TsArtifact dep) { installAsDep(dep, true); } @@ -102,6 +131,13 @@ protected void addCollectedDep(final TsArtifact artifact, final String scope, bo expectedResult.add(new AppDependency(artifact.toAppArtifact(), scope, optional)); } + protected void addCollectedDeploymentDep(TsArtifact ext) { + if(deploymentDeps.isEmpty()) { + deploymentDeps = new ArrayList<>(); + } + deploymentDeps.add(new AppDependency(ext.toAppArtifact(), "compile", false)); + } + protected void addManagedDep(TsArtifact dep) { root.addManagedDependency(new TsDependency(dep)); } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java index c8803e494422e..8038410c6f6bd 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java @@ -1,7 +1,5 @@ package io.quarkus.bootstrap.resolver; -import java.io.IOException; - import io.quarkus.bootstrap.BootstrapConstants; public class TsQuarkusExt { @@ -34,7 +32,7 @@ public TsQuarkusExt addDependency(TsQuarkusExt ext) { return this; } - public void install(TsRepoBuilder repo) throws IOException { + public void install(TsRepoBuilder repo) { repo.install(deployment); repo.install(runtime); } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/ManagedReplacedDependencyTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/ManagedReplacedDependencyTestCase.java new file mode 100644 index 0000000000000..87ddcd7c42aa9 --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/ManagedReplacedDependencyTestCase.java @@ -0,0 +1,57 @@ +package io.quarkus.bootstrap.resolver.replace.test; + +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +/** + * + * @author Alexey Loubyansky + */ +public class ManagedReplacedDependencyTestCase extends CollectDependenciesBase { + + @Override + protected void setupDependencies() throws Exception { + + // install ext 1.0.X in the repo + final TsQuarkusExt ext100 = new TsQuarkusExt("ext1", "100"); + install(ext100, false); + final TsQuarkusExt ext101 = new TsQuarkusExt("ext1", "101"); + install(ext101, false); + final TsQuarkusExt ext102 = new TsQuarkusExt("ext1", "102"); + install(ext102, false); + final TsQuarkusExt ext103 = new TsQuarkusExt("ext1", "103"); + install(ext103, true); + + // install ext 2.0.0 and add it as a direct dependency + final TsQuarkusExt ext200 = new TsQuarkusExt("ext2", "200"); + ext200.addDependency(ext100); + installAsDep(ext200); + + // install ext 2.0.1 and add it to the dependency management + final TsQuarkusExt ext201 = new TsQuarkusExt("ext2", "201"); + ext201.addDependency(ext101); + install(ext201, false); + + // install ext 3.0.0 + final TsQuarkusExt ext300 = new TsQuarkusExt("ext3", "300"); + ext300.addDependency(ext200); + install(ext300, false); + + // install ext 3.0.1 + final TsQuarkusExt ext301 = new TsQuarkusExt("ext3", "301"); + ext301.addDependency(ext201); + install(ext301, false); + + // add a dependency on ext3 (no version) + root.addDependency(TsArtifact.jar(ext300.getRuntime().getArtifactId(), null)); + + // the dependency management + addManagedDep(ext103.getRuntime()); + addManagedDep(ext201.getRuntime()); + addManagedDep(ext301.getRuntime()); + + addCollectedDep(ext301.getRuntime()); + addCollectedDeploymentDep(ext301.getDeployment()); + } +} From 00442d70e6c20c9c6bebc77d3ef42cabc36d6a71 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 21 Nov 2019 18:31:21 +0100 Subject: [PATCH 007/602] This fixes list (and also add) extensions commands in Gradle (by using the descriptor stored in a ThreadLocal) and in Maven (by properly resolving the BOM coordinates) --- .../io/quarkus/maven/BuildFileMojoBase.java | 33 +++++++++++-------- .../quarkus/cli/commands/AddExtensions.java | 7 ++-- .../rest/BasicRestProjectGenerator.java | 3 +- .../io/quarkus/maven/utilities/MojoUtils.java | 10 +++--- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/BuildFileMojoBase.java b/devtools/maven/src/main/java/io/quarkus/maven/BuildFileMojoBase.java index c985e767a5b8e..1acebb786f885 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildFileMojoBase.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildFileMojoBase.java @@ -86,23 +86,20 @@ public void execute() throws MojoExecutionException { continue; } // We don't know which BOM is the platform one, so we are trying every BOM here - String bomVersion = dep.getVersion(); - if (bomVersion.startsWith("${") && bomVersion.endsWith("}")) { - final String prop = bomVersion.substring(2, bomVersion.length() - 1); - bomVersion = mvnBuild.getProperty(prop); - if (bomVersion == null) { - getLog().debug("Failed to resolve version of " + dep); - continue; - } + final String bomVersion = resolveValue(dep.getVersion(), buildFile); + final String bomGroupId = resolveValue(dep.getGroupId(), buildFile); + final String bomArtifactId = resolveValue(dep.getArtifactId(), buildFile); + if (bomVersion == null || bomGroupId == null || bomArtifactId == null) { + continue; } - Artifact jsonArtifact = new DefaultArtifact(dep.getGroupId(), dep.getArtifactId(), dep.getClassifier(), - "json", bomVersion); + + Artifact jsonArtifact = new DefaultArtifact(bomGroupId, bomArtifactId, null, "json", bomVersion); try { jsonArtifact = mvn.resolve(jsonArtifact).getArtifact(); } catch (Exception e) { log.debug("Failed to resolve JSON descriptor as %s", jsonArtifact); - jsonArtifact = new DefaultArtifact(dep.getGroupId(), dep.getArtifactId() + "-descriptor-json", - dep.getClassifier(), "json", bomVersion); + jsonArtifact = new DefaultArtifact(bomGroupId, bomArtifactId + "-descriptor-json", null, "json", + bomVersion); try { jsonArtifact = mvn.resolve(jsonArtifact).getArtifact(); } catch (Exception e1) { @@ -113,7 +110,6 @@ public void execute() throws MojoExecutionException { descrArtifact = jsonArtifact; break; } - if (descrArtifact != null) { log.debug("Quarkus platform JSON descriptor resolved from %s", descrArtifact); final QuarkusPlatformDescriptor platform = QuarkusJsonPlatformDescriptorResolver.newInstance() @@ -153,4 +149,15 @@ protected void validateParameters() throws MojoExecutionException { } protected abstract void doExecute(BuildFile buildFile) throws MojoExecutionException; + + private String resolveValue(String expr, BuildFile buildFile) throws IOException { + if (expr.startsWith("${") && expr.endsWith("}")) { + final String v = buildFile.getProperty(expr.substring(2, expr.length() - 1)); + if (v == null) { + getLog().debug("Failed to resolve version of " + v); + } + return v; + } + return expr; + } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index 614e08289440b..c7a108d5f48e1 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -17,7 +17,6 @@ import io.quarkus.dependencies.Extension; import io.quarkus.generators.BuildTool; import io.quarkus.maven.utilities.MojoUtils; -import io.quarkus.platform.tools.config.QuarkusPlatformConfig; public class AddExtensions { @@ -55,9 +54,9 @@ static SelectionResult select(String query, List extensions, boolean if (matchesNameOrArtifactId.size() == 1) { return new SelectionResult(matchesNameOrArtifactId, true); } - + extensions = extensions.stream().filter(e -> !e.isUnlisted()).collect(Collectors.toList()); - + // Try short names Set matchesShortName = extensions.stream().filter(extension -> matchesShortName(extension, q)) .collect(Collectors.toSet()); @@ -238,6 +237,6 @@ public AddExtensionResult addExtensions(final Set extensions) throws IOE } private List getDependenciesFromBom() { - return QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor().getManagedDependencies(); + return MojoUtils.getPlatformDescriptor().getManagedDependencies(); } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java index bdefc77adb6e0..f004f131b9688 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java @@ -13,7 +13,6 @@ import io.quarkus.generators.ProjectGenerator; import io.quarkus.generators.SourceType; import io.quarkus.maven.utilities.MojoUtils; -import io.quarkus.platform.tools.config.QuarkusPlatformConfig; public class BasicRestProjectGenerator implements ProjectGenerator { @@ -124,7 +123,7 @@ private void generate(final String templateName, final Map conte throws IOException { if (!writer.exists(outputFilePath)) { String path = templateName; - String template = QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor().getTemplate(path); + String template = MojoUtils.getPlatformDescriptor().getTemplate(path); if (template == null) { throw new IOException("Template resource is missing: " + path); } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java index e263c845d1a24..97f021f9e3206 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java @@ -67,10 +67,10 @@ private static String toPropExpr(String name) { private static Properties properties; - private static QuarkusPlatformDescriptor platformDescr; - - private static QuarkusPlatformDescriptor getPlatformDescriptor() { - return platformDescr == null ? platformDescr = QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor() : platformDescr; + public static QuarkusPlatformDescriptor getPlatformDescriptor() { + return QuarkusPlatformConfig.hasThreadLocal() + ? QuarkusPlatformConfig.getThreadLocal().getPlatformDescriptor() + : QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor(); } private static Properties getProperties() { @@ -269,7 +269,7 @@ public static void write(Model model, OutputStream fileOutputStream) throws IOEx } public static List loadExtensions() { - return QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor().getExtensions(); + return getPlatformDescriptor().getExtensions(); } public static String credentials(final Dependency d) { From f34b92d5f571e413e064a546555e80cf8c40c06a Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 19 Sep 2019 14:45:31 -0500 Subject: [PATCH 008/602] Add pluggable log handler and formatter capability Co-authored-by: ia3andy --- .../builditem/LogConsoleFormatBuildItem.java | 36 ++++++++++++++ .../builditem/LogHandlerBuildItem.java | 34 +++++++++++++ .../logging/LoggingResourceProcessor.java | 13 ++++- .../runtime/logging/ConsoleConfig.java | 11 +++-- .../runtime/logging/LoggingSetupRecorder.java | 49 +++++++++++++++---- 5 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/LogConsoleFormatBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/LogHandlerBuildItem.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogConsoleFormatBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogConsoleFormatBuildItem.java new file mode 100644 index 0000000000000..a30522477735a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogConsoleFormatBuildItem.java @@ -0,0 +1,36 @@ +package io.quarkus.deployment.builditem; + +import java.util.Optional; +import java.util.logging.Formatter; + +import org.wildfly.common.Assert; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * The log console format build item. Producing this item will cause the logging subsystem to disregard its + * console logging formatting configuration and use the formatter provided instead. If multiple formatters + * are enabled at run time, a warning message is printed and only one is used. + */ +public final class LogConsoleFormatBuildItem extends MultiBuildItem { + private final RuntimeValue> formatterValue; + + /** + * Construct a new instance. + * + * @param formatterValue the optional formatter run time value to use (must not be {@code null}) + */ + public LogConsoleFormatBuildItem(final RuntimeValue> formatterValue) { + this.formatterValue = Assert.checkNotNullParam("formatterValue", formatterValue); + } + + /** + * Get the formatter value. + * + * @return the formatter value + */ + public RuntimeValue> getFormatterValue() { + return formatterValue; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogHandlerBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogHandlerBuildItem.java new file mode 100644 index 0000000000000..963c17912dcc8 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogHandlerBuildItem.java @@ -0,0 +1,34 @@ +package io.quarkus.deployment.builditem; + +import java.util.Optional; +import java.util.logging.Handler; + +import org.wildfly.common.Assert; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * A build item for adding additional logging handlers. + */ +public final class LogHandlerBuildItem extends MultiBuildItem { + private final RuntimeValue> handlerValue; + + /** + * Construct a new instance. + * + * @param handlerValue the handler value to add to the run time configuration + */ + public LogHandlerBuildItem(final RuntimeValue> handlerValue) { + this.handlerValue = Assert.checkNotNullParam("handlerValue", handlerValue); + } + + /** + * Get the handler value. + * + * @return the handler value + */ + public RuntimeValue> getHandlerValue() { + return handlerValue; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index 46cafcd65e7ad..bc90baeec0057 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -1,7 +1,9 @@ package io.quarkus.deployment.logging; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; +import java.util.logging.Handler; import java.util.stream.Collectors; import org.jboss.logmanager.EmbeddedConfigurator; @@ -11,11 +13,14 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.LogCategoryBuildItem; +import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem; +import io.quarkus.deployment.builditem.LogHandlerBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.runtime.logging.LogConfig; import io.quarkus.runtime.logging.LoggingSetupRecorder; @@ -73,8 +78,12 @@ void miscSetup( @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - void setupLoggingRuntimeInit(LoggingSetupRecorder recorder, LogConfig log) { - recorder.initializeLogging(log); + void setupLoggingRuntimeInit(LoggingSetupRecorder recorder, LogConfig log, List handlers, + List consoleFormatItems) { + final List>> list = handlers.stream().map(LogHandlerBuildItem::getHandlerValue) + .collect(Collectors.toList()); + recorder.initializeLogging(log, list, + consoleFormatItems.stream().map(LogConsoleFormatBuildItem::getFormatterValue).collect(Collectors.toList())); } @BuildStep diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/ConsoleConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/ConsoleConfig.java index 0c5ae163e5700..ba9558590ce3c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/ConsoleConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/ConsoleConfig.java @@ -16,13 +16,14 @@ public class ConsoleConfig { boolean enable; /** - * The log format + * The log format. Note that this value will be ignored if an extension is present that takes + * control of console formatting (e.g. an XML or JSON-format extension). */ @ConfigItem(defaultValue = "%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n") String format; /** - * The console log level + * The console log level. */ @ConfigItem(defaultValue = "ALL") Level level; @@ -30,12 +31,16 @@ public class ConsoleConfig { /** * If the console logging should be in color. If undefined quarkus takes * best guess based on operating system and environment. + * Note that this value will be ignored if an extension is present that takes + * control of console formatting (e.g. an XML or JSON-format extension). */ @ConfigItem Optional color; /** - * Specify how much the colors should be darkened + * Specify how much the colors should be darkened. + * Note that this value will be ignored if an extension is present that takes + * control of console formatting (e.g. an XML or JSON-format extension). */ @ConfigItem(defaultValue = "0") int darken; diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index 31d523acfb59e..56a1521836bca 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -10,7 +10,9 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.logging.ErrorManager; +import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; @@ -29,6 +31,7 @@ import org.jboss.logmanager.handlers.SizeRotatingFileHandler; import org.jboss.logmanager.handlers.SyslogHandler; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; /** @@ -62,7 +65,9 @@ public class LoggingSetupRecorder { public LoggingSetupRecorder() { } - public void initializeLogging(LogConfig config) { + public void initializeLogging(LogConfig config, final List>> additionalHandlers, + final List>> possibleFormatters) { + final Map categories = config.categories; final LogContext logContext = LogContext.getLogContext(); final Logger rootLogger = logContext.getLogger(""); @@ -81,9 +86,9 @@ public void initializeLogging(LogConfig config) { for (Entry entry : filters.entrySet()) { filterElements.add(new LogCleanupFilterElement(entry.getKey(), entry.getValue().ifStartsWith)); } - ArrayList handlers = new ArrayList<>(3); + ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); if (config.console.enable) { - errorManager = configureConsoleHandler(config.console, errorManager, filterElements, handlers); + errorManager = configureConsoleHandler(config.console, errorManager, filterElements, handlers, possibleFormatters); } if (config.file.enable) { @@ -94,6 +99,16 @@ public void initializeLogging(LogConfig config) { configureSyslogHandler(config.syslog, errorManager, filterElements, handlers); } + for (RuntimeValue> additionalHandler : additionalHandlers) { + final Optional optional = additionalHandler.getValue(); + if (optional.isPresent()) { + final Handler handler = optional.get(); + handler.setErrorManager(errorManager); + handler.setFilter(new LogCleanupFilter(filterElements)); + handlers.add(handler); + } + } + InitialConfigurator.DELAYED_HANDLER.setHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS)); } @@ -120,12 +135,25 @@ private boolean hasColorSupport() { } private ErrorManager configureConsoleHandler(ConsoleConfig config, ErrorManager errorManager, - List filterElements, ArrayList handlers) { - final PatternFormatter formatter; - if (config.color.orElse(hasColorSupport())) { - formatter = new ColorPatternFormatter(config.darken, config.format); - } else { - formatter = new PatternFormatter(config.format); + List filterElements, ArrayList handlers, + List>> possibleFormatters) { + Formatter formatter = null; + boolean formatterWarning = false; + for (RuntimeValue> value : possibleFormatters) { + if (formatter != null) { + formatterWarning = true; + } + final Optional val = value.getValue(); + if (val.isPresent()) { + formatter = val.get(); + } + } + if (formatter == null) { + if (config.color.orElse(Boolean.valueOf(hasColorSupport())).booleanValue()) { + formatter = new ColorPatternFormatter(config.darken, config.format); + } else { + formatter = new PatternFormatter(config.format); + } } final ConsoleHandler handler = new ConsoleHandler(formatter); handler.setLevel(config.level); @@ -141,6 +169,9 @@ private ErrorManager configureConsoleHandler(ConsoleConfig config, ErrorManager handlers.add(handler); } errorManager = handler.getLocalErrorManager(); + if (formatterWarning) { + errorManager.error("Multiple formatters were activated", null, ErrorManager.GENERIC_FAILURE); + } return errorManager; } From 26ee8514f05cf47b8149f2c0bf482f794cef2dad Mon Sep 17 00:00:00 2001 From: ia3andy Date: Fri, 20 Sep 2019 11:35:14 +0200 Subject: [PATCH 009/602] Slightly clean LoggingSetupRecorder for readability --- .../runtime/logging/LoggingSetupRecorder.java | 133 +++++++++--------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index 56a1521836bca..77fae3133cab5 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -40,24 +40,24 @@ @Recorder public class LoggingSetupRecorder { - static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); + private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); /** * ConEmu ANSI X3.64 support enabled, * used by cmder */ - static final boolean IS_CON_EMU_ANSI = IS_WINDOWS && "ON".equals(System.getenv("ConEmuANSI")); + private static final boolean IS_CON_EMU_ANSI = IS_WINDOWS && "ON".equals(System.getenv("ConEmuANSI")); /** * These tests are same as used in jansi * Source: https://github.com/fusesource/jansi/commit/bb3d538315c44f799d34fd3426f6c91c8e8dfc55 */ - static final boolean IS_CYGWIN = IS_WINDOWS + private static final boolean IS_CYGWIN = IS_WINDOWS && System.getenv("PWD") != null && System.getenv("PWD").startsWith("/") && !"cygwin".equals(System.getenv("TERM")); - static final boolean IS_MINGW_XTERM = IS_WINDOWS + private static final boolean IS_MINGW_XTERM = IS_WINDOWS && System.getenv("MSYSTEM") != null && System.getenv("MSYSTEM").startsWith("MINGW") && "xterm".equals(System.getenv("TERM")); @@ -71,7 +71,7 @@ public void initializeLogging(LogConfig config, final List categories = config.categories; final LogContext logContext = LogContext.getLogContext(); final Logger rootLogger = logContext.getLogger(""); - ErrorManager errorManager = new OnlyOnceErrorManager(); + rootLogger.setLevel(config.level.orElse(Level.INFO)); for (Map.Entry entry : categories.entrySet()) { final String name = entry.getKey(); @@ -86,17 +86,25 @@ public void initializeLogging(LogConfig config, final List entry : filters.entrySet()) { filterElements.add(new LogCleanupFilterElement(entry.getKey(), entry.getValue().ifStartsWith)); } - ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); + final ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); + ErrorManager errorManager = new OnlyOnceErrorManager(); + if (config.console.enable) { - errorManager = configureConsoleHandler(config.console, errorManager, filterElements, handlers, possibleFormatters); + final Handler consoleHandler = configureConsoleHandler(config.console, errorManager, filterElements, + possibleFormatters); + errorManager = consoleHandler.getErrorManager(); + handlers.add(consoleHandler); } if (config.file.enable) { - configureFileHandler(config.file, errorManager, filterElements, handlers); + handlers.add(configureFileHandler(config.file, errorManager, filterElements)); } if (config.syslog.enable) { - configureSyslogHandler(config.syslog, errorManager, filterElements, handlers); + final Handler syslogHandler = configureSyslogHandler(config.syslog, errorManager, filterElements); + if (syslogHandler != null) { + handlers.add(syslogHandler); + } } for (RuntimeValue> additionalHandler : additionalHandlers) { @@ -112,21 +120,26 @@ public void initializeLogging(LogConfig config, final List filterElements, ArrayList handlers, - List>> possibleFormatters) { + private static Handler configureConsoleHandler(final ConsoleConfig config, final ErrorManager defaultErrorManager, + final List filterElements, + final List>> possibleFormatters) { Formatter formatter = null; boolean formatterWarning = false; for (RuntimeValue> value : possibleFormatters) { @@ -149,34 +162,29 @@ private ErrorManager configureConsoleHandler(ConsoleConfig config, ErrorManager } } if (formatter == null) { - if (config.color.orElse(Boolean.valueOf(hasColorSupport())).booleanValue()) { + if (config.color.orElse(hasColorSupport())) { formatter = new ColorPatternFormatter(config.darken, config.format); } else { formatter = new PatternFormatter(config.format); } } - final ConsoleHandler handler = new ConsoleHandler(formatter); - handler.setLevel(config.level); - handler.setErrorManager(errorManager); - handler.setFilter(new LogCleanupFilter(filterElements)); - if (config.async.enable) { - final AsyncHandler asyncHandler = new AsyncHandler(config.async.queueLength); - asyncHandler.setOverflowAction(config.async.overflow); - asyncHandler.addHandler(handler); - asyncHandler.setLevel(config.level); - handlers.add(asyncHandler); - } else { - handlers.add(handler); - } - errorManager = handler.getLocalErrorManager(); + final ConsoleHandler consoleHandler = new ConsoleHandler(formatter); + consoleHandler.setLevel(config.level); + consoleHandler.setErrorManager(defaultErrorManager); + consoleHandler.setFilter(new LogCleanupFilter(filterElements)); + + final Handler handler = config.async.enable ? createAsyncHandler(config.async, config.level, consoleHandler) + : consoleHandler; + if (formatterWarning) { - errorManager.error("Multiple formatters were activated", null, ErrorManager.GENERIC_FAILURE); + handler.getErrorManager().error("Multiple formatters were activated", null, ErrorManager.GENERIC_FAILURE); } - return errorManager; + + return handler; } - private void configureFileHandler(FileConfig config, ErrorManager errorManager, - List filterElements, ArrayList handlers) { + private static Handler configureFileHandler(final FileConfig config, final ErrorManager errorManager, + final List filterElements) { FileHandler handler = new FileHandler(); FileConfig.RotationConfig rotationConfig = config.rotation; if (rotationConfig.maxFileSize.isPresent() && rotationConfig.fileSuffix.isPresent()) { @@ -209,18 +217,14 @@ private void configureFileHandler(FileConfig config, ErrorManager errorManager, handler.setLevel(config.level); handler.setFilter(new LogCleanupFilter(filterElements)); if (config.async.enable) { - final AsyncHandler asyncHandler = new AsyncHandler(config.async.queueLength); - asyncHandler.setOverflowAction(config.async.overflow); - asyncHandler.addHandler(handler); - asyncHandler.setLevel(config.level); - handlers.add(asyncHandler); - } else { - handlers.add(handler); + return createAsyncHandler(config.async, config.level, handler); } + return handler; } - private void configureSyslogHandler(SyslogConfig config, ErrorManager errorManager, - List filterElements, ArrayList handlers) { + private static Handler configureSyslogHandler(final SyslogConfig config, + final ErrorManager errorManager, + final List filterElements) { try { final SyslogHandler handler = new SyslogHandler(config.endpoint.getHostString(), config.endpoint.getPort()); handler.setAppName(config.appName.orElse(getProcessName())); @@ -237,25 +241,20 @@ private void configureSyslogHandler(SyslogConfig config, ErrorManager errorManag handler.setErrorManager(errorManager); handler.setFilter(new LogCleanupFilter(filterElements)); if (config.async.enable) { - final AsyncHandler asyncHandler = new AsyncHandler(config.async.queueLength); - asyncHandler.setOverflowAction(config.async.overflow); - asyncHandler.addHandler(handler); - asyncHandler.setLevel(config.level); - handlers.add(asyncHandler); - } else { - handlers.add(handler); + return createAsyncHandler(config.async, config.level, handler); } + return handler; } catch (IOException e) { errorManager.error("Failed to create syslog handler", e, ErrorManager.OPEN_FAILURE); + return null; } } - public void initializeLoggingForImageBuild() { - if (ImageInfo.inImageBuildtimeCode()) { - final ConsoleHandler handler = new ConsoleHandler(new PatternFormatter( - "%d{HH:mm:ss,SSS} %-5p [%c{1.}] %s%e%n")); - handler.setLevel(Level.INFO); - InitialConfigurator.DELAYED_HANDLER.setHandlers(new Handler[] { handler }); - } + private static AsyncHandler createAsyncHandler(AsyncConfig asyncConfig, Level level, Handler handler) { + final AsyncHandler asyncHandler = new AsyncHandler(asyncConfig.queueLength); + asyncHandler.setOverflowAction(asyncConfig.overflow); + asyncHandler.addHandler(handler); + asyncHandler.setLevel(level); + return asyncHandler; } } From b92e9f2f67ef3203b1c00da8adc8701df825cf74 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 19 Sep 2019 16:19:32 -0500 Subject: [PATCH 010/602] Introduce JSON formatting extension --- bom/deployment/pom.xml | 5 ++ bom/runtime/pom.xml | 5 ++ extensions/logging-json/deployment/pom.xml | 46 +++++++++++++++ .../json/deployment/LoggingJsonSteps.java | 17 ++++++ extensions/logging-json/pom.xml | 22 ++++++++ extensions/logging-json/runtime/pom.xml | 56 +++++++++++++++++++ .../logging/json/runtime/JsonConfig.java | 54 ++++++++++++++++++ .../json/runtime/LoggingJsonRecorder.java | 32 +++++++++++ extensions/pom.xml | 3 + 9 files changed, 240 insertions(+) create mode 100644 extensions/logging-json/deployment/pom.xml create mode 100644 extensions/logging-json/deployment/src/main/java/io/quarkus/logging/json/deployment/LoggingJsonSteps.java create mode 100644 extensions/logging-json/pom.xml create mode 100644 extensions/logging-json/runtime/pom.xml create mode 100644 extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java create mode 100644 extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/LoggingJsonRecorder.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index 0f3b2a1d90516..d971194fab1a3 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -491,6 +491,11 @@ quarkus-vault-deployment ${project.version} + + io.quarkus + quarkus-logging-json-deployment + ${project.version} + io.quarkus diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 912ea68cfca06..11693a600d96f 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -707,6 +707,11 @@ quarkus-vault-spi ${project.version} + + io.quarkus + quarkus-logging-json + ${project.version} + diff --git a/extensions/logging-json/deployment/pom.xml b/extensions/logging-json/deployment/pom.xml new file mode 100644 index 0000000000000..ec7943d460791 --- /dev/null +++ b/extensions/logging-json/deployment/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + io.quarkus + quarkus-logging-json-parent + 999-SNAPSHOT + ../ + + + quarkus-logging-json-deployment + Quarkus - JSON Logging - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-logging-json + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + + \ No newline at end of file diff --git a/extensions/logging-json/deployment/src/main/java/io/quarkus/logging/json/deployment/LoggingJsonSteps.java b/extensions/logging-json/deployment/src/main/java/io/quarkus/logging/json/deployment/LoggingJsonSteps.java new file mode 100644 index 0000000000000..f09287aae3204 --- /dev/null +++ b/extensions/logging-json/deployment/src/main/java/io/quarkus/logging/json/deployment/LoggingJsonSteps.java @@ -0,0 +1,17 @@ +package io.quarkus.logging.json.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem; +import io.quarkus.logging.json.runtime.JsonConfig; +import io.quarkus.logging.json.runtime.LoggingJsonRecorder; + +public final class LoggingJsonSteps { + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public LogConsoleFormatBuildItem setUpFormatter(LoggingJsonRecorder recorder, JsonConfig config) { + return new LogConsoleFormatBuildItem(recorder.initializeJsonLogging(config)); + } +} diff --git a/extensions/logging-json/pom.xml b/extensions/logging-json/pom.xml new file mode 100644 index 0000000000000..d4b1e19361696 --- /dev/null +++ b/extensions/logging-json/pom.xml @@ -0,0 +1,22 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-logging-json-parent + Quarkus - JSON Logging + pom + + + deployment + runtime + + + \ No newline at end of file diff --git a/extensions/logging-json/runtime/pom.xml b/extensions/logging-json/runtime/pom.xml new file mode 100644 index 0000000000000..ab8f071a757f9 --- /dev/null +++ b/extensions/logging-json/runtime/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + io.quarkus + quarkus-logging-json-parent + 999-SNAPSHOT + ../ + + + quarkus-logging-json + + Quarkus - JSON Logging - Runtime + + + + io.quarkus + quarkus-core + + + + io.quarkus + quarkus-jsonp + + + + org.jboss.logmanager + jboss-logmanager-embedded + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + \ No newline at end of file diff --git a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java new file mode 100644 index 0000000000000..1d6f2d0972140 --- /dev/null +++ b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java @@ -0,0 +1,54 @@ +package io.quarkus.logging.json.runtime; + +import java.util.Optional; + +import org.jboss.logmanager.formatters.StructuredFormatter; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration for JSON log formatting. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "log.console.json") +public class JsonConfig { + /** + * Determine whether to enable the JSON console formatting extension, which disables "normal" console formatting. + */ + @ConfigItem(name = ConfigItem.PARENT) + boolean enable; + /** + * Enable "pretty printing" of the JSON record. Note that some JSON parsers will fail to read pretty printed output. + */ + @ConfigItem + boolean prettyPrint; + /** + * The date format to use. The special string "default" indicates that the default format should be used. + */ + @ConfigItem(defaultValue = "default") + String dateFormat; + /** + * The special end-of-record delimiter to be used. By default, no delimiter is used. + */ + @ConfigItem + Optional recordDelimiter; + /** + * The zone ID to use. The special string "default" indicates that the default zone should be used. + */ + @ConfigItem(defaultValue = "default") + String zoneId; + /** + * The exception output type to specify. + */ + @ConfigItem(defaultValue = "detailed") + StructuredFormatter.ExceptionOutputType exceptionOutputType; + /** + * Enable printing of more details in the log. + *

+ * Printing the details can be expensive as the values are retrieved from the caller. The details include the + * source class name, source file name, source method name and source line number. + */ + @ConfigItem + boolean printDetails; +} diff --git a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/LoggingJsonRecorder.java b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/LoggingJsonRecorder.java new file mode 100644 index 0000000000000..639f8cb62a3a5 --- /dev/null +++ b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/LoggingJsonRecorder.java @@ -0,0 +1,32 @@ +package io.quarkus.logging.json.runtime; + +import java.util.Optional; +import java.util.logging.Formatter; + +import org.jboss.logmanager.formatters.JsonFormatter; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class LoggingJsonRecorder { + public RuntimeValue> initializeJsonLogging(final JsonConfig config) { + if (!config.enable) { + return new RuntimeValue<>(Optional.empty()); + } + final JsonFormatter formatter = new JsonFormatter(); + formatter.setPrettyPrint(config.prettyPrint); + final String dateFormat = config.dateFormat; + if (!dateFormat.equals("default")) { + formatter.setDateFormat(dateFormat); + } + formatter.setExceptionOutputType(config.exceptionOutputType); + formatter.setPrintDetails(config.printDetails); + config.recordDelimiter.ifPresent(formatter::setRecordDelimiter); + final String zoneId = config.zoneId; + if (!zoneId.equals("default")) { + formatter.setZoneId(zoneId); + } + return new RuntimeValue<>(Optional.of(formatter)); + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index be34e299fd3f5..bf2c42c061554 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -129,6 +129,9 @@ vault vertx-graphql + + + logging-json + + io.quarkus + quarkus-logging-json + + + +---- + +The presence of this extension will, by default, replace the output format configuration from the console configuration. +This means that the format string and the color settings (if any) will be ignored. The other console configuration items +(including those controlling asynchronous logging and the log level) will continue to be applied. + +===== Configuration + +The JSON logging extension can be configured in various ways. The following properties are supported: + +[cols=" Date: Mon, 23 Sep 2019 10:24:46 +0200 Subject: [PATCH 012/602] Add tests for json formatter and custom log handlers --- .../extest/deployment/TestProcessor.java | 13 +++++ ...application-additional-handlers.properties | 6 ++ .../logging/AdditionalHandlersTest.java | 43 ++++++++++++++ .../AdditionalLogHandlerValueFactory.java | 43 ++++++++++++++ extensions/logging-json/deployment/pom.xml | 21 +++++++ .../json/JsonFormatterCustomConfigTest.java | 56 +++++++++++++++++++ .../json/JsonFormatterDefaultConfigTest.java | 56 +++++++++++++++++++ ...plication-json-formatter-custom.properties | 11 ++++ ...lication-json-formatter-default.properties | 5 ++ 9 files changed, 254 insertions(+) create mode 100644 core/test-extension/deployment/src/main/resources/application-additional-handlers.properties create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java create mode 100644 core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/logging/AdditionalLogHandlerValueFactory.java create mode 100644 extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java create mode 100644 extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java create mode 100644 extensions/logging-json/deployment/src/test/resources/application-json-formatter-custom.properties create mode 100644 extensions/logging-json/deployment/src/test/resources/application-json-formatter-default.properties diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java index e972f4c79ae00..e578f1e26dbb0 100644 --- a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java @@ -45,6 +45,7 @@ import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.LogHandlerBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; @@ -64,6 +65,7 @@ import io.quarkus.extest.runtime.config.TestConfigRoot; import io.quarkus.extest.runtime.config.TestRunTimeConfig; import io.quarkus.extest.runtime.config.XmlConfig; +import io.quarkus.extest.runtime.logging.AdditionalLogHandlerValueFactory; import io.quarkus.extest.runtime.subst.DSAPublicKeyObjectSubstitution; import io.quarkus.extest.runtime.subst.KeyProxy; import io.quarkus.runtime.RuntimeValue; @@ -109,6 +111,17 @@ BeanDefiningAnnotationBuildItem registerBeanDefiningAnnotations() { return new BeanDefiningAnnotationBuildItem(TEST_ANNOTATION, TEST_ANNOTATION_SCOPE); } + /** + * Register an additional log handler + * + * @return LogHandlerBuildItem + */ + @BuildStep + @Record(RUNTIME_INIT) + LogHandlerBuildItem registerAdditionalLogHandler(final AdditionalLogHandlerValueFactory factory) { + return new LogHandlerBuildItem(factory.create()); + } + @BuildStep void registerNativeImageResources() { resource.produce(new NativeImageResourceBuildItem("/DSAPublicKey.encoded")); diff --git a/core/test-extension/deployment/src/main/resources/application-additional-handlers.properties b/core/test-extension/deployment/src/main/resources/application-additional-handlers.properties new file mode 100644 index 0000000000000..a84b1bf3b79de --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-additional-handlers.properties @@ -0,0 +1,6 @@ +quarkus.log.level=INFO +quarkus.log.console.enable=true +quarkus.log.console.level=WARNING +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n +# Resource path to DSAPublicKey base64 encoded bytes +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java new file mode 100644 index 0000000000000..c5a21f0c6d09c --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java @@ -0,0 +1,43 @@ +package io.quarkus.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.extest.runtime.logging.AdditionalLogHandlerValueFactory.TestHandler; +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; + +public class AdditionalHandlersTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-additional-handlers.properties"); + + @Test + public void additionalHandlersConfigurationTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof TestHandler)) + .findFirst().orElse(null); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.FINE); + + TestHandler testHandler = (TestHandler) handler; + assertThat(testHandler.records).isNotEmpty(); + } + +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/logging/AdditionalLogHandlerValueFactory.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/logging/AdditionalLogHandlerValueFactory.java new file mode 100644 index 0000000000000..13d926b02dfba --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/logging/AdditionalLogHandlerValueFactory.java @@ -0,0 +1,43 @@ +package io.quarkus.extest.runtime.logging; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class AdditionalLogHandlerValueFactory { + + public RuntimeValue> create() { + return new RuntimeValue<>(Optional.of(new TestHandler())); + } + + public static class TestHandler extends Handler { + + public final List records = new ArrayList<>(); + + @Override + public void publish(LogRecord record) { + records.add(record); + } + + @Override + public void flush() { + } + + @Override + public Level getLevel() { + return Level.FINE; + } + + @Override + public void close() throws SecurityException { + + } + } +} diff --git a/extensions/logging-json/deployment/pom.xml b/extensions/logging-json/deployment/pom.xml index ec7943d460791..3a79fcbf3e448 100644 --- a/extensions/logging-json/deployment/pom.xml +++ b/extensions/logging-json/deployment/pom.xml @@ -23,6 +23,27 @@ io.quarkus quarkus-logging-json + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-arc-deployment + test + diff --git a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java new file mode 100644 index 0000000000000..4641620751fab --- /dev/null +++ b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java @@ -0,0 +1,56 @@ +package io.quarkus.logging.json; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZoneId; +import java.util.Arrays; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.formatters.JsonFormatter; +import org.jboss.logmanager.formatters.StructuredFormatter; +import org.jboss.logmanager.handlers.ConsoleHandler; +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; + +public class JsonFormatterCustomConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-json-formatter-custom.properties"); + + @Test + public void jsonFormatterCustomConfigurationTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof ConsoleHandler)) + .findFirst().orElse(null); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.WARNING); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(JsonFormatter.class); + JsonFormatter jsonFormatter = (JsonFormatter) formatter; + assertThat(jsonFormatter.isPrettyPrint()).isTrue(); + assertThat(jsonFormatter.getDateTimeFormatter().toString()) + .isEqualTo("Value(DayOfMonth)' 'Text(MonthOfYear,SHORT)' 'Value(Year,4,19,EXCEEDS_PAD)"); + assertThat(jsonFormatter.getDateTimeFormatter().getZone()).isEqualTo(ZoneId.of("UTC+05:00")); + assertThat(jsonFormatter.getExceptionOutputType()) + .isEqualTo(StructuredFormatter.ExceptionOutputType.DETAILED_AND_FORMATTED); + assertThat(jsonFormatter.getRecordDelimiter()).isEqualTo("\n;"); + assertThat(jsonFormatter.isPrintDetails()).isTrue(); + } +} diff --git a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java new file mode 100644 index 0000000000000..fd15d5d34f79f --- /dev/null +++ b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java @@ -0,0 +1,56 @@ +package io.quarkus.logging.json; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.formatters.JsonFormatter; +import org.jboss.logmanager.formatters.StructuredFormatter; +import org.jboss.logmanager.handlers.ConsoleHandler; +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; + +public class JsonFormatterDefaultConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-json-formatter-default.properties"); + + @Test + public void jsonFormatterDefaultConfigurationTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof ConsoleHandler)) + .findFirst().orElse(null); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.WARNING); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(JsonFormatter.class); + JsonFormatter jsonFormatter = (JsonFormatter) formatter; + assertThat(jsonFormatter.isPrettyPrint()).isFalse(); + assertThat(jsonFormatter.getDateTimeFormatter().toString()) + .isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()).toString()); + assertThat(jsonFormatter.getDateTimeFormatter().getZone()).isEqualTo(ZoneId.systemDefault()); + assertThat(jsonFormatter.getExceptionOutputType()).isEqualTo(StructuredFormatter.ExceptionOutputType.DETAILED); + assertThat(jsonFormatter.getRecordDelimiter()).isEqualTo("\n"); + assertThat(jsonFormatter.isPrintDetails()).isFalse(); + } +} diff --git a/extensions/logging-json/deployment/src/test/resources/application-json-formatter-custom.properties b/extensions/logging-json/deployment/src/test/resources/application-json-formatter-custom.properties new file mode 100644 index 0000000000000..485f7fbc6c62f --- /dev/null +++ b/extensions/logging-json/deployment/src/test/resources/application-json-formatter-custom.properties @@ -0,0 +1,11 @@ +quarkus.log.level=INFO +quarkus.log.console.enable=true +quarkus.log.console.level=WARNING +quarkus.log.console.json=true +quarkus.log.console.json.pretty-print=true +quarkus.log.console.json.date-format=d MMM uuuu +quarkus.log.console.json.record-delimiter=\n; +quarkus.log.console.json.zone-id=UTC+05:00 +quarkus.log.console.json.exception-output-type=DETAILED_AND_FORMATTED +quarkus.log.console.json.print-details=true + diff --git a/extensions/logging-json/deployment/src/test/resources/application-json-formatter-default.properties b/extensions/logging-json/deployment/src/test/resources/application-json-formatter-default.properties new file mode 100644 index 0000000000000..359d81b0e740e --- /dev/null +++ b/extensions/logging-json/deployment/src/test/resources/application-json-formatter-default.properties @@ -0,0 +1,5 @@ +quarkus.log.level=INFO +quarkus.log.console.enable=true +quarkus.log.console.level=WARNING +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n +quarkus.log.console.json=true From c343d434bea16b112255312518288b2f42d32783 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Wed, 20 Nov 2019 12:29:57 +0100 Subject: [PATCH 013/602] Change doc id format to be consistent with other anchors --- docs/src/main/asciidoc/logging.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 3885f90a933ca..c75fdadf1de7e 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -40,7 +40,7 @@ The root logger category is handled separately, and is configured via the follow |quarkus.log.level|INFO|The default minimum log level for every log category. |=== -[id="format_string"] +[id="format-string"] == Format String The logging format string supports the following symbols: @@ -75,14 +75,14 @@ The logging format string supports the following symbols: |%x|Nested Diagnostics context values|Renders all the values from Nested Diagnostics Context in format {value1.value2} |=== -[id="alt_console_format"] +[id="alt-console-format"] === Alternative Console Logging Formats It is possible to change the output format of the console log. This can be useful in environments where the output of the Quarkus application is captured by a service which can, for example, process and store the log information for later analysis. -[id="json_logging"] +[id="json-logging"] ==== JSON Logging In order to configure JSON logging, the `quarkus-logging-json` extension may be employed. Add this extension to your From 8e46c3020b400da909a1b54b41a6e20a582d6118 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Wed, 20 Nov 2019 12:30:44 +0100 Subject: [PATCH 014/602] Change JSON Logging extension name in all pom.xml --- extensions/logging-json/deployment/pom.xml | 2 +- extensions/logging-json/pom.xml | 2 +- extensions/logging-json/runtime/pom.xml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/logging-json/deployment/pom.xml b/extensions/logging-json/deployment/pom.xml index 3a79fcbf3e448..dcf12462ed46d 100644 --- a/extensions/logging-json/deployment/pom.xml +++ b/extensions/logging-json/deployment/pom.xml @@ -12,7 +12,7 @@ quarkus-logging-json-deployment - Quarkus - JSON Logging - Deployment + Quarkus - Logging - JSON - Deployment diff --git a/extensions/logging-json/pom.xml b/extensions/logging-json/pom.xml index d4b1e19361696..063b93e5e5f7a 100644 --- a/extensions/logging-json/pom.xml +++ b/extensions/logging-json/pom.xml @@ -11,7 +11,7 @@ 4.0.0 quarkus-logging-json-parent - Quarkus - JSON Logging + Quarkus - Logging - JSON pom diff --git a/extensions/logging-json/runtime/pom.xml b/extensions/logging-json/runtime/pom.xml index ab8f071a757f9..8bb3f44c6e74e 100644 --- a/extensions/logging-json/runtime/pom.xml +++ b/extensions/logging-json/runtime/pom.xml @@ -13,7 +13,8 @@ quarkus-logging-json - Quarkus - JSON Logging - Runtime + Quarkus - Logging - JSON - Runtime + Add JSON formatter for console logging From e6362cc67a11aa915d580f28da3c6d8bdf756635 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Wed, 20 Nov 2019 12:31:14 +0100 Subject: [PATCH 015/602] Add JSON Logging extension metadata file --- .../main/resources/META-INF/quarkus-extension.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 extensions/logging-json/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/extensions/logging-json/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/logging-json/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..01d0cd843ec6c --- /dev/null +++ b/extensions/logging-json/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "Logging JSON" +metadata: + keywords: + - "logging" + - "json" + - "formatter" + categories: + - "core" + status: "preview" + guide: "https://quarkus.io/guides/logging#json-logging" \ No newline at end of file From 024482dded6c19e3fda8ec2749c105f3250b06e4 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Thu, 21 Nov 2019 23:30:55 +0100 Subject: [PATCH 016/602] Add a preview note in the guides of preview extensions --- docs/src/main/asciidoc/amazon-lambda-http.adoc | 8 +++++++- docs/src/main/asciidoc/amazon-lambda.adoc | 8 +++++++- docs/src/main/asciidoc/amqp.adoc | 7 +++++++ docs/src/main/asciidoc/azure-functions-http.adoc | 7 +++++++ docs/src/main/asciidoc/dynamodb.adoc | 7 +++++++ .../main/asciidoc/hibernate-search-elasticsearch.adoc | 8 ++++---- docs/src/main/asciidoc/infinispan-embedded.adoc | 7 +++++++ docs/src/main/asciidoc/jms.adoc | 7 +++++++ docs/src/main/asciidoc/kogito.adoc | 7 +++++++ docs/src/main/asciidoc/kotlin.adoc | 7 +++++++ docs/src/main/asciidoc/mongodb-panache.adoc | 7 ++++++- docs/src/main/asciidoc/mongodb.adoc | 7 +++++++ docs/src/main/asciidoc/neo4j.adoc | 10 ++++++++-- docs/src/main/asciidoc/scheduler.adoc | 9 +++++++-- docs/src/main/asciidoc/security-oauth2.adoc | 7 +++++++ docs/src/main/asciidoc/security-openid-connect.adoc | 7 +++++++ .../main/asciidoc/software-transactional-memory.adoc | 7 +++++++ docs/src/main/asciidoc/spring-data-jpa.adoc | 7 +++++++ docs/src/main/asciidoc/spring-di.adoc | 7 +++++++ docs/src/main/asciidoc/spring-web.adoc | 7 +++++++ docs/src/main/asciidoc/vault.adoc | 7 +++++++ .../src/main/resources/META-INF/quarkus-extension.yaml | 1 + 22 files changed, 145 insertions(+), 11 deletions(-) diff --git a/docs/src/main/asciidoc/amazon-lambda-http.adoc b/docs/src/main/asciidoc/amazon-lambda-http.adoc index ada6bcab0b3ef..1892d7de3d05d 100644 --- a/docs/src/main/asciidoc/amazon-lambda-http.adoc +++ b/docs/src/main/asciidoc/amazon-lambda-http.adoc @@ -7,7 +7,6 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] - The `quarkus-amazon-lambda-http` extension allows you to write microservices with Resteasy (JAX-RS), Undertow (servlet), or Vert.x Web and make these microservices deployable as an Amazon Lambda using https://aws.amazon.com/api-gateway/[Amazon's API Gateway] and https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html[Amazon's SAM framework]. @@ -15,6 +14,13 @@ using https://aws.amazon.com/api-gateway/[Amazon's API Gateway] and https://docs You can deploy your Lambda as a pure Java jar, or you can compile your project to a native image and deploy that for a smaller memory footprint and startup time. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/amazon-lambda.adoc b/docs/src/main/asciidoc/amazon-lambda.adoc index 3151a7ba6a959..781b97f54e6d5 100644 --- a/docs/src/main/asciidoc/amazon-lambda.adoc +++ b/docs/src/main/asciidoc/amazon-lambda.adoc @@ -7,13 +7,19 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] - The `quarkus-amazon-lambda` extension allows you to use Quarkus to build your Amazon Lambdas. Your lambdas can use injection annotations from CDI or Spring and other Quarkus facilities as you need them. Quarkus lambdas can be deployed using the Amazon Java Runtime, or you can build a native executable and use Amazon's Custom Runtime if you want a smaller memory footprint and faster cold boot startup time. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/amqp.adoc b/docs/src/main/asciidoc/amqp.adoc index c27f7aee2d635..cf08d9f3df7da 100644 --- a/docs/src/main/asciidoc/amqp.adoc +++ b/docs/src/main/asciidoc/amqp.adoc @@ -8,6 +8,13 @@ include::./attributes.adoc[] This guide demonstrates how your Quarkus application can utilize MicroProfile Reactive Messaging to interact with AMQP. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/azure-functions-http.adoc b/docs/src/main/asciidoc/azure-functions-http.adoc index 99bdc0cd69135..1afdde9c75c17 100644 --- a/docs/src/main/asciidoc/azure-functions-http.adoc +++ b/docs/src/main/asciidoc/azure-functions-http.adoc @@ -12,6 +12,13 @@ Undertow (servlet), or Vert.x Web and make these microservices deployable to the One azure function deployment can represent any number of JAX-RS, servlet, or Vert.x Web endpoints. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/dynamodb.adoc b/docs/src/main/asciidoc/dynamodb.adoc index b91f14ef57b1d..d12fa47aa81ff 100644 --- a/docs/src/main/asciidoc/dynamodb.adoc +++ b/docs/src/main/asciidoc/dynamodb.adoc @@ -17,6 +17,13 @@ NOTE: The DynamoDB extension is based on https://docs.aws.amazon.com/sdk-for-jav It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async). Keep in mind it's actively developed and does not support yet all the features available in SDK 1.x such as https://github.com/aws/aws-sdk-java-v2/issues/36[Document APIs] or https://github.com/aws/aws-sdk-java-v2/issues/35[Object Mappers] +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + The Quarkus extension supports two programming models: * Blocking access using URL Connection HTTP client (by default) or the Apache HTTP Client diff --git a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc index 0346279477063..bbeb67b5209aa 100644 --- a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc +++ b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc @@ -14,10 +14,10 @@ We will also explore how you can can query your Elasticsearch cluster using the [WARNING] ==== -The version of Hibernate Search shipped with Quarkus is still a Beta. - -While APIs are quite stable and the code is of production quality and thoroughly tested, -some features are still missing, performance might not be optimal and some APIs might change before the final release. +This extension is considered `preview`. +It is based on a beta version of Hibernate Search. +While APIs are quite stable and the code is of production quality and thoroughly tested, some features are still missing, performance might not be optimal and some APIs or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. ==== == Prerequisites diff --git a/docs/src/main/asciidoc/infinispan-embedded.adoc b/docs/src/main/asciidoc/infinispan-embedded.adoc index b22439d5d3b05..6e88963936bd8 100644 --- a/docs/src/main/asciidoc/infinispan-embedded.adoc +++ b/docs/src/main/asciidoc/infinispan-embedded.adoc @@ -13,6 +13,13 @@ directly in your application. Check out the link:https://infinispan.org/documentation/[Infinispan documentation] to find out more about the Infinispan project. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Adding the Infinispan Embedded Extension After you set up your Quarkus project, run the following command from the base directory: diff --git a/docs/src/main/asciidoc/jms.adoc b/docs/src/main/asciidoc/jms.adoc index 93de0306d7f75..8e72826491f32 100644 --- a/docs/src/main/asciidoc/jms.adoc +++ b/docs/src/main/asciidoc/jms.adoc @@ -8,6 +8,13 @@ include::./attributes.adoc[] This guide demonstrates how your Quarkus application can use Artemis JMS messaging. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/kogito.adoc b/docs/src/main/asciidoc/kogito.adoc index e8526300214d9..b40de1ac26424 100644 --- a/docs/src/main/asciidoc/kogito.adoc +++ b/docs/src/main/asciidoc/kogito.adoc @@ -15,6 +15,13 @@ Drools (for business rules) and jBPM (for business processes). Kogito aims at pr to business automation where the main message is to expose your business knowledge (processes, rules and decisions) in a domain specific way. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index ffea28298cd2d..625ce474f06a3 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -11,6 +11,13 @@ https://kotlinlang.org/[Kotlin] is a very popular programming language that targ Quarkus provides first class support for using Kotlin as will be explained in this guide. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 897a642f486b0..376525f0617af 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -14,7 +14,12 @@ MongoDB with Panache provides active record style entities (and repositories) li It is built on top of the link:mongodb[MongoDB Client] extension. -WARNING: This extension is still experimental, feedback is welcome on the https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== == First: an example diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 1e874d641b7f5..e8069c6486787 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -10,6 +10,13 @@ MongoDB is a well known NoSQL Database that is widely used. In this guide, we see how you can get your REST services to use the MongoDB database. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 568fe7dd81e05..0738cf8654a75 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -34,9 +34,15 @@ The driver itself is released under the Apache 2.0 license, while Neo4j itself is available in a GPL3-licensed open-source "community edition", with online backup and high availability extensions licensed under a closed-source commercial license. -== Programming model +[WARNING] +==== +This extension is considered `preview`. +It is based on an alpha version of the Neo4j driver. +Some interactions with the driver, the API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== -NOTE: The Neo4j extension is based on an alpha version of the Neo4j driver. Some interactions with the driver might change in the future. +== Programming model The driver and thus the Quarkus extension support three different programming models: diff --git a/docs/src/main/asciidoc/scheduler.adoc b/docs/src/main/asciidoc/scheduler.adoc index 25da629f2981c..14c6380fa626d 100644 --- a/docs/src/main/asciidoc/scheduler.adoc +++ b/docs/src/main/asciidoc/scheduler.adoc @@ -10,6 +10,13 @@ include::./attributes.adoc[] Modern applications often need to run specific tasks periodically. In this guide, you learn how to schedule periodic tasks. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: @@ -19,8 +26,6 @@ To complete this guide, you need: * JDK 1.8+ installed with `JAVA_HOME` configured appropriately * Apache Maven 3.5.3+ - - == Architecture In this guide, we create a straightforward application accessible using HTTP to get the current value of a counter. diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index f9e1689e48508..b1757f3837fad 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -15,6 +15,13 @@ It can be used to implement an application authentication mechanism based on tok If your OAuth2 Authentication server provides JWT tokens, you should use link:security-jwt[MicroProfile JWT RBAC] instead, this extension aims to be used with opaque tokens and validate the token by calling an introspection endpoint. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Solution We recommend that you follow the instructions in the next sections and create the application step by step. diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 4e46737879d1c..0012cccdaf90e 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -13,6 +13,13 @@ Bearer Token Authorization is the process of authorizing HTTP requests based on We are going to give you a guideline on how to use OpenId Connect and OAuth 2.0 in your JAX-RS applications using the Quarkus OpenID Connect Extension. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/software-transactional-memory.adoc b/docs/src/main/asciidoc/software-transactional-memory.adoc index 439dfc505b053..d3c8d27c2c660 100644 --- a/docs/src/main/asciidoc/software-transactional-memory.adoc +++ b/docs/src/main/asciidoc/software-transactional-memory.adoc @@ -24,6 +24,13 @@ There is also a fully worked example in the quickstarts which you may access by Git repository: `git clone {quickstarts-clone-url}`, or by downloading an {quickstarts-archive-url}[archive]. Look for the `software-transactional-memory-quickstart` example. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Setting it up To use the extension include it as a dependency in your application pom: diff --git a/docs/src/main/asciidoc/spring-data-jpa.adoc b/docs/src/main/asciidoc/spring-data-jpa.adoc index e762f9a28b1b6..eed8f33c26d7f 100644 --- a/docs/src/main/asciidoc/spring-data-jpa.adoc +++ b/docs/src/main/asciidoc/spring-data-jpa.adoc @@ -10,6 +10,13 @@ include::./attributes.adoc[] While users are encouraged to use Hibernate ORM with Panache for Relational Database access, Quarkus provides a compatibility layer for Spring Data JPA repositories in the form of the `spring-data-jpa` extension. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/spring-di.adoc b/docs/src/main/asciidoc/spring-di.adoc index 9e3ae38038faf..ec8bba87fcbf8 100644 --- a/docs/src/main/asciidoc/spring-di.adoc +++ b/docs/src/main/asciidoc/spring-di.adoc @@ -11,6 +11,13 @@ While users are encouraged to use CDI annotations for injection, Quarkus provide This guide explains how a Quarkus application can leverage the well known Dependency Injection annotations included in the Spring Framework. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/spring-web.adoc b/docs/src/main/asciidoc/spring-web.adoc index 5153b9c662950..176b8f61a112c 100644 --- a/docs/src/main/asciidoc/spring-web.adoc +++ b/docs/src/main/asciidoc/spring-web.adoc @@ -11,6 +11,13 @@ While users are encouraged to use JAX-RS annotation for defining REST endpoints, This guide explains how a Quarkus application can leverage the well known Spring Web annotations to define RESTful services. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/vault.adoc b/docs/src/main/asciidoc/vault.adoc index c425646154249..febab24c1063e 100644 --- a/docs/src/main/asciidoc/vault.adoc +++ b/docs/src/main/asciidoc/vault.adoc @@ -23,6 +23,13 @@ https://www.vaultproject.io/docs/secrets/kv/index.html[Vault kv secret engine] a Under the hood, the Quarkus Vault extension takes care of authentication when negotiating a client Vault token plus any transparent token or lease renewals according to ttl and max-ttl. +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + == Prerequisites To complete this guide, you need: diff --git a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml index b5bd36a227634..ee534812a3b46 100644 --- a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -7,4 +7,5 @@ metadata: - "amazon" categories: - "cloud" + guide: "https://quarkus.io/guides/amazon-lambda" status: "preview" From fe83c221725dd2bb6b9badcb475b9ad5bad5a88b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 22 Nov 2019 11:17:41 +0200 Subject: [PATCH 017/602] Include the reason compilation failed for Kotlin --- .../quarkus/kotlin/deployment/KotlinCompilationProvider.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java index 139f3041977a8..99b26a6b486f7 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java @@ -71,8 +71,9 @@ public boolean hasErrors() { public void report(CompilerMessageSeverity severity, String s, CompilerMessageLocation location) { if (severity.isError()) { if ((location != null) && (location.getLineContent() != null)) { - errors.add(String.format("%s%n%s:%d:%d", location.getLineContent(), location.getPath(), location.getLine(), - location.getColumn())); + errors.add(String.format("%s%n%s:%d:%d%nReason: %s", location.getLineContent(), location.getPath(), + location.getLine(), + location.getColumn(), s)); } else { errors.add(s); } From e35b035166bd0e8b9b3a24ded6eb6ef664b74563 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 22 Nov 2019 09:03:07 -0300 Subject: [PATCH 018/602] Bump Flyway to 6.0.8 --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 11693a600d96f..d54bdab762c64 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -148,7 +148,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.0.6 + 6.0.8 1.0.5 4.0.0-beta03 3.10.2 From 1e9912f70b130fcfee018dade8f151f011fcacde Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 22 Nov 2019 17:10:37 +0530 Subject: [PATCH 019/602] issue-5680 Create parent dir(s) explicitly while creating a new zip file using ZipFileSystem --- .../io/quarkus/bootstrap/util/ZipUtils.java | 14 ++++- .../quarkus/bootstrap/util/ZipUtilsTest.java | 60 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java index 541e179aa6513..d98313f646224 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java @@ -99,7 +99,19 @@ public static void zip(Path src, Path zipFile) throws IOException { } public static FileSystem newZip(Path zipFile) throws IOException { - return newFileSystem(toZipUri(zipFile), Files.exists(zipFile) ? Collections.emptyMap() : CREATE_ENV); + final Map env; + if (Files.exists(zipFile)) { + env = Collections.emptyMap(); + } else { + env = CREATE_ENV; + // explicitly create any parent dirs, since the ZipFileSystem only creates a new file + // with "create" = "true", but doesn't create any parent dirs. + + // It's OK to not check the existence of the parent dir(s) first, since the API, + // as per its contract doesn't throw any exception if the parent dir(s) already exist + Files.createDirectories(zipFile.getParent()); + } + return newFileSystem(toZipUri(zipFile), env); } private static void copyToZip(Path srcRoot, Path srcPath, FileSystem zipfs) throws IOException { diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/util/ZipUtilsTest.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/util/ZipUtilsTest.java index 4dcb7d0878ff1..66440560fc8ec 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/util/ZipUtilsTest.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/util/ZipUtilsTest.java @@ -5,10 +5,15 @@ import java.io.IOException; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * Tests {@link ZipUtils} */ @@ -49,4 +54,59 @@ public void testCorruptZipException() throws Exception { Files.delete(tmpFile); } } + + /** + * Test that the {@link ZipUtils#newZip(Path)} works as expected + * + * @throws Exception + */ + @Test + public void testNewZip() throws Exception { + final Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); + final Path zipPath = Paths.get(tmpDir.toString(), "ziputilstest-" + System.currentTimeMillis() + ".jar"); + try { + try (final FileSystem fs = ZipUtils.newZip(zipPath)) { + final Path someFileInZip = fs.getPath("hello.txt"); + Files.write(someFileInZip, "hello".getBytes(StandardCharsets.UTF_8)); + } + // now just verify that the content was actually written out + try (final FileSystem fs = ZipUtils.newFileSystem(zipPath)) { + assertFileExistsWithContent(fs.getPath("hello.txt"), "hello"); + } + } finally { + Files.deleteIfExists(zipPath); + } + } + + /** + * Tests that the {@link ZipUtils#newZip(Path)} works correctly, and creates the zip file, + * when the parent directories of the {@link Path} passed to it are not present. + * + * @throws Exception + * @see + */ + @Test + public void testNewZipForNonExistentParentDir() throws Exception { + final Path tmpDir = Files.createTempDirectory(null); + final Path nonExistentLevel1Dir = tmpDir.resolve("non-existent-level1"); + final Path nonExistentLevel2Dir = nonExistentLevel1Dir.resolve("non-existent-level2"); + final Path zipPath = Paths.get(nonExistentLevel2Dir.toString(), "ziputilstest-nonexistentdirs.jar"); + try { + try (final FileSystem fs = ZipUtils.newZip(zipPath)) { + final Path someFileInZip = fs.getPath("hello.txt"); + Files.write(someFileInZip, "hello".getBytes(StandardCharsets.UTF_8)); + } + // now just verify that the content was actually written out + try (final FileSystem fs = ZipUtils.newFileSystem(zipPath)) { + assertFileExistsWithContent(fs.getPath("hello.txt"), "hello"); + } + } finally { + Files.deleteIfExists(zipPath); + } + } + + private static void assertFileExistsWithContent(final Path path, final String content) throws IOException { + final String readContent = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + assertEquals(content, readContent, "Unexpected content in " + path); + } } From 18e9e50bfbe08a580d3f193c8e72a27908180b5d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 14:56:42 -0600 Subject: [PATCH 020/602] Remove out of date comment from Agroal extension --- .../main/java/io/quarkus/agroal/runtime/AgroalRecorder.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java index 7d12466b86bff..1c223cfd8586a 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java @@ -29,11 +29,6 @@ public void created(BeanContainer beanContainer) { } public void configureRuntimeProperties(AgroalRuntimeConfig agroalRuntimeConfig) { - // TODO @dmlloyd - // Same here, the map is entirely empty (obviously, I didn't expect the values - // that were not properly injected but at least the config objects present in - // the map) - // The elements from the default datasource are there Arc.container().instance(AbstractDataSourceProducer.class).get().setRuntimeConfig(agroalRuntimeConfig); } } From 162d6a9e5f79939fdb870b0f15217079fd0b4447 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 14:58:21 -0600 Subject: [PATCH 021/602] Modify Dynamodb extension to correctly use Optional --- .../deployment/DynamodbProcessor.java | 5 +- ...amodbAsyncClientBrokenProxyConfigTest.java | 2 + ...modbAsyncClientTlsFileStoreConfigTest.java | 2 + .../DynamodbBrokenEndpointConfigTest.java | 2 + .../runtime/AwsCredentialsProviderConfig.java | 6 +- .../runtime/AwsCredentialsProviderType.java | 5 +- .../runtime/DynamodbClientProducer.java | 61 ++++++------------- .../runtime/NettyHttpClientConfig.java | 4 +- .../dynamodb/runtime/SdkBuildTimeConfig.java | 3 +- .../runtime/SyncHttpClientConfig.java | 4 +- .../runtime/TlsManagersProviderConfig.java | 3 +- .../runtime/TlsManagersProviderType.java | 5 +- .../HibernateSearchElasticsearchRecorder.java | 1 - 13 files changed, 45 insertions(+), 58 deletions(-) diff --git a/extensions/amazon-dynamodb/deployment/src/main/java/io/quarkus/dynamodb/deployment/DynamodbProcessor.java b/extensions/amazon-dynamodb/deployment/src/main/java/io/quarkus/dynamodb/deployment/DynamodbProcessor.java index 43ed8b1da59cc..a91c8481c8ccc 100644 --- a/extensions/amazon-dynamodb/deployment/src/main/java/io/quarkus/dynamodb/deployment/DynamodbProcessor.java +++ b/extensions/amazon-dynamodb/deployment/src/main/java/io/quarkus/dynamodb/deployment/DynamodbProcessor.java @@ -1,6 +1,7 @@ package io.quarkus.dynamodb.deployment; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -81,14 +82,14 @@ void setup(CombinedIndexBuildItem combinedIndexBuildItem, // Indicates that this extension would like the SSL support to be enabled extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.DYNAMODB)); - INTERCEPTOR_PATHS.stream().forEach(path -> resource.produce(new NativeImageResourceBuildItem(path))); + INTERCEPTOR_PATHS.forEach(path -> resource.produce(new NativeImageResourceBuildItem(path))); List knownInterceptorImpls = combinedIndexBuildItem.getIndex() .getAllKnownImplementors(EXECUTION_INTERCEPTOR_NAME) .stream() .map(c -> c.name().toString()).collect(Collectors.toList()); - buildTimeConfig.sdk.interceptors.stream().forEach(interceptorClass -> { + buildTimeConfig.sdk.interceptors.orElse(Collections.emptyList()).forEach(interceptorClass -> { if (!knownInterceptorImpls.contains(interceptorClass.getName())) { throw new ConfigurationError( String.format( diff --git a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientBrokenProxyConfigTest.java b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientBrokenProxyConfigTest.java index 476fcb6633ea1..9e262b3136874 100644 --- a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientBrokenProxyConfigTest.java +++ b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientBrokenProxyConfigTest.java @@ -5,6 +5,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -12,6 +13,7 @@ import io.quarkus.test.QuarkusUnitTest; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +@Disabled("https://github.com/quarkusio/quarkus/issues/5286") public class DynamodbAsyncClientBrokenProxyConfigTest { @Inject diff --git a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientTlsFileStoreConfigTest.java b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientTlsFileStoreConfigTest.java index e7087cdec5b11..66143920fe989 100644 --- a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientTlsFileStoreConfigTest.java +++ b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbAsyncClientTlsFileStoreConfigTest.java @@ -4,12 +4,14 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusUnitTest; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +@Disabled("https://github.com/quarkusio/quarkus/issues/5286") public class DynamodbAsyncClientTlsFileStoreConfigTest { @Inject diff --git a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbBrokenEndpointConfigTest.java b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbBrokenEndpointConfigTest.java index 2d99e62bfcd22..ca9284f69ca88 100644 --- a/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbBrokenEndpointConfigTest.java +++ b/extensions/amazon-dynamodb/deployment/src/test/java/io/quarkus/dynamodb/deployment/DynamodbBrokenEndpointConfigTest.java @@ -5,6 +5,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -13,6 +14,7 @@ import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +@Disabled("https://github.com/quarkusio/quarkus/issues/5286") public class DynamodbBrokenEndpointConfigTest { @Inject diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java index 47916fe636dfb..c7394909b14b8 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java @@ -94,13 +94,13 @@ public static class StaticCredentialsProviderConfig { * AWS Access key id */ @ConfigItem - public String accessKeyId; + public Optional accessKeyId; /** * AWS Secret access key */ @ConfigItem - public String secretAccessKey; + public Optional secretAccessKey; } @ConfigGroup @@ -145,6 +145,6 @@ public static class ProcessCredentialsProviderConfig { * The command that should be executed to retrieve credentials. */ @ConfigItem - public String command; + public Optional command; } } diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderType.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderType.java index 8907be67bb409..7cbacef6973a0 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderType.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderType.java @@ -26,7 +26,8 @@ public AwsCredentialsProvider create(AwsCredentialsProviderConfig config) { @Override public AwsCredentialsProvider create(AwsCredentialsProviderConfig config) { return StaticCredentialsProvider.create( - AwsBasicCredentials.create(config.staticProvider.accessKeyId, config.staticProvider.secretAccessKey)); + AwsBasicCredentials.create(config.staticProvider.accessKeyId.get(), + config.staticProvider.secretAccessKey.get())); } }, @@ -72,7 +73,7 @@ public AwsCredentialsProvider create(AwsCredentialsProviderConfig config) { builder.credentialRefreshThreshold(config.processProvider.credentialRefreshThreshold); builder.processOutputLimit(config.processProvider.processOutputLimit.asLongValue()); - builder.command(config.processProvider.command); + builder.command(config.processProvider.command.get()); return builder.build(); } diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/DynamodbClientProducer.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/DynamodbClientProducer.java index a0c73d547b29a..66975baa6fa55 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/DynamodbClientProducer.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/DynamodbClientProducer.java @@ -1,6 +1,7 @@ package io.quarkus.dynamodb.runtime; import java.net.URI; +import java.util.Collections; import java.util.HashSet; import java.util.Objects; @@ -91,15 +92,15 @@ private void initAwsClient(AwsClientBuilder builder, AwsConfig config) { config.region.ifPresent(builder::region); if (config.credentials.type == AwsCredentialsProviderType.STATIC) { - if (StringUtils.isBlank(config.credentials.staticProvider.accessKeyId) - || StringUtils.isBlank(config.credentials.staticProvider.secretAccessKey)) { + if (!config.credentials.staticProvider.accessKeyId.isPresent() + || !config.credentials.staticProvider.secretAccessKey.isPresent()) { throw new RuntimeConfigurationError( "quarkus.dynamodb.aws.credentials.static-provider.access-key-id and " + "quarkus.dynamodb.aws.credentials.static-provider.secret-access-key cannot be empty if STATIC credentials provider used."); } } if (config.credentials.type == AwsCredentialsProviderType.PROCESS) { - if (StringUtils.isBlank(config.credentials.processProvider.command)) { + if (!config.credentials.processProvider.command.isPresent()) { throw new RuntimeConfigurationError( "quarkus.dynamodb.aws.credentials.process-provider.command cannot be empty if PROCESS credentials provider used."); } @@ -121,7 +122,7 @@ private void initSdkClient(SdkClientBuilder builder, SdkConfig config) { ClientOverrideConfiguration.Builder overrides = ClientOverrideConfiguration.builder(); config.apiCallTimeout.ifPresent(overrides::apiCallTimeout); config.apiCallAttemptTimeout.ifPresent(overrides::apiCallAttemptTimeout); - buildTimeConfig.sdk.interceptors.stream() + buildTimeConfig.sdk.interceptors.orElse(Collections.emptyList()).stream() .map(this::createInterceptor) .filter(Objects::nonNull) .forEach(overrides::addExecutionInterceptor); @@ -163,12 +164,12 @@ private ApacheHttpClient.Builder createApacheClientBuilder(SyncHttpClientConfig builder.maxConnections(config.apache.maxConnections); builder.useIdleConnectionReaper(config.apache.useIdleConnectionReaper); - if (config.apache.proxy.enabled) { + if (config.apache.proxy.enabled && config.apache.proxy.endpoint.isPresent()) { ProxyConfiguration.Builder proxyBuilder = ProxyConfiguration.builder() - .endpoint(config.apache.proxy.endpoint); + .endpoint(config.apache.proxy.endpoint.get()); config.apache.proxy.username.ifPresent(proxyBuilder::username); config.apache.proxy.password.ifPresent(proxyBuilder::password); - config.apache.proxy.nonProxyHosts.forEach(proxyBuilder::addNonProxyHost); + config.apache.proxy.nonProxyHosts.ifPresent(c -> c.forEach(proxyBuilder::addNonProxyHost)); config.apache.proxy.ntlmDomain.ifPresent(proxyBuilder::ntlmDomain); config.apache.proxy.ntlmWorkstation.ifPresent(proxyBuilder::ntlmWorkstation); config.apache.proxy.preemptiveBasicAuthenticationEnabled @@ -199,14 +200,14 @@ private NettyNioAsyncHttpClient.Builder createNettyClientBuilder(NettyHttpClient config.sslProvider.ifPresent(builder::sslProvider); builder.useIdleConnectionReaper(config.useIdleConnectionReaper); - if (config.proxy.enabled) { + if (config.proxy.enabled && config.proxy.endpoint.isPresent()) { software.amazon.awssdk.http.nio.netty.ProxyConfiguration.Builder proxyBuilder = software.amazon.awssdk.http.nio.netty.ProxyConfiguration - .builder().scheme(config.proxy.endpoint.getScheme()) - .host(config.proxy.endpoint.getHost()) - .nonProxyHosts(new HashSet<>(config.proxy.nonProxyHosts)); + .builder().scheme(config.proxy.endpoint.get().getScheme()) + .host(config.proxy.endpoint.get().getHost()) + .nonProxyHosts(new HashSet<>(config.proxy.nonProxyHosts.orElse(Collections.emptyList()))); - if (config.proxy.endpoint.getPort() != -1) { - proxyBuilder.port(config.proxy.endpoint.getPort()); + if (config.proxy.endpoint.get().getPort() != -1) { + proxyBuilder.port(config.proxy.endpoint.get().getPort()); } builder.proxyConfiguration(proxyBuilder.build()); } @@ -239,11 +240,8 @@ private void validateApacheClientConfig(SyncHttpClientConfig config) { if (config.apache.maxConnections <= 0) { throw new RuntimeConfigurationError("quarkus.dynamodb.sync-client.max-connections may not be negative or zero."); } - if (config.apache.proxy != null && config.apache.proxy.enabled) { - URI proxyEndpoint = config.apache.proxy.endpoint; - if (proxyEndpoint != null) { - validateProxyEndpoint(proxyEndpoint, "sync"); - } + if (config.apache.proxy.enabled) { + config.apache.proxy.endpoint.ifPresent(u -> validateProxyEndpoint(u, "sync")); } validateTlsManagersProvider(config.apache.tlsManagersProvider, "sync"); } @@ -266,11 +264,8 @@ private void validateNettyClientConfig(NettyHttpClientConfig asyncClient) { "quarkus.dynamodb.async-client.event-loop.number-of-threads may not be negative or zero."); } } - if (asyncClient.proxy != null && asyncClient.proxy.enabled) { - URI proxyEndpoint = asyncClient.proxy.endpoint; - if (proxyEndpoint != null) { - validateProxyEndpoint(proxyEndpoint, "async"); - } + if (asyncClient.proxy.enabled) { + asyncClient.proxy.endpoint.ifPresent(proxyEndpoint -> validateProxyEndpoint(proxyEndpoint, "async")); } validateTlsManagersProvider(asyncClient.tlsManagersProvider, "async"); } @@ -310,30 +305,12 @@ private void validateProxyEndpoint(URI endpoint, String clientType) { private void validateTlsManagersProvider(TlsManagersProviderConfig config, String clientType) { if (config.type == TlsManagersProviderType.FILE_STORE) { - if (config.fileStore == null) { + if (!config.fileStore.isPresent()) { throw new RuntimeConfigurationError( String.format( "quarkus.dynamodb.%s-client.tls-managers-provider.file-store must be specified if 'FILE_STORE' provider type is used", clientType)); } - if (config.fileStore.path == null) { - throw new RuntimeConfigurationError( - String.format( - "quarkus.dynamodb.%s-client.tls-managers-provider.file-store.path should not be empty if 'FILE_STORE' provider is used.", - clientType)); - } - if (StringUtils.isBlank(config.fileStore.type)) { - throw new RuntimeConfigurationError( - String.format( - "quarkus.dynamodb.%s-client.tls-managers-provider.file-store.type should not be empty if 'FILE_STORE' provider is used.", - clientType)); - } - if (StringUtils.isBlank(config.fileStore.password)) { - throw new RuntimeConfigurationError( - String.format( - "quarkus.dynamodb.%s-client.tls-managers-provider.file-store.password should not be empty if 'FILE_STORE' provider is used.", - clientType)); - } } } } diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/NettyHttpClientConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/NettyHttpClientConfig.java index 7902e13a4c4b8..fdc93e00745a0 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/NettyHttpClientConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/NettyHttpClientConfig.java @@ -168,13 +168,13 @@ public static class NettyProxyConfiguration { * raised. */ @ConfigItem - public URI endpoint; + public Optional endpoint; /** * The hosts that the client is allowed to access without going through the proxy. */ @ConfigItem - public List nonProxyHosts; + public Optional> nonProxyHosts; } //TODO: additionalChannelOptions diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SdkBuildTimeConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SdkBuildTimeConfig.java index fb2ac2204a3f4..214642e32832c 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SdkBuildTimeConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SdkBuildTimeConfig.java @@ -1,6 +1,7 @@ package io.quarkus.dynamodb.runtime; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -20,5 +21,5 @@ public class SdkBuildTimeConfig { * @see software.amazon.awssdk.core.interceptor.ExecutionInterceptor */ @ConfigItem - public List> interceptors; + public Optional>> interceptors; } diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SyncHttpClientConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SyncHttpClientConfig.java index ca3a9b6c9dedf..31607560615bb 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SyncHttpClientConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/SyncHttpClientConfig.java @@ -103,7 +103,7 @@ public static class HttpClientProxyConfiguration { * raised. */ @ConfigItem - public URI endpoint; + public Optional endpoint; /** * The username to use when connecting through a proxy. @@ -139,7 +139,7 @@ public static class HttpClientProxyConfiguration { * The hosts that the client is allowed to access without going through the proxy. */ @ConfigItem - public List nonProxyHosts; + public Optional> nonProxyHosts; } } } diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderConfig.java index 2b3612053f874..c30a6a6a25d91 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderConfig.java @@ -1,6 +1,7 @@ package io.quarkus.dynamodb.runtime; import java.nio.file.Path; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -32,7 +33,7 @@ public class TlsManagersProviderConfig { * Used only if {@code FILE_STORE} type is chosen. */ @ConfigItem - public FileStoreTlsManagersProviderConfig fileStore; + public Optional fileStore; @ConfigGroup public static class FileStoreTlsManagersProviderConfig { diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderType.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderType.java index 84f82da15377e..e9960cd350e55 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderType.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/TlsManagersProviderType.java @@ -20,8 +20,9 @@ public TlsKeyManagersProvider create(TlsManagersProviderConfig config) { FILE_STORE { @Override public TlsKeyManagersProvider create(TlsManagersProviderConfig config) { - return FileStoreTlsKeyManagersProvider.create(config.fileStore.path, config.fileStore.type, - config.fileStore.password); + final TlsManagersProviderConfig.FileStoreTlsManagersProviderConfig fileStore = config.fileStore.get(); + return FileStoreTlsKeyManagersProvider.create(fileStore.path, fileStore.type, + fileStore.password); } }; diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java index fdd8b13c7cc3b..f5e5ff1a5d85b 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java @@ -8,7 +8,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.function.BiConsumer; -import java.util.function.Function; import org.hibernate.boot.Metadata; import org.hibernate.boot.spi.BootstrapContext; From 29d70b9731eaf3e6c6c284225b13e2f833ef8477 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 14:59:08 -0600 Subject: [PATCH 022/602] Move amazon-lambda build time config flag to buidl time class --- .../deployment/AmazonLambdaProcessor.java | 3 ++- .../lambda/runtime/LambdaBuildTimeConfig.java | 20 +++++++++++++++++++ .../amazon/lambda/runtime/LambdaConfig.java | 9 --------- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java index 70ea6982a82cb..e5ad77550273f 100644 --- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java @@ -21,6 +21,7 @@ import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder; import io.quarkus.amazon.lambda.runtime.FunctionError; +import io.quarkus.amazon.lambda.runtime.LambdaBuildTimeConfig; import io.quarkus.amazon.lambda.runtime.LambdaConfig; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; @@ -196,7 +197,7 @@ void ipv4Only(BuildProducer systemProperty) { @BuildStep @Record(value = ExecutionTime.RUNTIME_INIT) - void enableNativeEventLoop(LambdaConfig config, + void enableNativeEventLoop(LambdaBuildTimeConfig config, AmazonLambdaRecorder recorder, List orderServicesFirst, // force some ordering of recorders ShutdownContextBuildItem shutdownContextBuildItem, diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java new file mode 100644 index 0000000000000..3af310748aa10 --- /dev/null +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java @@ -0,0 +1,20 @@ +package io.quarkus.amazon.lambda.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * + */ +@ConfigRoot(phase = ConfigPhase.BUILD_TIME) +public class LambdaBuildTimeConfig { + + /** + * If true, this will enable the aws event poll loop within a Quarkus test run. This loop normally only runs in native + * image. This option is strictly for testing purposes. + * + */ + @ConfigItem + public boolean enablePollingJvmMode; +} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java index dd5aa8ecb5f95..c22d3462f2bf4 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaConfig.java @@ -20,13 +20,4 @@ public class LambdaConfig { */ @ConfigItem public Optional handler; - - /** - * If true, this will enable the aws event poll loop within a Quarkus test run. This loop normally only runs in native - * image. This option is strictly for testing purposes. - * - */ - @ConfigItem - public boolean enablePollingJvmMode; - } From e32e4d5bcb59682114d3afe39f32da758f4e47a1 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:00:16 -0600 Subject: [PATCH 023/602] Modify ArC to use the standard config producer --- .../arc/deployment/ConfigBuildStep.java | 4 +- .../arc/runtime/ConfigBeanCreator.java | 4 +- .../quarkus/arc/runtime/ConfigRecorder.java | 4 +- .../arc/runtime/QuarkusConfigProducer.java | 95 ------------------- 4 files changed, 6 insertions(+), 101 deletions(-) delete mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/QuarkusConfigProducer.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index cf66f9e1e516b..b1bebc9675a57 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -27,11 +27,11 @@ import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.arc.runtime.ConfigBeanCreator; import io.quarkus.arc.runtime.ConfigRecorder; -import io.quarkus.arc.runtime.QuarkusConfigProducer; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.smallrye.config.inject.ConfigProducer; /** * MicroProfile Config related build steps. @@ -44,7 +44,7 @@ public class ConfigBuildStep { @BuildStep AdditionalBeanBuildItem bean() { - return new AdditionalBeanBuildItem(QuarkusConfigProducer.class); + return new AdditionalBeanBuildItem(ConfigProducer.class); } @BuildStep diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java index fa6b238652058..3f652d1dade3f 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java @@ -8,8 +8,8 @@ import javax.enterprise.inject.spi.InjectionPoint; import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import io.quarkus.arc.BeanCreator; import io.quarkus.arc.impl.InjectionPointProvider; @@ -58,7 +58,7 @@ public Object create(CreationalContext creationalContext, Map> properties) { - Config config = ConfigProviderResolver.instance().getConfig(); + Config config = ConfigProvider.getConfig(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = ConfigRecorder.class.getClassLoader(); diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/QuarkusConfigProducer.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/QuarkusConfigProducer.java deleted file mode 100644 index 548fb96ce7f05..0000000000000 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/QuarkusConfigProducer.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.quarkus.arc.runtime; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; - -import io.smallrye.config.inject.ConfigProducerUtil; - -/** - * This class is the same as io.smallrye.config.inject.ConfigProducer - * but uses the proper Quarkus way of obtaining org.eclipse.microprofile.config.Config - */ -@ApplicationScoped -public class QuarkusConfigProducer { - - @Produces - Config getConfig(InjectionPoint injectionPoint) { - return ConfigProviderResolver.instance().getConfig(); - } - - @Dependent - @Produces - @ConfigProperty - String produceStringConfigProperty(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, String.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Long getLongValue(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, Long.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Integer getIntegerValue(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, Integer.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Float produceFloatConfigProperty(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, Float.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Double produceDoubleConfigProperty(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, Double.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Boolean produceBooleanConfigProperty(InjectionPoint ip) { - return ConfigProducerUtil.getValue(ip, Boolean.class, getConfig(ip)); - } - - @Dependent - @Produces - @ConfigProperty - Optional produceOptionalConfigValue(InjectionPoint injectionPoint) { - return ConfigProducerUtil.optionalConfigValue(injectionPoint, getConfig(injectionPoint)); - } - - @Dependent - @Produces - @ConfigProperty - Set producesSetConfigPropery(InjectionPoint ip) { - return ConfigProducerUtil.collectionConfigProperty(ip, getConfig(ip), new HashSet<>()); - } - - @Dependent - @Produces - @ConfigProperty - List producesListConfigPropery(InjectionPoint ip) { - return ConfigProducerUtil.collectionConfigProperty(ip, getConfig(ip), new ArrayList()); - } - -} From bbd29940238a1c871b836395a25675d4bfe27789 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:01:00 -0600 Subject: [PATCH 024/602] Modify flyway extension to use Optional for empty lists --- .../io/quarkus/flyway/FlywayProcessor.java | 3 ++- .../flyway/runtime/FlywayBuildConfig.java | 3 ++- .../flyway/runtime/FlywayProducer.java | 20 ++----------------- .../flyway/runtime/FlywayRuntimeConfig.java | 2 +- 4 files changed, 7 insertions(+), 21 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index af692ba0c7ff6..42613a5575660 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -13,6 +13,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; @@ -120,7 +121,7 @@ private List discoverApplicationMigrations(FlywayBuildConfig flywayBuild throws IOException, URISyntaxException { List resources = new ArrayList<>(); try { - List locations = new ArrayList<>(flywayBuildConfig.locations); + List locations = new ArrayList<>(flywayBuildConfig.locations.orElse(Collections.emptyList())); if (locations.isEmpty()) { locations.add("db/migration"); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java index 8c2d21f0c05ec..a888dbabd7bff 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java @@ -1,6 +1,7 @@ package io.quarkus.flyway.runtime; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -16,5 +17,5 @@ public final class FlywayBuildConfig { * scanned recursively down non-hidden directories. */ @ConfigItem - public List locations; + public Optional> locations; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java index 1c1c6637e16fa..5268403cdad12 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java @@ -1,8 +1,5 @@ package io.quarkus.flyway.runtime; -import java.util.List; -import java.util.stream.Collectors; - import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; @@ -26,15 +23,9 @@ public Flyway produceFlyway() { FluentConfiguration configure = Flyway.configure(); configure.dataSource(dataSource); flywayRuntimeConfig.connectRetries.ifPresent(configure::connectRetries); - List notEmptySchemas = filterBlanks(flywayRuntimeConfig.schemas); - if (!notEmptySchemas.isEmpty()) { - configure.schemas(notEmptySchemas.toArray(new String[0])); - } + flywayRuntimeConfig.schemas.ifPresent(l -> configure.schemas(l.toArray(new String[0]))); flywayRuntimeConfig.table.ifPresent(configure::table); - List notEmptyLocations = filterBlanks(flywayBuildConfig.locations); - if (!notEmptyLocations.isEmpty()) { - configure.locations(notEmptyLocations.toArray(new String[0])); - } + flywayBuildConfig.locations.ifPresent(l -> configure.locations(l.toArray(new String[0]))); flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); @@ -45,13 +36,6 @@ public Flyway produceFlyway() { return configure.load(); } - // NOTE: Have to do this filtering because SmallRye config was injecting an empty string in the list somehow! - // TODO: remove this when https://github.com/quarkusio/quarkus/issues/2288 is fixed - private List filterBlanks(List values) { - return values.stream().filter(it -> it != null && !"".equals(it)) - .collect(Collectors.toList()); - } - public void setFlywayRuntimeConfig(FlywayRuntimeConfig flywayRuntimeConfig) { this.flywayRuntimeConfig = flywayRuntimeConfig; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index b2a17c8260225..a8e3a7c0e0d2d 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -22,7 +22,7 @@ public final class FlywayRuntimeConfig { * It will also be the one containing the schema history table. */ @ConfigItem - public List schemas; + public Optional> schemas; /** * The name of Flyway's schema history table. * By default (single-schema mode) the schema history table is placed in the default schema for the connection provided by From 0701b90456a03ae4c09f33fcbe41ad832735b6e1 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:02:00 -0600 Subject: [PATCH 025/602] Modify Hibernate Search Elasticsearch to use Optional for empty lists --- .../runtime/HibernateSearchElasticsearchRecorder.java | 3 +-- .../runtime/HibernateSearchElasticsearchRuntimeConfig.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java index f5e5ff1a5d85b..f8aeabead98bf 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java @@ -123,8 +123,7 @@ private void contributeBackendBuildTimeProperties(BiConsumer pro private void contributeBackendRuntimeProperties(BiConsumer propertyCollector, String backendName, ElasticsearchBackendRuntimeConfig elasticsearchBackendConfig) { addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.HOSTS, - elasticsearchBackendConfig.hosts, - v -> (!v.isEmpty() && !(v.size() == 1 && v.get(0).isEmpty())), Function.identity()); + elasticsearchBackendConfig.hosts); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.USERNAME, elasticsearchBackendConfig.username); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PASSWORD, diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java index 04aee3fccd968..6628067575d9c 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java @@ -63,7 +63,7 @@ public static class ElasticsearchBackendRuntimeConfig { * The list of hosts of the Elasticsearch servers. */ @ConfigItem(defaultValue = "http://localhost:9200") - List hosts; + Optional> hosts; /** * The username used for authentication. From 832e1cb6aa504ba6cb64fb8f4fc55a4ccdf86b2a Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:02:33 -0600 Subject: [PATCH 026/602] Move Jaeger enable flat to build time configuration --- .../jaeger/deployment/JaegerProcessor.java | 10 +++------- .../jaeger/runtime/JaegerBuildTimeConfig.java | 17 +++++++++++++++++ .../io/quarkus/jaeger/runtime/JaegerConfig.java | 6 ------ 3 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerBuildTimeConfig.java diff --git a/extensions/jaeger/deployment/src/main/java/io/quarkus/jaeger/deployment/JaegerProcessor.java b/extensions/jaeger/deployment/src/main/java/io/quarkus/jaeger/deployment/JaegerProcessor.java index 97cf0ba37b5bc..35e53d2176d8b 100644 --- a/extensions/jaeger/deployment/src/main/java/io/quarkus/jaeger/deployment/JaegerProcessor.java +++ b/extensions/jaeger/deployment/src/main/java/io/quarkus/jaeger/deployment/JaegerProcessor.java @@ -8,6 +8,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.jaeger.runtime.JaegerBuildTimeConfig; import io.quarkus.jaeger.runtime.JaegerConfig; import io.quarkus.jaeger.runtime.JaegerDeploymentRecorder; @@ -16,19 +17,14 @@ public class JaegerProcessor { @Inject BuildProducer extensionSslNativeSupport; - /** - * The jaeger configuration - */ - JaegerConfig jaeger; - @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - void setupTracer(JaegerDeploymentRecorder jdr) { + void setupTracer(JaegerDeploymentRecorder jdr, JaegerBuildTimeConfig buildTimeConfig, JaegerConfig jaeger) { // Indicates that this extension would like the SSL support to be enabled extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.JAEGER)); - if (jaeger.enabled) { + if (buildTimeConfig.enabled) { jdr.registerTracer(jaeger); } } diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerBuildTimeConfig.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerBuildTimeConfig.java new file mode 100644 index 0000000000000..e3e2e26e01af6 --- /dev/null +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerBuildTimeConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.jaeger.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * The Jaeger build time configuration. + */ +@ConfigRoot +public class JaegerBuildTimeConfig { + /** + * Defines if the Jaeger extension is enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean enabled; + +} diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java index 14ad7472779de..92f3a22ae15ac 100644 --- a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java @@ -16,12 +16,6 @@ @ConfigRoot(phase = ConfigPhase.RUN_TIME) public class JaegerConfig { - /** - * Defines if the Jaeger extension is enabled. - */ - @ConfigItem(defaultValue = "true") - public boolean enabled; - /** * The traces endpoint, in case the client should connect directly to the Collector, * like http://jaeger-collector:14268/api/traces From 42ef9b9fe8f8e806f647ec60ec7a2098c2a391ea Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:05:43 -0600 Subject: [PATCH 027/602] Simplify kubernetes-client config slightly --- .../client/runtime/KubernetesClientBuildConfig.java | 5 ++--- .../client/runtime/KubernetesClientProducer.java | 10 +--------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java index bc97f1f936835..c41980955be42 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientBuildConfig.java @@ -1,7 +1,6 @@ package io.quarkus.kubernetes.client.runtime; import java.time.Duration; -import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; @@ -100,7 +99,7 @@ public class KubernetesClientBuildConfig { * By default there is no limit to the number of reconnect attempts */ @ConfigItem(defaultValue = "-1") // default lifted from Kubernetes Client - Integer watchReconnectLimit; + int watchReconnectLimit; /** * Maximum amount of time to wait for a connection with the API server to be established @@ -148,5 +147,5 @@ public class KubernetesClientBuildConfig { * IP addresses or hosts to exclude from proxying */ @ConfigItem - List> noProxy; + Optional noProxy; } diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java index 5ecb3798fc0fb..59355f75001d6 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java @@ -43,19 +43,11 @@ public Config config() { .withHttpsProxy(buildConfig.httpsProxy.orElse(base.getHttpsProxy())) .withProxyUsername(buildConfig.proxyUsername.orElse(base.getProxyUsername())) .withProxyPassword(buildConfig.proxyPassword.orElse(base.getProxyPassword())) - .withNoProxy(buildConfig.noProxy.size() > 0 ? buildConfig.noProxy.toArray(new String[0]) : base.getNoProxy()) + .withNoProxy(buildConfig.noProxy.isPresent() ? buildConfig.noProxy.get() : base.getNoProxy()) .build(); } - private String or(String value, String fallback) { - if (value.isEmpty()) { - return fallback; - } else { - return value; - } - } - @DefaultBean @Singleton @Produces From 96f7d0fa51136550918c46a8df4396d6e82a1aae Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:07:01 -0600 Subject: [PATCH 028/602] Use Optional for empty list in mongodb-client --- .../main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java | 2 +- .../java/io/quarkus/mongodb/runtime/MongoClientRecorder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java index 32e46e5641010..87fedc898e970 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java @@ -67,7 +67,7 @@ public class MongoClientConfig { * The addressed are passed as {@code host:port}. */ @ConfigItem - public List hosts; + public Optional> hosts; /** * Configure the database name. diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java index 823595b12f18e..2fb6b72445303 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java @@ -139,7 +139,7 @@ void initialize(MongoClientConfig config, List codecProviders) { settings.applyToClusterSettings(builder -> { if (!maybeConnectionString.isPresent()) { // Parse hosts - List hosts = parseHosts(config.hosts); + List hosts = parseHosts(config.hosts.orElse(Collections.emptyList())); builder.hosts(hosts); if (hosts.size() == 1 && !config.replicaSetName.isPresent()) { From c3ea06d79ffb4616141c07ba6c2fee1dff1d59eb Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:07:35 -0600 Subject: [PATCH 029/602] Move Narayana use of run time config to recorder method parameter --- .../narayana/jta/deployment/NarayanaJtaProcessor.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java index 02387f0f90e63..805d99e651568 100644 --- a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java +++ b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java @@ -41,11 +41,6 @@ class NarayanaJtaProcessor { - /** - * The transactions configuration. - */ - TransactionManagerConfiguration transactions; - @BuildStep public NativeImageSystemPropertyBuildItem nativeImageSystemPropertyBuildItem() { return new NativeImageSystemPropertyBuildItem("CoordinatorEnvironmentBean.transactionStatusManagerEnable", "false"); @@ -62,7 +57,8 @@ public void build(NarayanaJtaRecorder recorder, BuildProducer additionalBeans, BuildProducer reflectiveClass, BuildProducer runtimeInit, - BuildProducer feature) { + BuildProducer feature, + TransactionManagerConfiguration transactions) { feature.produce(new FeatureBuildItem(FeatureBuildItem.NARAYANA_JTA)); additionalBeans.produce(new AdditionalBeanBuildItem(NarayanaJtaProducers.class)); additionalBeans.produce(new AdditionalBeanBuildItem(CDIDelegatingTransactionManager.class)); From c7ec6bc34934012e75e92a0742f67bf049869058 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:08:08 -0600 Subject: [PATCH 030/602] Use build time config to disable OIDC and use Optional for emptiable lists --- .../KeycloakPolicyEnforcerBuildStep.java | 13 ++--- .../oidc/deployment/OidcBuildStep.java | 51 +++++++++++-------- .../runtime/CodeAuthenticationMechanism.java | 2 +- .../oidc/runtime/OidcBuildTimeConfig.java | 38 ++++++++++++++ .../io/quarkus/oidc/runtime/OidcConfig.java | 34 +------------ .../io/quarkus/oidc/runtime/OidcRecorder.java | 6 +-- 6 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcBuildTimeConfig.java diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java index 7de9e80705c7f..f9775d62682b0 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java @@ -11,6 +11,7 @@ import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerConfig; import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerRecorder; import io.quarkus.oidc.OIDCException; +import io.quarkus.oidc.runtime.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; public class KeycloakPolicyEnforcerBuildStep { @@ -36,13 +37,13 @@ EnableAllSecurityServicesBuildItem security() { @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - public void setup(OidcConfig oidcConfig, KeycloakPolicyEnforcerConfig config, KeycloakPolicyEnforcerRecorder recorder, - BeanContainerBuildItem bc) { - if (!oidcConfig.getApplicationType().equals(OidcConfig.ApplicationType.SERVICE)) { - throw new OIDCException("Application type [" + oidcConfig.getApplicationType() + "] not supported"); + public void setup(OidcBuildTimeConfig buildTimeConfig, KeycloakPolicyEnforcerConfig keycloakConfig, + OidcConfig runTimeConfig, KeycloakPolicyEnforcerRecorder recorder, BeanContainerBuildItem bc) { + if (!buildTimeConfig.applicationType.equals(OidcBuildTimeConfig.ApplicationType.SERVICE)) { + throw new OIDCException("Application type [" + buildTimeConfig.applicationType + "] not supported"); } - if (config.policyEnforcer.enable) { - recorder.setup(oidcConfig, config, bc.getValue()); + if (keycloakConfig.policyEnforcer.enable) { + recorder.setup(runTimeConfig, keycloakConfig, bc.getValue()); } } } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java index 18503bda8ac3b..ad4fbb7596bdf 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java @@ -1,5 +1,7 @@ package io.quarkus.oidc.deployment; +import java.util.function.BooleanSupplier; + import org.eclipse.microprofile.jwt.Claim; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -12,6 +14,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.oidc.runtime.BearerAuthenticationMechanism; import io.quarkus.oidc.runtime.CodeAuthenticationMechanism; +import io.quarkus.oidc.runtime.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; import io.quarkus.oidc.runtime.OidcIdentityProvider; import io.quarkus.oidc.runtime.OidcJsonWebTokenProducer; @@ -25,14 +28,16 @@ @SuppressWarnings("deprecation") public class OidcBuildStep { - @BuildStep + OidcBuildTimeConfig buildTimeConfig; + + @BuildStep(onlyIf = IsEnabled.class) FeatureBuildItem featureBuildItem() { return new FeatureBuildItem(FeatureBuildItem.OIDC); } - @BuildStep - AdditionalBeanBuildItem jwtClaimIntegration(Capabilities capabilities, OidcConfig config) { - if (!capabilities.isCapabilityPresent(Capabilities.JWT) && config.enabled) { + @BuildStep(onlyIf = IsEnabled.class) + AdditionalBeanBuildItem jwtClaimIntegration(Capabilities capabilities) { + if (!capabilities.isCapabilityPresent(Capabilities.JWT)) { AdditionalBeanBuildItem.Builder removable = AdditionalBeanBuildItem.builder(); removable.addBeanClass(CommonJwtProducer.class); removable.addBeanClass(RawClaimTypeProducer.class); @@ -43,35 +48,37 @@ AdditionalBeanBuildItem jwtClaimIntegration(Capabilities capabilities, OidcConfi return null; } - @BuildStep - public AdditionalBeanBuildItem beans(OidcConfig config) { - if (config.enabled) { - AdditionalBeanBuildItem.Builder beans = AdditionalBeanBuildItem.builder().setUnremovable(); + @BuildStep(onlyIf = IsEnabled.class) + public AdditionalBeanBuildItem beans() { + AdditionalBeanBuildItem.Builder beans = AdditionalBeanBuildItem.builder().setUnremovable(); - if (OidcConfig.ApplicationType.SERVICE.equals(config.getApplicationType())) { - beans.addBeanClass(BearerAuthenticationMechanism.class); - } else if (OidcConfig.ApplicationType.WEB_APP.equals(config.getApplicationType())) { - beans.addBeanClass(CodeAuthenticationMechanism.class); - } - return beans.addBeanClass(OidcJsonWebTokenProducer.class) - .addBeanClass(OidcTokenCredentialProducer.class) - .addBeanClass(OidcIdentityProvider.class).build(); + if (OidcBuildTimeConfig.ApplicationType.SERVICE.equals(buildTimeConfig.applicationType)) { + beans.addBeanClass(BearerAuthenticationMechanism.class); + } else if (OidcBuildTimeConfig.ApplicationType.WEB_APP.equals(buildTimeConfig.applicationType)) { + beans.addBeanClass(CodeAuthenticationMechanism.class); } - - return null; + return beans.addBeanClass(OidcJsonWebTokenProducer.class) + .addBeanClass(OidcTokenCredentialProducer.class) + .addBeanClass(OidcIdentityProvider.class).build(); } - @BuildStep + @BuildStep(onlyIf = IsEnabled.class) EnableAllSecurityServicesBuildItem security() { return new EnableAllSecurityServicesBuildItem(); } @Record(ExecutionTime.RUNTIME_INIT) - @BuildStep + @BuildStep(onlyIf = IsEnabled.class) public void setup(OidcConfig config, OidcRecorder recorder, InternalWebVertxBuildItem vertxBuildItem, BeanContainerBuildItem bc) { - if (config.enabled) { - recorder.setup(config, vertxBuildItem.getVertx(), bc.getValue()); + recorder.setup(config, buildTimeConfig, vertxBuildItem.getVertx(), bc.getValue()); + } + + static class IsEnabled implements BooleanSupplier { + OidcBuildTimeConfig config; + + public boolean getAsBoolean() { + return config.enabled; } } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 8c6c060b8872a..6e58af3a13dfc 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -86,7 +86,7 @@ public CompletionStage getChallenge(RoutingContext context) { List scopes = new ArrayList<>(); scopes.add("openid"); - scopes.addAll(config.authentication.scopes); + config.authentication.scopes.ifPresent(scopes::addAll); params.put("scopes", new JsonArray(scopes)); params.put("redirect_uri", buildRedirectUri(context)); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcBuildTimeConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcBuildTimeConfig.java new file mode 100644 index 0000000000000..92a666d785923 --- /dev/null +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcBuildTimeConfig.java @@ -0,0 +1,38 @@ +package io.quarkus.oidc.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Build time configuration for OIDC. + */ +@ConfigRoot +public class OidcBuildTimeConfig { + /** + * If the OIDC extension is enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean enabled; + + /** + * The application type, which can be one of the following values from enum {@link ApplicationType}. + */ + @ConfigItem(defaultValue = "service") + public ApplicationType applicationType; + + public enum ApplicationType { + /** + * A {@code WEB_APP} is a client that server pages, usually a frontend application. For this type of client the + * Authorization Code Flow is + * defined as the preferred method for authenticating users. + */ + WEB_APP, + + /** + * A {@code SERVICE} is a client that has a set of protected HTTP resources, usually a backend application following the + * RESTful Architectural Design. For this type of client, the Bearer Authorization method is defined as the preferred + * method for authenticating and authorizing users. + */ + SERVICE + } +} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java index ed478e8260e38..7d9aee11b475b 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java @@ -12,12 +12,6 @@ @ConfigRoot(phase = ConfigPhase.RUN_TIME) public class OidcConfig { - /** - * If the OIDC extension is enabled. - */ - @ConfigItem(defaultValue = "true") - public boolean enabled; - /** * The base URL of the OpenID Connect (OIDC) server, for example, 'https://host:port/auth'. * All the other OIDC server page and service URLs are derived from this URL. @@ -75,12 +69,6 @@ public class OidcConfig { */ Authentication authentication; - /** - * The application type, which can be one of the following values from enum {@link ApplicationType}.. - */ - @ConfigItem(defaultValue = "service") - ApplicationType applicationType; - public String getAuthServerUrl() { return authServerUrl; } @@ -97,10 +85,6 @@ public Roles getRoles() { return roles; } - public ApplicationType getApplicationType() { - return applicationType; - } - @ConfigGroup public static class Credentials { @@ -163,22 +147,6 @@ public static class Authentication { * */ @ConfigItem - public List scopes; - } - - public enum ApplicationType { - /** - * A {@code WEB_APP} is a client that server pages, usually a frontend application. For this type of client the - * Authorization Code Flow is - * defined as the preferred method for authenticating users. - */ - WEB_APP, - - /** - * A {@code SERVICE} is a client that has a set of protected HTTP resources, usually a backend application following the - * RESTful Architectural Design. For this type of client, the Bearer Authorization method is defined as the preferred - * method for authenticating and authorizing users. - */ - SERVICE + public Optional> scopes; } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 538f3f07a781f..52850a8a437e9 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -21,7 +21,7 @@ public class OidcRecorder { private static final Logger LOG = Logger.getLogger(OidcRecorder.class); - public void setup(OidcConfig config, RuntimeValue vertx, BeanContainer beanContainer) { + public void setup(OidcConfig config, OidcBuildTimeConfig btConfig, RuntimeValue vertx, BeanContainer beanContainer) { OAuth2ClientOptions options = new OAuth2ClientOptions(); // Base IDP server URL @@ -92,9 +92,9 @@ public void handle(AsyncResult event) { identityProvider.setConfig(config); AbstractOidcAuthenticationMechanism mechanism = null; - if (OidcConfig.ApplicationType.SERVICE.equals(config.applicationType)) { + if (OidcBuildTimeConfig.ApplicationType.SERVICE.equals(btConfig.applicationType)) { mechanism = beanContainer.instance(BearerAuthenticationMechanism.class); - } else if (OidcConfig.ApplicationType.WEB_APP.equals(config.applicationType)) { + } else if (OidcBuildTimeConfig.ApplicationType.WEB_APP.equals(btConfig.applicationType)) { mechanism = beanContainer.instance(CodeAuthenticationMechanism.class); } From a80b5f064bfa5e223e529f4bca8a8b6a39beabc8 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:08:47 -0600 Subject: [PATCH 031/602] Do not cache config object in RestClientBase --- .../java/io/quarkus/restclient/runtime/RestClientBase.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java index 4f05cce3357fe..8bfacff3c5c02 100644 --- a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java +++ b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java @@ -14,7 +14,7 @@ import javax.net.ssl.HostnameVerifier; import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.rest.client.RestClientBuilder; public class RestClientBase { @@ -38,12 +38,9 @@ public class RestClientBase { private final String baseUriFromAnnotation; private final String propertyPrefix; - private final Config config; - public RestClientBase(Class proxyType, String baseUriFromAnnotation, String propertyPrefix) { this.proxyType = proxyType; this.baseUriFromAnnotation = baseUriFromAnnotation; - this.config = ConfigProviderResolver.instance().getConfig(); this.propertyPrefix = propertyPrefix; } @@ -211,6 +208,7 @@ private void configureBaseUrl(RestClientBuilder builder) { } private Optional getOptionalProperty(String propertyFormat, Class type) { + final Config config = ConfigProvider.getConfig(); Optional interfaceNameValue = config.getOptionalValue(String.format(propertyFormat, proxyType.getName()), type); return interfaceNameValue.isPresent() ? interfaceNameValue : config.getOptionalValue(String.format(propertyFormat, propertyPrefix), type); From 717278665053a2d32f0f5f0460b0b1ebb6bb4525 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:09:16 -0600 Subject: [PATCH 032/602] Use Optional for empty list in security extension --- .../io/quarkus/security/deployment/SecurityConfig.java | 3 ++- .../quarkus/security/deployment/SecurityProcessor.java | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityConfig.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityConfig.java index 98871ec136c1e..9097ba3dedb55 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityConfig.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityConfig.java @@ -1,6 +1,7 @@ package io.quarkus.security.deployment; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -16,5 +17,5 @@ public final class SecurityConfig { * List of security providers to enable for reflection */ @ConfigItem - public List securityProviders; + public Optional> securityProviders; } diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index 949f134a91eb6..0b04ffdcffcad 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -74,11 +75,9 @@ public class SecurityProcessor { @BuildStep void services(BuildProducer jcaProviders) { // Create JCAProviderBuildItems for any configured provider names - if (security.securityProviders != null) { - for (String providerName : security.securityProviders) { - jcaProviders.produce(new JCAProviderBuildItem(providerName)); - log.debugf("Added providerName: %s", providerName); - } + for (String providerName : security.securityProviders.orElse(Collections.emptyList())) { + jcaProviders.produce(new JCAProviderBuildItem(providerName)); + log.debugf("Added providerName: %s", providerName); } } From beb7b2a92bbf1c9b01ad73ba3e402afe84e4ee21 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:09:43 -0600 Subject: [PATCH 033/602] Use ConfigProvider.getConfig() in smallrye-reactive-messaging --- .../deployment/SmallRyeReactiveMessagingProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index 85dcf93459609..7eebdbc360786 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -12,7 +12,7 @@ import javax.enterprise.context.Dependent; import javax.enterprise.inject.spi.DeploymentException; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.reactive.messaging.spi.Connector; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -236,7 +236,7 @@ public void build(SmallRyeReactiveMessagingRecorder recorder, RecorderContext re recorder.registerMediators(configurations, beanContainer.getValue()); for (EmitterBuildItem it : emitterFields) { - int defaultBufferSize = ConfigProviderResolver.instance().getConfig() + int defaultBufferSize = ConfigProvider.getConfig() .getOptionalValue("smallrye.messaging.emitter.default-buffer-size", Integer.class).orElse(127); if (it.getOverflow() != null) { recorder.configureEmitter(beanContainer.getValue(), it.getName(), it.getOverflow(), it.getBufferSize(), From bb11e2e62ab1c7791b3c45212085e0d2fc548c62 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:10:25 -0600 Subject: [PATCH 034/602] TikaProcessorTest must register a configuration now --- .../tika/deployment/TikaProcessorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java index c1b0dbcf9583e..f91b52b559e51 100644 --- a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java +++ b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java @@ -7,10 +7,43 @@ import java.util.List; import java.util.Optional; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + public class TikaProcessorTest { + // We must register a configuration otherwise we'll get an exception. + + static volatile SmallRyeConfig config; + + @BeforeAll + public static void setItUp() { + final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); + builder.addDefaultSources(); + builder.addDiscoveredConverters(); + builder.addDiscoveredSources(); + config = builder.build(); + QuarkusConfigFactory.setConfig(config); + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + final Config existingConfig = cpr.getConfig(); + if (existingConfig != TikaProcessorTest.config) { + cpr.releaseConfig(existingConfig); + } + } + + @AfterAll + public static void tearItDown() { + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + cpr.releaseConfig(config); + } + @Test public void testSupportedParserNames() throws Exception { Optional parserNames = Optional.of("pdf"); From 5d6d05edd8ac17af97f41df3e7a8c04d550cffaf Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:11:27 -0600 Subject: [PATCH 035/602] Optional websocket config should be marked optional --- .../websockets/deployment/UndertowWebsocketProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java index c5b199b1e60d6..a4a5e84610dfe 100644 --- a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/UndertowWebsocketProcessor.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import javax.websocket.ClientEndpoint; @@ -174,11 +175,11 @@ static class HotReloadConfig { /** * The security key for remote hot deployment */ - String password; + Optional password; /** * The remote URL to connect to */ - String url; + Optional url; } } From e4fe074c0edf05f4d6b7044e5cccec25d22fbf5d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:12:01 -0600 Subject: [PATCH 036/602] No configuration is available when hot deployment happens --- .../deployment/WebsocketHotReloadSetup.java | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java index 517257292e73e..24e2d2a41f216 100644 --- a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java @@ -6,10 +6,8 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.List; -import java.util.Optional; import java.util.Properties; -import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; import io.quarkus.deployment.devmode.HotReplacementContext; @@ -33,24 +31,17 @@ public class WebsocketHotReloadSetup implements HotReplacementSetup { @Override public void setupHotDeployment(HotReplacementContext hotReplacementContext) { - Optional password = ConfigProvider.getConfig() - .getOptionalValue(HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD_PASSWORD, String.class); - if (password.isPresent()) { - replacementPassword = password.get(); - } else { - - List resources = hotReplacementContext.getResourcesDir(); - if (!resources.isEmpty()) { - //TODO: fix this - File appConfig = resources.get(0).resolve("application.properties").toFile(); - if (appConfig.isFile()) { - try (InputStream pw = new FileInputStream(appConfig)) { - Properties p = new Properties(); - p.load(pw); - replacementPassword = p.getProperty(HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD_PASSWORD); - } catch (IOException e) { - logger.error("Failed to read application.properties", e); - } + List resources = hotReplacementContext.getResourcesDir(); + if (!resources.isEmpty()) { + //TODO: fix this + File appConfig = resources.get(0).resolve("application.properties").toFile(); + if (appConfig.isFile()) { + try (InputStream pw = new FileInputStream(appConfig)) { + Properties p = new Properties(); + p.load(pw); + replacementPassword = p.getProperty(HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD_PASSWORD); + } catch (IOException e) { + logger.error("Failed to read application.properties", e); } } } From de94dd330e69523fb0eaebae8107a71c65a79e44 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:13:45 -0600 Subject: [PATCH 037/602] Move vertx-http build time config to separate class and use Optional for empty lists --- .../http/deployment/VertxHttpProcessor.java | 9 +++++---- .../vertx/http/runtime/FormAuthConfig.java | 2 +- .../http/runtime/HttpBuildTimeConfig.java | 6 ++++++ .../vertx/http/runtime/HttpConfiguration.java | 10 ++-------- .../http/runtime/PolicyMappingConfig.java | 5 +++-- .../vertx/http/runtime/ServerSslConfig.java | 3 ++- .../vertx/http/runtime/VertxHttpRecorder.java | 7 +++---- .../vertx/http/runtime/cors/CORSConfig.java | 8 ++++---- .../vertx/http/runtime/cors/CORSFilter.java | 20 ++++++++++--------- .../security/FormAuthenticationMechanism.java | 4 ++-- .../PathMatchingHttpSecurityPolicy.java | 8 +++++--- 11 files changed, 44 insertions(+), 38 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 10529a8c85d63..2ec89082b8ba1 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.OptionalInt; import java.util.stream.Collectors; import org.eclipse.microprofile.config.ConfigProvider; @@ -40,8 +41,8 @@ class VertxHttpProcessor { @BuildStep - HttpRootPathBuildItem httpRoot(HttpBuildTimeConfig config) { - return new HttpRootPathBuildItem(config.rootPath); + HttpRootPathBuildItem httpRoot(HttpBuildTimeConfig httpBuildTimeConfig) { + return new HttpRootPathBuildItem(httpBuildTimeConfig.rootPath); } @BuildStep @@ -75,7 +76,7 @@ void filterMultipleVertxInstancesWarning(LaunchModeBuildItem launchModeBuildItem @BuildStep(onlyIf = IsNormal.class) @Record(value = ExecutionTime.RUNTIME_INIT, optional = true) public KubernetesPortBuildItem kubernetes(HttpConfiguration config, VertxHttpRecorder recorder) { - int port = ConfigProvider.getConfig().getOptionalValue("quarkus.http.port", Integer.class).orElse(8080); + int port = ConfigProvider.getConfig().getValue("quarkus.http.port", OptionalInt.class).orElse(8080); recorder.warnIfPortChanged(config, port); return new KubernetesPortBuildItem(config.port, "http"); } @@ -127,7 +128,7 @@ ServiceStartBuildItem finalizeRouter( defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode()); - boolean startVirtual = requireVirtual.isPresent() || httpConfiguration.virtual; + boolean startVirtual = requireVirtual.isPresent() || httpBuildTimeConfig.virtual; if (startVirtual) { reflectiveClass .produce(new ReflectiveClassBuildItem(true, false, false, VirtualServerChannel.class)); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java index de98eb467c90a..0eb5eb09798a5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java @@ -31,7 +31,7 @@ public class FormAuthConfig { /** * The landing page to redirect to if there is no saved page to redirect back to */ - @ConfigItem + @ConfigItem(defaultValue = "/index.html") public String landingPage; /** diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index 904f812417188..41147163da72f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -15,4 +15,10 @@ public class HttpBuildTimeConfig { public AuthConfig auth; + /** + * If this is true then only a virtual channel will be set up for vertx web. + * We have this switch for testing purposes. + */ + @ConfigItem + public boolean virtual; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java index 636c8d4e1a8fa..2016bfc2bdda5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.runtime; import java.time.Duration; +import java.util.Optional; import java.util.OptionalInt; import io.quarkus.runtime.LaunchMode; @@ -69,13 +70,6 @@ public class HttpConfiguration { @ConfigItem public OptionalInt ioThreads; - /** - * If this is true then only a virtual channel will be set up for vertx web. - * We have this switch for testing purposes. - */ - @ConfigItem(defaultValue = "false") - public boolean virtual; - /** * Server limits configuration */ @@ -100,7 +94,7 @@ public class HttpConfiguration { * is not suitable for production environments. This must be more than 16 characters long for security reasons */ @ConfigItem(name = "auth.session.encryption-key") - public String encryptionKey; + public Optional encryptionKey; public int determinePort(LaunchMode launchMode) { return launchMode == LaunchMode.TEST ? testPort : port; diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyMappingConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyMappingConfig.java index 231efe41c3562..7d59ce7132944 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyMappingConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/PolicyMappingConfig.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.runtime; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -30,7 +31,7 @@ public class PolicyMappingConfig { * */ @ConfigItem - public List methods; + public Optional> methods; /** * The paths that this permission check applies to. If the path ends in /* then this is treated @@ -43,5 +44,5 @@ public class PolicyMappingConfig { * */ @ConfigItem - public List paths; + public Optional> paths; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java index 08ec9669bbf49..a7e5b589bfec8 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.runtime; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -21,7 +22,7 @@ public class ServerSslConfig { * The cipher suites to use. If none is given, a reasonable default is selected. */ @ConfigItem - public List cipherSuites; + public Optional> cipherSuites; /** * The list of protocols to explicitly enable. diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index bb70fcaa2ba6d..ccdbdc3c4a8d5 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -355,10 +356,8 @@ private static HttpServerOptions createSslOptions(HttpConfiguration httpConfigur serverOptions); } - for (String cipher : sslConfig.cipherSuites) { - if (!cipher.isEmpty()) { - serverOptions.addEnabledCipherSuite(cipher); - } + for (String cipher : sslConfig.cipherSuites.orElse(Collections.emptyList())) { + serverOptions.addEnabledCipherSuite(cipher); } for (String protocol : sslConfig.protocols) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java index 36eedcccd58e4..306d7b477fcb1 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSConfig.java @@ -20,7 +20,7 @@ public class CORSConfig { * default: returns any requested origin as valid */ @ConfigItem - public List origins; + public Optional> origins; /** * HTTP methods allowed for CORS @@ -31,7 +31,7 @@ public class CORSConfig { * default: returns any requested method as valid */ @ConfigItem - public List methods; + public Optional> methods; /** * HTTP headers allowed for CORS @@ -42,7 +42,7 @@ public class CORSConfig { * default: returns any requested header as valid */ @ConfigItem - public List headers; + public Optional> headers; /** * HTTP headers exposed in CORS @@ -52,7 +52,7 @@ public class CORSConfig { * default: empty */ @ConfigItem - public List exposedHeaders; + public Optional> exposedHeaders; /** * The `Access-Control-Max-Age` response header value indicating diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index ecb9a3164dcb7..a217a0529e15f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -1,10 +1,11 @@ package io.quarkus.vertx.http.runtime.cors; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.regex.Pattern; -import java.util.stream.Collectors; import io.vertx.core.Handler; import io.vertx.core.http.HttpHeaders; @@ -26,7 +27,7 @@ public CORSFilter(CORSConfig corsConfig) { } private void processRequestedHeaders(HttpServerResponse response, String allowHeadersValue) { - if (corsConfig.headers.isEmpty()) { + if (!corsConfig.headers.isPresent()) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeadersValue); } else { List requestedHeaders = new ArrayList<>(); @@ -35,7 +36,7 @@ private void processRequestedHeaders(HttpServerResponse response, String allowHe } List validRequestedHeaders = new ArrayList<>(); - for (String configHeader : corsConfig.headers) { + for (String configHeader : corsConfig.headers.get()) { if (requestedHeaders.contains(configHeader.toLowerCase())) { validRequestedHeaders.add(configHeader); } @@ -48,7 +49,7 @@ private void processRequestedHeaders(HttpServerResponse response, String allowHe } private void processMethods(HttpServerResponse response, String allowMethodsValue) { - if (corsConfig.methods.isEmpty()) { + if (!corsConfig.methods.isPresent()) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); } else { List requestedMethods = new ArrayList<>(); @@ -57,7 +58,7 @@ private void processMethods(HttpServerResponse response, String allowMethodsValu } List validRequestedMethods = new ArrayList<>(); - for (HttpMethod configMethod : corsConfig.methods) { + for (HttpMethod configMethod : corsConfig.methods.get()) { if (requestedMethods.contains(configMethod.name().toLowerCase())) { validRequestedMethods.add(configMethod.name()); } @@ -90,7 +91,7 @@ public void handle(RoutingContext event) { processRequestedHeaders(response, requestedHeaders); } - boolean allowsOrigin = corsConfig.origins.isEmpty() || corsConfig.origins.contains(origin); + boolean allowsOrigin = !corsConfig.origins.isPresent() || corsConfig.origins.get().contains(origin); if (allowsOrigin) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); @@ -98,10 +99,11 @@ public void handle(RoutingContext event) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - final String exposedHeaders = corsConfig.exposedHeaders.stream().collect(Collectors.joining(",")); + final Optional> exposedHeaders = corsConfig.exposedHeaders; - if (!exposedHeaders.isEmpty()) { - response.headers().set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposedHeaders); + if (exposedHeaders.isPresent()) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, + String.join(",", exposedHeaders.orElse(Collections.emptyList()))); } if (request.method().equals(HttpMethod.OPTIONS)) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java index 4cab439d87940..3e909ce55c67f 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java @@ -47,7 +47,7 @@ public FormAuthenticationMechanism() { public void init(HttpConfiguration httpConfiguration, HttpBuildTimeConfig buildTimeConfig) { String key; - if (httpConfiguration.encryptionKey.isEmpty()) { + if (!httpConfiguration.encryptionKey.isPresent()) { if (encryptionKey != null) { //persist across dev mode restarts key = encryptionKey; @@ -58,7 +58,7 @@ public void init(HttpConfiguration httpConfiguration, HttpBuildTimeConfig buildT log.warn("Encryption key was not specified for persistent FORM auth, using temporary key " + key); } } else { - key = httpConfiguration.encryptionKey; + key = httpConfiguration.encryptionKey.get(); } FormAuthConfig form = buildTimeConfig.auth.form; loginManager = new PersistentLoginManager(key, "quarkus-credential", form.timeout.toMillis(), diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/PathMatchingHttpSecurityPolicy.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/PathMatchingHttpSecurityPolicy.java index 24badd427437a..361aa106d323b 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/PathMatchingHttpSecurityPolicy.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/PathMatchingHttpSecurityPolicy.java @@ -84,12 +84,14 @@ void init(HttpBuildTimeConfig config, Map> throw new RuntimeException("Unable to find HTTP security policy " + entry.getValue().policy); } - for (String path : entry.getValue().paths) { + for (String path : entry.getValue().paths.orElse(Collections.emptyList())) { if (tempMap.containsKey(path)) { - HttpMatcher m = new HttpMatcher(new HashSet<>(entry.getValue().methods), checker); + HttpMatcher m = new HttpMatcher(new HashSet<>(entry.getValue().methods.orElse(Collections.emptyList())), + checker); tempMap.get(path).add(m); } else { - HttpMatcher m = new HttpMatcher(new HashSet<>(entry.getValue().methods), checker); + HttpMatcher m = new HttpMatcher(new HashSet<>(entry.getValue().methods.orElse(Collections.emptyList())), + checker); List perms = new ArrayList<>(); tempMap.put(path, perms); perms.add(m); From 255159808246c7bb62fd870dc988b6e839a9ad5e Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:14:28 -0600 Subject: [PATCH 038/602] Remove unused field fro vertx-web processor --- .../java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index 3739282f1e7c2..e1d7458b9001e 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -74,8 +74,6 @@ class VertxWebProcessor { private static final DotName[] ROUTE_PARAM_TYPES = { ROUTING_CONTEXT, RX_ROUTING_CONTEXT, ROUTING_EXCHANGE }; private static final DotName[] ROUTE_FILTER_TYPES = { ROUTING_CONTEXT }; - HttpConfiguration httpConfiguration; - @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.VERTX_WEB); From 381dc9367ccb9969afa365f5183e9b403575680b Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:15:31 -0600 Subject: [PATCH 039/602] Modify test extension to properly layer configurations --- .../deployment/src/main/resources/application.properties | 2 ++ .../java/io/quarkus/logging/AdditionalHandlersTest.java | 7 ++++++- .../java/io/quarkus/logging/AsyncConsoleHandlerTest.java | 4 ++++ .../test/java/io/quarkus/logging/AsyncFileHandlerTest.java | 4 ++++ .../java/io/quarkus/logging/AsyncSyslogHandlerTest.java | 4 ++++ .../test/java/io/quarkus/logging/ConsoleHandlerTest.java | 6 +++++- .../src/test/java/io/quarkus/logging/FileHandlerTest.java | 6 +++++- .../io/quarkus/logging/PeriodicRotatingLoggingTest.java | 4 ++++ .../quarkus/logging/PeriodicSizeRotatingLoggingTest.java | 4 ++++ .../java/io/quarkus/logging/SizeRotatingLoggingTest.java | 4 ++++ .../test/java/io/quarkus/logging/SyslogHandlerTest.java | 6 +++++- 11 files changed, 47 insertions(+), 4 deletions(-) diff --git a/core/test-extension/deployment/src/main/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties index 894276bfffe01..9c904afc68904 100644 --- a/core/test-extension/deployment/src/main/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -48,6 +48,8 @@ quarkus.btrt.all-values.nested-config-map.key1.nested-value=value1 quarkus.btrt.all-values.nested-config-map.key1.oov=value1.1+value1.2 quarkus.btrt.all-values.nested-config-map.key2.nested-value=value2 quarkus.btrt.all-values.nested-config-map.key2.oov=value2.1+value2.2 +quarkus.btrt.all-values.string-list=value1,value2 +quarkus.btrt.all-values.long-list=1,2,3 ### Configuration settings for the TestRunTimeConfig config root quarkus.rt.rt-string-opt=rtStringOptValue diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java index c5a21f0c6d09c..911ccd99d1238 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java @@ -9,6 +9,8 @@ import java.util.logging.Logger; import org.jboss.logmanager.handlers.DelayedHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -17,9 +19,12 @@ import io.quarkus.test.QuarkusUnitTest; public class AdditionalHandlersTest { + @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withConfigurationResource("application-additional-handlers.properties"); + .withConfigurationResource("application-additional-handlers.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); @Test public void additionalHandlersConfigurationTest() { diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java index 0c1c2543bb4fc..11c64e17dc845 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java @@ -11,6 +11,8 @@ import org.jboss.logmanager.handlers.AsyncHandler; import org.jboss.logmanager.handlers.ConsoleHandler; import org.jboss.logmanager.handlers.DelayedHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,6 +24,8 @@ public class AsyncConsoleHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-async-console-log.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("AsyncConsoleHandlerTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java index 3a7694fab493c..8a2130123f659 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java @@ -11,6 +11,8 @@ import org.jboss.logmanager.handlers.AsyncHandler; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.FileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,6 +24,8 @@ public class AsyncFileHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-async-file-log.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("AsyncFileHandlerTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java index b59fe7f55349c..23bdb08f74a47 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java @@ -11,6 +11,8 @@ import org.jboss.logmanager.handlers.AsyncHandler; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SyslogHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,6 +24,8 @@ public class AsyncSyslogHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-async-syslog.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("AsyncSyslogHandlerTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java index 25d14b8aac735..9207216456445 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java @@ -12,6 +12,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.ConsoleHandler; import org.jboss.logmanager.handlers.DelayedHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,7 +24,9 @@ public class ConsoleHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withConfigurationResource("application-console-output.properties"); + .withConfigurationResource("application-console-output.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); @Test public void consoleOutputTest() { diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java index 417b7dd410704..6af882744b3bc 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java @@ -12,6 +12,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.FileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,7 +24,9 @@ public class FileHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withConfigurationResource("application-file-output-log.properties"); + .withConfigurationResource("application-file-output-log.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); @Test public void fileOutputTest() { diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java index 657128a74f6fd..30d714602a24e 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java @@ -12,6 +12,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.PeriodicRotatingFileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -23,6 +25,8 @@ public class PeriodicRotatingLoggingTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-periodic-file-log-rotating.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("PeriodicRotatingLoggingTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java index e0af0bd2d9e9a..fdf584a222510 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java @@ -12,6 +12,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -23,6 +25,8 @@ public class PeriodicSizeRotatingLoggingTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-periodic-size-file-log-rotating.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("PeriodicSizeRotatingLoggingTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java index 1e187ebbecda7..466ed5d3d3e7a 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java @@ -12,6 +12,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SizeRotatingFileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -23,6 +25,8 @@ public class SizeRotatingLoggingTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withConfigurationResource("application-size-file-log-rotating.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")) .setLogFileName("SizeRotatingLoggingTest.log"); @Test diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java index 9c4b64d97594c..a8761b5ae7a4b 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java @@ -14,6 +14,8 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SyslogHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -24,7 +26,9 @@ public class SyslogHandlerTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withConfigurationResource("application-syslog-output.properties"); + .withConfigurationResource("application-syslog-output.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); @Test public void syslogOutputTest() { From f5b2a45118510f49e2baa658abe74c07998e9f0b Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:16:12 -0600 Subject: [PATCH 040/602] Modify test framework to use a cleaner test URL setting and to clean up stale configs --- .../io/quarkus/it/rest/ClientResource.java | 9 ++-- .../test/common/TestResourceManager.java | 6 +++ .../http/TestHTTPConfigSourceProvider.java | 49 +++++++++++++++++++ .../common/http/TestHTTPResourceManager.java | 13 +---- ...croprofile.config.spi.ConfigSourceProvider | 1 + 5 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java create mode 100644 test-framework/common/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java index 00dc29d79f246..75b40eba3588e 100644 --- a/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java @@ -10,6 +10,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -50,7 +51,7 @@ public String baseUriConfigKey() { @Path("/manual") public String manual() throws Exception { ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() - .baseUrl(new URL(System.getProperty("test.url"))) + .baseUrl(new URL(ConfigProvider.getConfig().getValue("test.url", String.class))) .build(ProgrammaticRestInterface.class); return iface.get(); } @@ -72,7 +73,7 @@ public CompletionStage asyncCdi() { @Produces("application/json") public TestResource.MyData getDataManual() throws Exception { ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() - .baseUrl(new URL(System.getProperty("test.url"))) + .baseUrl(new URL(ConfigProvider.getConfig().getValue("test.url", String.class))) .build(ProgrammaticRestInterface.class); return iface.getData(); } @@ -96,7 +97,7 @@ public CompletionStage getDataAsync() { @Produces("application/json") public List complexManual() throws Exception { ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() - .baseUrl(new URL(System.getProperty("test.url"))) + .baseUrl(new URL(ConfigProvider.getConfig().getValue("test.url", String.class))) .build(ProgrammaticRestInterface.class); System.out.println(iface.complex()); return iface.complex(); @@ -114,7 +115,7 @@ public List complexCdi() { @Produces("application/json") public Map getAllHeaders(String headerValue) throws Exception { ProgrammaticRestInterface client = RestClientBuilder.newBuilder() - .baseUrl(new URL(System.getProperty("test.url"))) + .baseUrl(new URL(ConfigProvider.getConfig().getValue("test.url", String.class))) .build(ProgrammaticRestInterface.class); return client.getAllHeaders(); } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java index 77a8276d2e16a..b52dc90832e03 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java @@ -23,6 +23,7 @@ import java.util.ServiceLoader; import java.util.Set; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; @@ -83,6 +84,11 @@ public void stop() { throw new RuntimeException("Unable to stop Quarkus test resource " + testResource, e); } } + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + } } @SuppressWarnings("unchecked") diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java new file mode 100644 index 0000000000000..887e880605cc4 --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java @@ -0,0 +1,49 @@ +package io.quarkus.test.common.http; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +/** + * + */ +public class TestHTTPConfigSourceProvider implements ConfigSourceProvider { + + static final String TEST_URL_VALUE = "http://${quarkus.http.host:localhost}:${quarkus.http.test-port:8081}${quarkus.servlet.context-path:}"; + static final String TEST_URL_KEY = "test.url"; + + static final String TEST_URL_SSL_VALUE = "https://${quarkus.http.host:localhost}:${quarkus.http.test-ssl-port:8444}${quarkus.servlet.context-path:}"; + static final String TEST_URL_SSL_KEY = "test.url.ssl"; + + static final Map entries; + + static { + Map map = new HashMap<>(); + map.put(TEST_URL_KEY, TEST_URL_VALUE); + map.put(TEST_URL_SSL_KEY, TEST_URL_SSL_VALUE); + entries = Collections.unmodifiableMap(map); + } + + public Iterable getConfigSources(final ClassLoader forClassLoader) { + return Collections.singletonList(new ConfigSource() { + public Map getProperties() { + return entries; + } + + public String getValue(final String propertyName) { + return entries.get(propertyName); + } + + public String getName() { + return "test URL provider"; + } + + public int getOrdinal() { + return Integer.MIN_VALUE + 1000; + } + }); + } +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java index 104000e22dcd6..3eac43f4ab436 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java @@ -7,25 +7,16 @@ import java.util.Map; import java.util.ServiceLoader; -import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; public class TestHTTPResourceManager { public static String getUri() { - Config config = ConfigProvider.getConfig(); - String host = config.getOptionalValue("quarkus.http.host", String.class).orElse("localhost"); - String port = config.getOptionalValue("quarkus.http.test-port", String.class).orElse("8081"); - String contextPath = config.getOptionalValue("quarkus.servlet.context-path", String.class).orElse(""); - return "http://" + host + ":" + port + contextPath; + return ConfigProvider.getConfig().getValue("test.url", String.class); } public static String getSslUri() { - Config config = ConfigProvider.getConfig(); - String host = config.getOptionalValue("quarkus.http.host", String.class).orElse("localhost"); - String port = config.getOptionalValue("quarkus.http.test-ssl-port", String.class).orElse("8444"); - String contextPath = config.getOptionalValue("quarkus.servlet.context-path", String.class).orElse(""); - return "https://" + host + ":" + port + contextPath; + return ConfigProvider.getConfig().getValue("test.url.ssl", String.class); } public static void inject(Object testCase) { diff --git a/test-framework/common/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider b/test-framework/common/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider new file mode 100644 index 0000000000000..6a237fbd72fe4 --- /dev/null +++ b/test-framework/common/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider @@ -0,0 +1 @@ +io.quarkus.test.common.http.TestHTTPConfigSourceProvider From 3ebd1e281dc497d90d41e4174213e570f29effb9 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:17:23 -0600 Subject: [PATCH 041/602] Fix test extension integration test using test extension to provide all required config --- .../src/main/resources/application.properties | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/integration-tests/test-extension/src/main/resources/application.properties b/integration-tests/test-extension/src/main/resources/application.properties index 93c02a609e050..f0e0f2e6a638c 100644 --- a/integration-tests/test-extension/src/main/resources/application.properties +++ b/integration-tests/test-extension/src/main/resources/application.properties @@ -6,3 +6,98 @@ quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n # Resource path to DSAPublicKey base64 encoded bytes quarkus.root.dsa-key-location=/DSAPublicKey.encoded + +### Configuration settings for the TestBuildTimeConfig config root +quarkus.bt.bt-string-opt=btStringOptValue +quarkus.bt.bt-sbv=StringBasedValue +# This is not set so that we should get the @ConfigItem defaultValue +#quarkus.bt.bt-sbv-with-default=StringBasedValue +quarkus.bt.all-values.oov=configPart1+configPart2 +quarkus.bt.all-values.ovo=configPart1+configPart2 +# This is not set so that we should get the @ConfigItem defaultValue +#quarkus.bt.bt-oov-with-default=ObjectOfValue +quarkus.bt.all-values.long-primitive=1234567891 +quarkus.bt.all-values.double-primitive=3.1415926535897932384 +quarkus.bt.all-values.long-value=1234567892 +quarkus.bt.all-values.opt-long-value=1234567893 +quarkus.bt.all-values.opt-double-value=3.1415926535897932384 +quarkus.bt.all-values.optional-long-value=1234567894 +quarkus.bt.all-values.nested-config-map.key1.nested-value=value1 +quarkus.bt.all-values.nested-config-map.key1.oov=value1.1+value1.2 +quarkus.bt.all-values.nested-config-map.key2.nested-value=value2 +quarkus.bt.all-values.nested-config-map.key2.oov=value2.1+value2.2 +quarkus.bt.all-values.string-list=value1,value2 +quarkus.bt.all-values.long-list=1,2,3 + +### Duplicate settings for the TestBuildAndRunTimeConfig. May be able to drop if ConfigRoot inheritance is added +quarkus.btrt.bt-string-opt=btStringOptValue +quarkus.btrt.bt-sbv=StringBasedValue +quarkus.btrt.all-values.oov=configPart1+configPart2 +quarkus.btrt.all-values.ovo=configPart1+configPart2 +quarkus.btrt.all-values.long-primitive=1234567891 +quarkus.btrt.all-values.double-primitive=3.1415926535897932384 +quarkus.btrt.all-values.long-value=1234567892 +quarkus.btrt.all-values.opt-long-value=1234567893 +quarkus.btrt.all-values.opt-double-value=3.1415926535897932384 +quarkus.btrt.all-values.optional-long-value=1234567894 +quarkus.btrt.all-values.nested-config-map.key1.nested-value=value1 +quarkus.btrt.all-values.nested-config-map.key1.oov=value1.1+value1.2 +quarkus.btrt.all-values.nested-config-map.key2.nested-value=value2 +quarkus.btrt.all-values.nested-config-map.key2.oov=value2.1+value2.2 +quarkus.btrt.all-values.string-list=value1,value2 +quarkus.btrt.all-values.long-list=1,2,3 + +### Configuration settings for the TestRunTimeConfig config root +quarkus.rt.rt-string-opt=rtStringOptValue +quarkus.rt.rt-string-opt-with-default=rtStringOptWithDefaultValue +quarkus.rt.all-values.oov=configPart1+configPart2 +quarkus.rt.all-values.ovo=configPart1+configPart2 +quarkus.rt.all-values.long-primitive=12345678911 +quarkus.rt.all-values.double-primitive=3.1415926535897932384 +quarkus.rt.all-values.long-value=12345678921 +quarkus.rt.all-values.opt-long-value=12345678931 +quarkus.rt.all-values.opt-double-value=3.1415926535897932384 +quarkus.rt.all-values.optional-long-value=12345678941 +quarkus.rt.all-values.nested-config-map.key1.nested-value=value1 +quarkus.rt.all-values.nested-config-map.key1.oov=value1.1+value1.2 +quarkus.rt.all-values.nested-config-map.key2.nested-value=value2 +quarkus.rt.all-values.nested-config-map.key2.oov=value2.1+value2.2 +quarkus.rt.all-values.string-list=value1,value2 +quarkus.rt.all-values.long-list=1,2,3 +# A nested map of properties +quarkus.rt.all-values.string-map.key1=value1 +quarkus.rt.all-values.string-map.key2=value2 +quarkus.rt.all-values.string-map.key3=value3 +# And list form +quarkus.rt.all-values.string-list-map.key1=value1,value2,value3 +quarkus.rt.all-values.string-list-map.key2=value4,value5 +quarkus.rt.all-values.string-list-map.key3=value6 +# A root map of properties +quarkus.rt.string-map.key1=value1 +quarkus.rt.string-map.key2=value2 +quarkus.rt.string-map.key3=value3 +# And list form +quarkus.rt.string-list-map.key1=value1 +quarkus.rt.string-list-map.key2=value2,value3 +quarkus.rt.string-list-map.key3=value4,value5,value6 + +### run time configuration using enhanced converters +quarkus.rt.my-enum=enum-two +quarkus.rt.my-enums=enum-one,enum-two +quarkus.rt.my-optional-enums=optional +quarkus.rt.no-hyphenate-first-enum=ENUM_ONE +quarkus.rt.no-hyphenate-second-enum=Enum_Two +quarkus.rt.primitive-boolean=YES +quarkus.rt.object-boolean=NO +quarkus.rt.primitive-integer=two +quarkus.rt.object-integer=nine +quarkus.rt.one-to-nine=one,two,three,four,five,six,seven,eight,nine +quarkus.rt.map-of-numbers.key1=one +quarkus.rt.map-of-numbers.key2=two + + +### build time and run time configuration using enhanced converters +quarkus.btrt.map-of-numbers.key1=one +quarkus.btrt.map-of-numbers.key2=two +quarkus.btrt.my-enum=optional +quarkus.btrt.my-enums=optional,enum-one,enum-two From 7fae55d4f0adc392a78b8b81dca684036b7082a7 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 13 Nov 2019 10:10:51 -0600 Subject: [PATCH 042/602] Fix native image launcher so that it has a config at start --- .../test/common/NativeImageLauncher.java | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java index 2e9a963279bb7..ea738d5ba3507 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java @@ -13,11 +13,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.ServiceLoader; -import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.quarkus.test.common.http.TestHTTPResourceManager; +import io.smallrye.config.SmallRyeConfig; public class NativeImageLauncher implements Closeable { @@ -32,15 +38,33 @@ public class NativeImageLauncher implements Closeable { private final Map systemProps = new HashMap<>(); private List startedNotifiers; - public NativeImageLauncher(Class testClass) { + private NativeImageLauncher(Class testClass, Config config) { this(testClass, - ConfigProvider.getConfig().getOptionalValue("quarkus.http.test-port", Integer.class).orElse(DEFAULT_PORT), - ConfigProvider.getConfig().getOptionalValue("quarkus.test.native-image-wait-time", Long.class) - .orElse(DEFAULT_IMAGE_WAIT_TIME), - ConfigProvider.getConfig().getOptionalValue("quarkus.test.native-image-profile", String.class) + config.getValue("quarkus.http.test-port", OptionalInt.class).orElse(DEFAULT_PORT), + config.getValue("quarkus.test.native-image-wait-time", OptionalLong.class).orElse(DEFAULT_IMAGE_WAIT_TIME), + config.getOptionalValue("quarkus.test.native-image-profile", String.class) .orElse(null)); } + public NativeImageLauncher(Class testClass) { + // todo: accessing run time config from here doesn't make sense + this(testClass, installAndGetSomeConfig()); + } + + private static Config installAndGetSomeConfig() { + final SmallRyeConfig config = ConfigUtils.configBuilder().build(); + QuarkusConfigFactory.setConfig(config); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + final Config installed = cpr.getConfig(); + if (installed != config) { + cpr.releaseConfig(installed); + } + } catch (IllegalStateException ignored) { + } + return config; + } + public NativeImageLauncher(Class testClass, int port, long imageWaitTime, String profile) { this.testClass = testClass; this.port = port; From 9e08e4059a7af3bdd36e6152a570c62f509e9c8d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 15:22:23 -0600 Subject: [PATCH 043/602] Introduce new configuration framework and update SmallRye Config version Fixes #4123, Fixes #4119, Fixes #4172, Fixes (part of) #2292 Fixes #734, Fixes #4512, Fixes #3498, Fixes #3937 Fixes #2816, Fixes #4077, Fixes #3516, Fixes #2760 Fixes #1989, Fixes #3030, Fixes #3637, Fixes #1887 Groundwork for #2489, Fixes #5116, Fixes #5192, Fixes #3888 Fixes #3089, Fixes #390, Fixes #891, Fixes #5484 Fixes #5454 --- bom/runtime/pom.xml | 7 +- .../creator/phase/augment/AugmentTask.java | 48 +- .../generateconfig/GenerateConfigTask.java | 22 +- .../quarkus/deployment/ApplicationConfig.java | 6 +- .../quarkus/deployment/ExtensionLoader.java | 250 ++-- .../io/quarkus/deployment/JniProcessor.java | 8 +- .../io/quarkus/deployment/QuarkusConfig.java | 4 + .../builditem/ApplicationInfoBuildItem.java | 12 +- .../BuildTimeConfigurationBuildItem.java | 19 - ...imeRunTimeFixedConfigurationBuildItem.java | 19 - .../builditem/ConfigurationBuildItem.java | 19 + .../RunTimeConfigurationBuildItem.java | 19 - .../RunTimeConfigurationProxyBuildItem.java | 20 + .../builditem/UnmatchedConfigBuildItem.java | 34 - .../configuration/BooleanConfigType.java | 133 -- .../BuildTimeConfigurationReader.java | 757 ++++++++++ .../configuration/CompoundConfigType.java | 65 - .../configuration/ConfigDefinition.java | 610 -------- .../deployment/configuration/ConfigType.java | 130 -- .../DefaultValuesConfigurationSource.java | 22 +- .../configuration/DoubleConfigType.java | 137 -- .../configuration/FloatConfigType.java | 138 -- .../configuration/GroupConfigType.java | 300 ---- .../configuration/IntConfigType.java | 137 -- .../configuration/LeafConfigType.java | 102 -- .../configuration/LongConfigType.java | 136 -- .../configuration/MapConfigType.java | 128 -- .../configuration/ObjectConfigType.java | 137 -- .../configuration/ObjectListConfigType.java | 138 -- .../OptionalObjectConfigType.java | 120 -- .../configuration/PropertiesUtil.java | 22 +- .../RunTimeConfigurationGenerator.java | 1270 +++++++++++++++++ .../definition/ClassDefinition.java | 279 ++++ .../configuration/definition/Definition.java | 35 + .../definition/GroupDefinition.java | 19 + .../definition/RootDefinition.java | 108 ++ .../{ => matching}/ConfigPatternMap.java | 86 +- .../configuration/matching/Container.java | 63 + .../matching/FieldContainer.java | 62 + .../configuration/matching/MapContainer.java | 46 + .../matching/PatternMapBuilder.java | 85 ++ .../configuration/type/ArrayOf.java | 53 + .../configuration/type/CollectionOf.java | 49 + .../configuration/type/ConverterType.java | 112 ++ .../deployment/configuration/type/Leaf.java | 47 + .../configuration/type/LowerBoundCheckOf.java | 49 + .../configuration/type/MinMaxValidated.java | 69 + .../configuration/type/OptionalOf.java | 43 + .../configuration/type/PatternValidated.java | 49 + .../configuration/type/UpperBoundCheckOf.java | 49 + .../quarkus/deployment/pkg/NativeConfig.java | 9 +- .../quarkus/deployment/pkg/PackageConfig.java | 3 +- .../pkg/steps/JarResultBuildStep.java | 3 +- .../pkg/steps/NativeImageBuildStep.java | 25 +- .../deployment/steps/ConfigBuildSteps.java | 107 ++ .../steps/ConfigDescriptionBuildStep.java | 60 +- .../deployment/steps/ConfigurationSetup.java | 830 ----------- .../deployment/steps/MainClassBuildStep.java | 38 +- .../quarkus/deployment/util/ReflectUtil.java | 67 + .../io/quarkus/runner/RuntimeClassLoader.java | 23 + .../java/io/quarkus/runner/RuntimeRunner.java | 21 +- .../main/java/io/quarkus/dev/DevModeMain.java | 11 +- .../generate_doc/ConfigDoItemFinder.java | 4 + .../java/io/quarkus/runtime/Application.java | 8 - .../configuration/BuildTimeConfigFactory.java | 46 - .../configuration/ConfigDiagnostic.java | 79 + .../configuration/ConfigInstantiator.java | 95 +- .../runtime/configuration/ConfigUtils.java | 222 +-- .../configuration/ConfigurationException.java | 46 + .../configuration/ConverterSupport.java | 1 + .../configuration/DefaultConfigSource.java | 53 - .../DeploymentProfileConfigSource.java | 5 + .../configuration/DurationConverter.java | 3 + .../configuration/ExpandingConfigSource.java | 4 + .../configuration/HyphenateEnumConverter.java | 12 +- .../configuration/MemorySizeConverter.java | 3 + .../runtime/configuration/NameIterator.java | 25 +- .../runtime/configuration/PathConverter.java | 2 +- .../configuration/QuarkusConfigFactory.java | 29 + .../runtime/configuration/RegexConverter.java | 2 +- .../SimpleConfigurationProviderResolver.java | 36 - .../runtime/configuration/Substitutions.java | 18 - .../TemporaryConfigSourceProvider.java | 17 - .../graal/ConfigurationSubstitutions.java | 49 + .../io/quarkus/runtime/util/StringUtil.java | 37 + .../io.smallrye.config.SmallRyeConfigFactory | 1 + ...croprofile.config.spi.ConfigSourceProvider | 1 - .../configuration/ConfigExpanderTestCase.java | 6 +- .../configuration/ConfigProfileTestCase.java | 6 +- .../java/io/quarkus/maven/RemoteDevMojo.java | 19 +- .../test/common/NativeImageLauncher.java | 2 +- 91 files changed, 4324 insertions(+), 3976 deletions(-) delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java rename core/deployment/src/main/java/io/quarkus/deployment/configuration/{ => matching}/ConfigPatternMap.java (62%) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java create mode 100644 core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory delete mode 100644 core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index d54bdab762c64..012ce836bdf4f 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -31,7 +31,7 @@ 1.3.1 1.0 1.3.4 - 1.3.9 + 1.4.1 2.1.0 2.3.1 1.1.20 @@ -1083,6 +1083,11 @@ + + io.smallrye + smallrye-config-common + ${smallrye-config.version} + io.smallrye smallrye-health diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java index 2da772eaae17a..0ae7a3f8c65f0 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java @@ -15,7 +15,9 @@ import java.util.Properties; import java.util.function.Consumer; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import io.quarkus.bootstrap.BootstrapDependencyProcessingException; @@ -36,8 +38,11 @@ import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.JarBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; /** * This phase consumes {@link CurateOutcome} and processes @@ -110,32 +115,25 @@ public AugmentOutcome run(CurateOutcome appState, CuratedApplicationCreator ctx) } //first lets look for some config, as it is not on the current class path //and we need to load it to run the build process - Path config = configDir.resolve("application.properties"); - if (Files.exists(config)) { + Path configPath = configDir.resolve("application.properties"); + SmallRyeConfigBuilder configBuilder = ConfigUtils.configBuilder(false); + if (Files.exists(configPath)) { try { - ConfigBuilder builder = SmallRyeConfigProviderResolver.instance().getBuilder() - .addDefaultSources() - .addDiscoveredConverters() - .addDiscoveredSources() - .withSources(new PropertiesConfigSource(config.toUri().toURL())); - - if (configCustomizer != null) { - configCustomizer.accept(builder); - } - SmallRyeConfigProviderResolver.instance().registerConfig(builder.build(), - Thread.currentThread().getContextClassLoader()); - } catch (Exception e) { - throw new RuntimeException(e); + configBuilder.withSources(new PropertiesConfigSource(configPath.toUri().toURL())); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to convert config URL", e); } - } else if (configCustomizer != null) { - ConfigBuilder builder = SmallRyeConfigProviderResolver.instance().getBuilder() - .addDefaultSources() - .addDiscoveredConverters() - .addDiscoveredSources(); - - configCustomizer.accept(builder); - SmallRyeConfigProviderResolver.instance().registerConfig(builder.build(), - Thread.currentThread().getContextClassLoader()); + } + if (configCustomizer != null) { + configCustomizer.accept(configBuilder); + } + final SmallRyeConfig config = configBuilder.build(); + QuarkusConfigFactory.setConfig(config); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + final Config existing = cpr.getConfig(); + if (existing != config) { + cpr.releaseConfig(existing); + // subsequent calls will get the new config } final AppModelResolver depResolver = appState.getArtifactResolver(); diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java b/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java index 1532db5c95940..f8e75ce1b156a 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java @@ -16,6 +16,7 @@ import java.util.regex.Pattern; import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import io.quarkus.bootstrap.BootstrapDependencyProcessingException; @@ -40,8 +41,11 @@ import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; /** * This phase generates an example configuration file @@ -65,12 +69,16 @@ public Path run(CurateOutcome appState, CuratedApplicationCreator creator) throw //TODO: do we actually need to load this config? Does it affect resolution? if (Files.exists(configFile)) { try { - Config built = SmallRyeConfigProviderResolver.instance().getBuilder() - .addDefaultSources() - .addDiscoveredConverters() - .addDiscoveredSources() - .withSources(new PropertiesConfigSource(configFile.toUri().toURL())).build(); - SmallRyeConfigProviderResolver.instance().registerConfig(built, Thread.currentThread().getContextClassLoader()); + SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(false) + .withSources(new PropertiesConfigSource(configFile.toUri().toURL())); + final SmallRyeConfig config = builder.build(); + QuarkusConfigFactory.setConfig(config); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + final Config existing = cpr.getConfig(); + if (existing != config) { + cpr.releaseConfig(existing); + // subsequent calls will get the new config + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java index 180ae5ad3a113..e430259613cdc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java @@ -1,5 +1,7 @@ package io.quarkus.deployment; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -12,12 +14,12 @@ public class ApplicationConfig { * If not set, defaults to the name of the project. */ @ConfigItem - public String name; + public Optional name; /** * The version of the application. * If not set, defaults to the version of the project */ @ConfigItem - public String version; + public Optional version; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 2ccecac799b29..b6c92f5d9a0f2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -25,12 +25,11 @@ import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; -import java.util.Set; import java.util.concurrent.Executor; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -40,6 +39,7 @@ import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import org.wildfly.common.function.Functions; @@ -62,33 +62,33 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.annotations.Weak; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem; -import io.quarkus.deployment.builditem.BuildTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; +import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; import io.quarkus.deployment.builditem.CapabilityBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; -import io.quarkus.deployment.builditem.UnmatchedConfigBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource; +import io.quarkus.deployment.configuration.definition.RootDefinition; import io.quarkus.deployment.recording.BytecodeRecorderImpl; +import io.quarkus.deployment.recording.ObjectLoader; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.ReflectUtil; import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSource; -import io.quarkus.runtime.configuration.ConverterSupport; -import io.quarkus.runtime.configuration.DeploymentProfileConfigSource; -import io.quarkus.runtime.configuration.ExpandingConfigSource; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; -import io.smallrye.config.SmallRyeConfigProviderResolver; /** * Utility class to load build steps, runtime recorders, and configuration roots from a given extension class. @@ -104,11 +104,6 @@ private ExtensionLoader() { public static final String RUN_TIME_CONFIG = "io.quarkus.runtime.generated.RunTimeConfig"; public static final String RUN_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.RunTimeConfigRoot"; - private static final FieldDescriptor RUN_TIME_CONFIG_FIELD = FieldDescriptor.of(RUN_TIME_CONFIG, "runConfig", - RUN_TIME_CONFIG_ROOT); - private static final FieldDescriptor BUILD_TIME_CONFIG_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "buildConfig", - BUILD_TIME_CONFIG_ROOT); - private static final String CONFIG_ROOTS_LIST = "META-INF/quarkus-config-roots.list"; @SuppressWarnings("deprecation") @@ -171,44 +166,29 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, LaunchMode launchMode, Consumer configCustomizer) throws IOException, ClassNotFoundException { - // set up the configuration definitions - final ConfigDefinition buildTimeConfig = new ConfigDefinition(FieldDescriptor.of("Bogus", "No field", "Nothing")); - final ConfigDefinition buildTimeRunTimeConfig = new ConfigDefinition(BUILD_TIME_CONFIG_FIELD); - final ConfigDefinition runTimeConfig = new ConfigDefinition(RUN_TIME_CONFIG_FIELD, true); - - // populate it with all known types + // populate with all known types + List> roots = new ArrayList<>(); for (Class clazz : ServiceUtil.classesNamedIn(classLoader, CONFIG_ROOTS_LIST)) { final ConfigRoot annotation = clazz.getAnnotation(ConfigRoot.class); if (annotation == null) { cfgLog.warnf("Ignoring configuration root %s because it has no annotation", clazz); } else { - final ConfigPhase phase = annotation.phase(); - if (phase == ConfigPhase.RUN_TIME) { - runTimeConfig.registerConfigRoot(clazz); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - buildTimeRunTimeConfig.registerConfigRoot(clazz); - } else if (phase == ConfigPhase.BUILD_TIME) { - buildTimeConfig.registerConfigRoot(clazz); - } else { - cfgLog.warnf("Unrecognized configuration phase \"%s\" on %s", phase, clazz); - } + roots.add(clazz); } } + final BuildTimeConfigurationReader reader = new BuildTimeConfigurationReader(roots); + // now prepare & load the build configuration - final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); - - // expand properties - final ExpandingConfigSource.Cache cache = new ExpandingConfigSource.Cache(); - builder.withWrapper(ExpandingConfigSource.wrapper(cache)); - builder.withWrapper(DeploymentProfileConfigSource.wrapper()); - builder.addDefaultSources(); - final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); - final DefaultValuesConfigurationSource defaultSource = new DefaultValuesConfigurationSource( - buildTimeConfig.getLeafPatterns()); + final SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(false); + + final DefaultValuesConfigurationSource ds1 = new DefaultValuesConfigurationSource( + reader.getBuildTimePatternMap()); + final DefaultValuesConfigurationSource ds2 = new DefaultValuesConfigurationSource( + reader.getBuildTimeRunTimePatternMap()); final PropertiesConfigSource pcs = new PropertiesConfigSource(buildSystemProps, "Build system"); - builder.withSources(inJar, defaultSource, pcs); + builder.withSources(ds1, ds2, pcs); // populate builder with all converters loaded from ServiceLoader ConverterSupport.populateConverters(builder); @@ -216,43 +196,53 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, if (configCustomizer != null) { configCustomizer.accept(builder); } - final SmallRyeConfig src = (SmallRyeConfig) builder - .addDefaultSources() - .addDiscoveredSources() - .addDiscoveredConverters() - .build(); - - SmallRyeConfigProviderResolver.instance().registerConfig(src, classLoader); - - Set unmatched = new HashSet<>(); - - ConfigDefinition.loadConfiguration(cache, src, - unmatched, - buildTimeConfig, - buildTimeRunTimeConfig, // this one is only for generating a default-values config source - runTimeConfig); + final SmallRyeConfig src = builder.build(); + + // install globally + QuarkusConfigFactory.setConfig(src); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } - unmatched.removeIf(s -> !inJar.getPropertyNames().contains(s) && !s.startsWith("quarkus.")); + final BuildTimeConfigurationReader.ReadResult readResult = reader.readConfiguration(src); + // the proxy objects used for run time config in the recorders + Map, Object> proxies = new HashMap<>(); Consumer result = Functions.discardingConsumer(); - result = result.andThen(bcb -> bcb.addBuildStep(bc -> { - bc.produce(new BuildTimeConfigurationBuildItem(buildTimeConfig)); - bc.produce(new BuildTimeRunTimeFixedConfigurationBuildItem(buildTimeRunTimeConfig)); - bc.produce(new RunTimeConfigurationBuildItem(runTimeConfig)); - bc.produce(new UnmatchedConfigBuildItem(Collections.unmodifiableSet(unmatched))); - }).produces(BuildTimeConfigurationBuildItem.class) - .produces(BuildTimeRunTimeFixedConfigurationBuildItem.class) - .produces(RunTimeConfigurationBuildItem.class) - .produces(UnmatchedConfigBuildItem.class) - .build()); for (Class clazz : ServiceUtil.classesNamedIn(classLoader, "META-INF/quarkus-build-steps.list")) { try { - result = result - .andThen(ExtensionLoader.loadStepsFrom(clazz, buildTimeConfig, buildTimeRunTimeConfig, launchMode)); + result = result.andThen( + ExtensionLoader.loadStepsFrom(clazz, readResult, proxies, launchMode)); } catch (Throwable e) { throw new RuntimeException("Failed to load steps from " + clazz, e); } } + // this has to be an identity hash map else the recorder will get angry + Map proxyFields = new IdentityHashMap<>(); + for (Map.Entry, Object> entry : proxies.entrySet()) { + final RootDefinition def = readResult.requireRootDefinitionForClass(entry.getKey()); + proxyFields.put(entry.getValue(), def.getDescriptor()); + } + result = result.andThen(bcb -> bcb.addBuildStep(bc -> { + bc.produce(new ConfigurationBuildItem(readResult)); + bc.produce(new RunTimeConfigurationProxyBuildItem(proxies)); + final ObjectLoader loader = new ObjectLoader() { + public ResultHandle load(final BytecodeCreator body, final Object obj, final boolean staticInit) { + return body.readStaticField(proxyFields.get(obj)); + } + + public boolean canHandleObject(final Object obj, final boolean staticInit) { + return proxyFields.containsKey(obj); + } + }; + bc.produce(new BytecodeRecorderObjectLoaderBuildItem(loader)); + }).produces(ConfigurationBuildItem.class) + .produces(RunTimeConfigurationProxyBuildItem.class) + .produces(BytecodeRecorderObjectLoaderBuildItem.class) + .build()); return result; } @@ -260,13 +250,13 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, * Load all the build steps from the given class. * * @param clazz the class to load from (must not be {@code null}) - * @param buildTimeConfig the build time configuration (must not be {@code null}) - * @param buildTimeRunTimeConfig the build time/run time visible config (must not be {@code null}) - * @param launchMode + * @param readResult the build time configuration read result (must not be {@code null}) + * @param runTimeProxies the map of run time proxy objects to populate for recorders (must not be {@code null}) + * @param launchMode the launch mode * @return a consumer which adds the steps to the given chain builder */ - public static Consumer loadStepsFrom(Class clazz, ConfigDefinition buildTimeConfig, - ConfigDefinition buildTimeRunTimeConfig, final LaunchMode launchMode) { + public static Consumer loadStepsFrom(Class clazz, BuildTimeConfigurationReader.ReadResult readResult, + Map, Object> runTimeProxies, final LaunchMode launchMode) { final Constructor[] constructors = clazz.getDeclaredConstructors(); // this is the chain configuration that will contain all steps on this class and be returned Consumer chainConfig = Functions.discardingConsumer(); @@ -365,15 +355,14 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); consumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { - ctorParamFns.add(bc -> bc.consume(BuildTimeConfigurationBuildItem.class).getConfigDefinition() - .getRealizedInstance(parameterClass)); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - ctorParamFns.add(bc -> bc.consume(BuildTimeRunTimeFixedConfigurationBuildItem.class) - .getConfigDefinition().getRealizedInstance(parameterClass)); + if (phase.isAvailableAtBuild()) { + ctorParamFns.add(bc -> bc.consume(ConfigurationBuildItem.class).getReadResult() + .requireRootObjectForClass(parameterClass)); + if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - ctorParamFns.add(bc -> bc.consume(RunTimeConfigurationBuildItem.class).getConfigDefinition() - .getRealizedInstance(parameterClass)); + throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { throw reportError(parameterClass, "Unknown value for ConfigPhase"); } @@ -477,27 +466,18 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); consumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { - stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final BuildTimeConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeConfigurationBuildItem.class); - ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); - }); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + if (phase.isAvailableAtBuild()) { stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final BuildTimeRunTimeFixedConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeRunTimeFixedConfigurationBuildItem.class); + final ConfigurationBuildItem configurationBuildItem = bc + .consume(ConfigurationBuildItem.class); ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); + configurationBuildItem.getReadResult().requireRootObjectForClass(fieldClass)); }); + if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(fieldClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final RunTimeConfigurationBuildItem configurationBuildItem = bc - .consume(RunTimeConfigurationBuildItem.class); - ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); - }); + throw reportError(field, "Run time configuration cannot be consumed here"); } else { throw reportError(fieldClass, "Unknown value for ConfigPhase"); } @@ -566,16 +546,14 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe } else if (parameterClass.isAnnotationPresent(ConfigRoot.class)) { final ConfigRoot annotation = parameterClass.getAnnotation(ConfigRoot.class); final ConfigPhase phase = annotation.phase(); - ConfigDefinition confDef; - if (phase == ConfigPhase.BUILD_TIME) { - confDef = buildTimeConfig; - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - confDef = buildTimeRunTimeConfig; + if (phase.isAvailableAtBuild()) { + paramSuppList.add(() -> readResult.requireRootObjectForClass(parameterClass)); + } else if (phase == ConfigPhase.RUN_TIME) { + throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { throw reportError(parameter, "Unsupported conditional class configuration build phase " + phase); } - paramSuppList.add(() -> confDef.getRealizedInstance(parameterClass)); } else { throw reportError(parameter, "Unsupported conditional class constructor parameter type " + parameterClass); @@ -600,17 +578,15 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe } else if (fieldClass.isAnnotationPresent(ConfigRoot.class)) { final ConfigRoot annotation = fieldClass.getAnnotation(ConfigRoot.class); final ConfigPhase phase = annotation.phase(); - ConfigDefinition confDef; - if (phase == ConfigPhase.BUILD_TIME) { - confDef = buildTimeConfig; - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - confDef = buildTimeRunTimeConfig; + if (phase.isAvailableAtBuild()) { + setup = setup.andThen(o -> ReflectUtil.setFieldVal(field, o, + readResult.requireRootObjectForClass(fieldClass))); + } else if (phase == ConfigPhase.RUN_TIME) { + throw reportError(field, "Run time configuration cannot be consumed here"); } else { throw reportError(field, "Unsupported conditional class configuration build phase " + phase); } - setup = setup.andThen( - o -> ReflectUtil.setFieldVal(field, o, confDef.getRealizedInstance(fieldClass))); } else { throw reportError(field, "Unsupported conditional class field type " + fieldClass); } @@ -757,24 +733,27 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); methodConsumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { + if (phase.isAvailableAtBuild()) { methodParamFns.add((bc, bri) -> { - final BuildTimeConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); - }); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - methodParamFns.add((bc, bri) -> { - final BuildTimeRunTimeFixedConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeRunTimeFixedConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); + final ConfigurationBuildItem configurationBuildItem = bc + .consume(ConfigurationBuildItem.class); + return configurationBuildItem.getReadResult().requireRootObjectForClass(parameterClass); }); + if (isRecorder && phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - methodParamFns.add((bc, bri) -> { - final RunTimeConfigurationBuildItem configurationBuildItem = bc - .consume(RunTimeConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); - }); + if (isRecorder) { + methodParamFns.add((bc, bri) -> { + final RunTimeConfigurationProxyBuildItem proxies = bc + .consume(RunTimeConfigurationProxyBuildItem.class); + return proxies.getProxyObjectFor(parameterClass); + }); + runTimeProxies.computeIfAbsent(parameterClass, ReflectUtil::newInstance); + } else { + throw reportError(parameter, + "Run time configuration cannot be consumed here unless the method is a @Recorder"); + } } else { throw reportError(parameterClass, "Unknown value for ConfigPhase"); } @@ -873,15 +852,12 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe "Bytecode recorder is static but an injected config object is declared as run time"); } methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(RunTimeConfigurationBuildItem.class)); - } - if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) { - methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(BuildTimeRunTimeFixedConfigurationBuildItem.class)); + .andThen(bsb -> bsb.consumes(RunTimeConfigurationProxyBuildItem.class)); } - if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { + if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED) + || methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(BuildTimeConfigurationBuildItem.class)); + .andThen(bsb -> bsb.consumes(ConfigurationBuildItem.class)); } final Consume[] consumes = method.getAnnotationsByType(Consume.class); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java index ed314d1ae7649..1b667e9618af1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java @@ -1,6 +1,8 @@ package io.quarkus.deployment; +import java.util.Collections; import java.util.List; +import java.util.Optional; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -19,7 +21,7 @@ static class JniConfig { * Paths of library to load. */ @ConfigItem - List libraryPaths; + Optional> libraryPaths; /** * Enable JNI support. @@ -30,8 +32,8 @@ static class JniConfig { @BuildStep void setupJni(BuildProducer jniProducer) { - if ((jni.enable) || !jni.libraryPaths.isEmpty()) { - jniProducer.produce(new JniBuildItem(jni.libraryPaths)); + if ((jni.enable) || jni.libraryPaths.isPresent()) { + jniProducer.produce(new JniBuildItem(jni.libraryPaths.orElse(Collections.emptyList()))); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java index 96d949aa42914..9960962fc63cd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java @@ -12,6 +12,10 @@ import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.configuration.ConfigurationError; +/** + * @deprecated Do not use this class anymore, instead try {@code ConfigProvider.getConfig.getValue()} instead. + */ +@Deprecated public final class QuarkusConfig extends SimpleBuildItem { public static final QuarkusConfig INSTANCE = new QuarkusConfig(); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java index b2f3e696c276c..46502b45e8d41 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java @@ -1,5 +1,7 @@ package io.quarkus.deployment.builditem; +import java.util.Optional; + import io.quarkus.builder.item.SimpleBuildItem; public final class ApplicationInfoBuildItem extends SimpleBuildItem { @@ -9,16 +11,16 @@ public final class ApplicationInfoBuildItem extends SimpleBuildItem { private final String name; private final String version; - public ApplicationInfoBuildItem(String name, String version) { - this.name = name; - this.version = version; + public ApplicationInfoBuildItem(Optional name, Optional version) { + this.name = name.orElse(UNSET_VALUE); + this.version = version.orElse(UNSET_VALUE); } public String getName() { - return name == null ? UNSET_VALUE : name; + return name; } public String getVersion() { - return version == null ? UNSET_VALUE : version; + return version; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java deleted file mode 100644 index 70d4c58fb3a1d..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the build time configuration. - */ -public final class BuildTimeConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public BuildTimeConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java deleted file mode 100644 index 1c129c15a8a7e..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the build time configuration that is visible from run time. - */ -public final class BuildTimeRunTimeFixedConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public BuildTimeRunTimeFixedConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java new file mode 100644 index 0000000000000..0885df05f505a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; + +/** + * The build item which carries the build time configuration. + */ +public final class ConfigurationBuildItem extends SimpleBuildItem { + private final BuildTimeConfigurationReader.ReadResult readResult; + + public ConfigurationBuildItem(final BuildTimeConfigurationReader.ReadResult readResult) { + this.readResult = readResult; + } + + public BuildTimeConfigurationReader.ReadResult getReadResult() { + return readResult; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java deleted file mode 100644 index 83fe0e8ad0fc1..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the run time configuration. - */ -public final class RunTimeConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public RunTimeConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java new file mode 100644 index 0000000000000..7fb9c3a7e12bb --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.deployment.builditem; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item that carries all the "fake" run time config objects for use by recorders. + */ +public final class RunTimeConfigurationProxyBuildItem extends SimpleBuildItem { + private final Map, Object> objects; + + public RunTimeConfigurationProxyBuildItem(final Map, Object> objects) { + this.objects = objects; + } + + public Object getProxyObjectFor(Class clazz) { + return objects.get(clazz); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java deleted file mode 100644 index efa0d13064d48..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.quarkus.deployment.builditem; - -import java.util.Set; - -import org.wildfly.common.Assert; - -import io.quarkus.builder.item.SimpleBuildItem; - -/** - * An internal build item which relays the unmatched configuration key set from the extension loader - * to configuration setup stages. - */ -public final class UnmatchedConfigBuildItem extends SimpleBuildItem { - private final Set set; - - /** - * Construct a new instance. - * - * @param set the non-{@code null}, immutable set - */ - public UnmatchedConfigBuildItem(final Set set) { - Assert.checkNotNullParam("set", set); - this.set = set; - } - - /** - * Get the set. - * - * @return the set - */ - public Set getSet() { - return set; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java deleted file mode 100644 index ec86a06eeeeff..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class BooleanConfigType extends LeafConfigType { - private static final MethodDescriptor BOOL_VALUE_METHOD = MethodDescriptor.ofMethod(Boolean.class, "booleanValue", - boolean.class); - - final String defaultValue; - private final Class> converterClass; - - public BooleanConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Optional optionalValue = ConfigUtils.getOptionalValue(config, name.toString(), Boolean.class, - converterClass); - field.setBoolean(enclosing, optionalValue.orElse(Boolean.FALSE).booleanValue()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // ConfigUtils.getOptionalValue(config, name.toString(), Boolean.class, converterClass).orElse(Boolean.FALSE).booleanValue() - final ResultHandle optionalValue = body.checkCast(body.invokeStaticMethod( - CU_GET_OPT_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Boolean.class), loadConverterClass(body)), Optional.class); - final ResultHandle convertedDefault = body.readStaticField(FieldDescriptor.of(Boolean.class, "FALSE", Boolean.class)); - final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( - OPT_OR_ELSE_METHOD, - optionalValue, - convertedDefault), Boolean.class); - final ResultHandle booleanValue = body.invokeVirtualMethod(BOOL_VALUE_METHOD, defaultedValue); - body.invokeStaticMethod(setter, enclosing, booleanValue); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class> getConverterClass() { - return converterClass; - } - - @Override - public Class getItemClass() { - return boolean.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Boolean value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Boolean.class, - converterClass); - field.setBoolean(enclosing, value.booleanValue()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle value = body.invokeVirtualMethod(BOOL_VALUE_METHOD, getConvertedDefault(body, cache, config)); - body.invokeStaticMethod(setter, enclosing, value); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(BOOL_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Boolean.class), loadConverterClass(body)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java new file mode 100644 index 0000000000000..4c582eb46afa3 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -0,0 +1,757 @@ +package io.quarkus.deployment.configuration; + +import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; +import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; +import static io.quarkus.deployment.util.ReflectUtil.reportError; +import static io.quarkus.deployment.util.ReflectUtil.toError; +import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; +import static io.quarkus.deployment.util.ReflectUtil.unwrapInvocationTargetException; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.spi.Converter; +import org.jboss.logging.Logger; +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.GroupDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.deployment.configuration.matching.FieldContainer; +import io.quarkus.deployment.configuration.matching.MapContainer; +import io.quarkus.deployment.configuration.matching.PatternMapBuilder; +import io.quarkus.deployment.configuration.type.ArrayOf; +import io.quarkus.deployment.configuration.type.CollectionOf; +import io.quarkus.deployment.configuration.type.ConverterType; +import io.quarkus.deployment.configuration.type.Leaf; +import io.quarkus.deployment.configuration.type.LowerBoundCheckOf; +import io.quarkus.deployment.configuration.type.MinMaxValidated; +import io.quarkus.deployment.configuration.type.OptionalOf; +import io.quarkus.deployment.configuration.type.PatternValidated; +import io.quarkus.deployment.configuration.type.UpperBoundCheckOf; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.ExpandingConfigSource; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; +import io.quarkus.runtime.configuration.NameIterator; +import io.smallrye.config.Converters; +import io.smallrye.config.SmallRyeConfig; + +/** + * A configuration reader. + */ +public final class BuildTimeConfigurationReader { + private static final Logger log = Logger.getLogger("io.quarkus.config.build"); + + final ConfigPatternMap buildTimePatternMap; + final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap runTimePatternMap; + + final List buildTimeVisibleRoots; + final List allRoots; + + /** + * Construct a new instance. + * + * @param configRoots the configuration root class list (must not be {@code null}) + */ + public BuildTimeConfigurationReader(final List> configRoots) { + Assert.checkNotNullParam("configRoots", configRoots); + + List runTimeRoots = new ArrayList<>(); + List buildTimeRunTimeRoots = new ArrayList<>(); + List buildTimeRoots = new ArrayList<>(); + Map, GroupDefinition> groups = new HashMap<>(); + for (Class configRoot : configRoots) { + String name = ConfigItem.HYPHENATED_ELEMENT_NAME; + ConfigPhase phase = ConfigPhase.BUILD_TIME; + ConfigRoot annotation = configRoot.getAnnotation(ConfigRoot.class); + if (annotation != null) { + name = annotation.name(); + phase = annotation.phase(); + } + RootDefinition.Builder defBuilder = new RootDefinition.Builder(); + defBuilder.setConfigPhase(phase); + defBuilder.setRootName(name); + processClass(defBuilder, configRoot, groups); + RootDefinition definition = defBuilder.build(); + if (phase == ConfigPhase.BUILD_TIME) { + buildTimeRoots.add(definition); + } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + buildTimeRunTimeRoots.add(definition); + } else { + assert phase == ConfigPhase.RUN_TIME; + runTimeRoots.add(definition); + } + } + + runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots); + buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots); + buildTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRoots); + + buildTimeVisibleRoots = new ArrayList<>(buildTimeRoots.size() + buildTimeRunTimeRoots.size()); + buildTimeVisibleRoots.addAll(buildTimeRoots); + buildTimeVisibleRoots.addAll(buildTimeRunTimeRoots); + + List allRoots = new ArrayList<>(buildTimeVisibleRoots.size() + runTimeRoots.size()); + allRoots.addAll(buildTimeVisibleRoots); + allRoots.addAll(runTimeRoots); + + this.allRoots = allRoots; + } + + private static void processClass(ClassDefinition.Builder builder, Class clazz, + final Map, GroupDefinition> groups) { + builder.setConfigurationClass(clazz); + for (Field field : clazz.getDeclaredFields()) { + int mods = field.getModifiers(); + if (Modifier.isStatic(mods)) { + continue; + } + if (Modifier.isFinal(mods)) { + continue; + } + if (Modifier.isPrivate(mods)) { + throw reportError(field, "Configuration field may not be private"); + } + if (!Modifier.isPublic(mods) || !Modifier.isPublic(clazz.getModifiers())) { + field.setAccessible(true); + } + builder.addMember(processValue(field, field.getGenericType(), groups)); + } + } + + private static ClassDefinition.ClassMember.Specification processValue(Field field, Type valueType, + Map, GroupDefinition> groups) { + + Class valueClass = rawTypeOf(valueType); + final boolean isOptional = valueClass == Optional.class; + + if (valueClass == Map.class) { + if (!(valueType instanceof ParameterizedType)) { + throw reportError(field, "Map values must be parameterized"); + } + Class keyClass = rawTypeOfParameter(valueType, 0); + if (keyClass != String.class) { + throw reportError(field, "Map key types other than String are not yet supported"); + } + final ClassDefinition.ClassMember.Specification nested = processValue(field, typeOfParameter(valueType, 1), groups); + if (nested instanceof ClassDefinition.GroupMember.Specification + && ((ClassDefinition.GroupMember.Specification) nested).isOptional()) { + throw reportError(field, "Group map values may not be optional"); + } + return new ClassDefinition.MapMember.Specification(nested); + } else if (valueClass.getAnnotation(ConfigGroup.class) != null + || isOptional && rawTypeOfParameter(valueType, 0).getAnnotation(ConfigGroup.class) != null) { + Class groupClass; + if (isOptional) { + groupClass = rawTypeOfParameter(valueType, 0); + } else { + groupClass = valueClass; + } + GroupDefinition def = groups.get(groupClass); + if (def == null) { + final GroupDefinition.Builder subBuilder = new GroupDefinition.Builder(); + processClass(subBuilder, groupClass, groups); + groups.put(groupClass, def = subBuilder.build()); + } + return new ClassDefinition.GroupMember.Specification(field, def, isOptional); + } else { + final String defaultDefault; + // primitive values generally get their normal initializers as a default value + if (valueClass == boolean.class) { + defaultDefault = "false"; + } else if (valueClass.isPrimitive() && valueClass != char.class) { + defaultDefault = "0"; + } else { + defaultDefault = null; + } + ConfigItem configItem = field.getAnnotation(ConfigItem.class); + if (configItem != null) { + final String defaultVal = configItem.defaultValue(); + return new ClassDefinition.ItemMember.Specification(field, + defaultVal.equals(ConfigItem.NO_DEFAULT) ? defaultDefault : defaultVal); + } else { + ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class); + if (configProperty != null) { + log.warnf("Using @ConfigProperty for Quarkus configuration items is deprecated " + + "(use @ConfigItem instead) at %s#%s", field.getDeclaringClass().getName(), field.getName()); + final String defaultVal = configProperty.defaultValue(); + return new ClassDefinition.ItemMember.Specification(field, + defaultVal.equals(ConfigProperty.UNCONFIGURED_VALUE) ? defaultDefault : defaultVal); + } else { + // todo: should we log a warning that there is no annotation for the property, or just allow it? + return new ClassDefinition.ItemMember.Specification(field, defaultDefault); + } + } + } + } + + public ConfigPatternMap getBuildTimePatternMap() { + return buildTimePatternMap; + } + + public ConfigPatternMap getBuildTimeRunTimePatternMap() { + return buildTimeRunTimePatternMap; + } + + public ConfigPatternMap getRunTimePatternMap() { + return runTimePatternMap; + } + + public List getBuildTimeVisibleRoots() { + return buildTimeVisibleRoots; + } + + public List getAllRoots() { + return allRoots; + } + + public ReadResult readConfiguration(final SmallRyeConfig config) { + return new ReadOperation(config).run(); + } + + final class ReadOperation { + final SmallRyeConfig config; + final Set processedNames = new HashSet<>(); + + final Map, Object> objectsByRootClass = new HashMap<>(); + final Map specifiedRunTimeDefaultValues = new TreeMap<>(); + final Map buildTimeRunTimeVisibleValues = new TreeMap<>(); + + final Map> convByType = new HashMap<>(); + + ReadOperation(final SmallRyeConfig config) { + this.config = config; + } + + ReadResult run() { + final StringBuilder nameBuilder; + nameBuilder = new StringBuilder().append("quarkus"); + // eager init first + int len = nameBuilder.length(); + for (RootDefinition root : buildTimeVisibleRoots) { + Class clazz = root.getConfigurationClass(); + Object instance; + try { + Constructor cons = clazz.getDeclaredConstructor(); + cons.setAccessible(true); + instance = cons.newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + objectsByRootClass.put(clazz, instance); + String rootName = root.getRootName(); + nameBuilder.append('.').append(rootName); + readConfigGroup(root, instance, nameBuilder); + nameBuilder.setLength(len); + } + // sweep-up + for (String propertyName : config.getPropertyNames()) { + NameIterator ni = new NameIterator(propertyName); + if (ni.hasNext() && ni.nextSegmentEquals("quarkus")) { + ni.next(); + // build time patterns + Container matched = buildTimePatternMap.match(ni); + if (matched instanceof FieldContainer) { + ni.goToEnd(); + // cursor is located after group property key (if any) + getGroup((FieldContainer) matched, ni); + // we don't have to set the field because the group object init does it for us + continue; + } else if (matched != null) { + assert matched instanceof MapContainer; + // it's a leaf value within a map + // these must always be explicitly set + ni.goToEnd(); + // cursor is located after the map key + final String key = ni.getPreviousSegment(); + final Map map = getMap((MapContainer) matched, ni); + // we always have to set the map entry ourselves + Field field = matched.findField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + map.put(key, config.getValue(propertyName, converter)); + continue; + } + // build time (run time visible) patterns + ni.goToStart(); + ni.next(); + matched = buildTimeRunTimePatternMap.match(ni); + if (matched instanceof FieldContainer) { + ni.goToEnd(); + // cursor is located after group property key (if any) + getGroup((FieldContainer) matched, ni); + buildTimeRunTimeVisibleValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + continue; + } else if (matched != null) { + assert matched instanceof MapContainer; + // it's a leaf value within a map + // these must always be explicitly set + ni.goToEnd(); + // cursor is located after the map key + final String key = ni.getPreviousSegment(); + final Map map = getMap((MapContainer) matched, ni); + // we always have to set the map entry ourselves + Field field = matched.findField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + map.put(key, config.getValue(propertyName, converter)); + // cache the resolved value + buildTimeRunTimeVisibleValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + continue; + } + // run time patterns + ni.goToStart(); + ni.next(); + matched = runTimePatternMap.match(ni); + if (matched != null) { + // it's a specified run-time default (record for later) + boolean old = ExpandingConfigSource.setExpanding(false); + try { + specifiedRunTimeDefaultValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + } finally { + ExpandingConfigSource.setExpanding(old); + } + } + } else { + // it's not managed by us; record it + boolean old = ExpandingConfigSource.setExpanding(false); + try { + specifiedRunTimeDefaultValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + } finally { + ExpandingConfigSource.setExpanding(old); + } + } + } + return new ReadResult(objectsByRootClass, specifiedRunTimeDefaultValues, buildTimeRunTimeVisibleValues, + buildTimePatternMap, buildTimeRunTimePatternMap, runTimePatternMap, allRoots); + } + + /** + * Get a matched group. The tree node points to the property within the group that was matched. + * + * @param matched the matcher tree node + * @param ni the name iterator, positioned after the group member key (if any) + * @return the (possibly new) group instance + */ + private Object getGroup(FieldContainer matched, NameIterator ni) { + final ClassDefinition.ClassMember classMember = matched.getClassMember(); + ClassDefinition definition = matched.findEnclosingClass(); + Class configurationClass = definition.getConfigurationClass(); + if (definition instanceof RootDefinition) { + // found the root + return objectsByRootClass.get(configurationClass); + } + Container parent = matched.getParent(); + final boolean consume = !classMember.getPropertyName().isEmpty(); + if (consume) { + ni.previous(); + } + // now the cursor is *before* the group member key but after the base group + if (parent instanceof FieldContainer) { + FieldContainer parentClass = (FieldContainer) parent; + Field field = parentClass.findField(); + // the cursor is located after the enclosing group's property name (if any) + Object enclosing = getGroup(parentClass, ni); + // cursor restored to after group member key + if (consume) { + ni.next(); + } + if ((classMember instanceof ClassDefinition.GroupMember) + && ((ClassDefinition.GroupMember) classMember).isOptional()) { + Optional opt; + try { + opt = (Optional) field.get(enclosing); + } catch (IllegalAccessException e) { + throw toError(e); + } + if (opt.isPresent()) { + return opt.get(); + } else { + Object instance = recreateGroup(ni, definition, configurationClass); + try { + field.set(enclosing, Optional.of(instance)); + } catch (IllegalAccessException e) { + throw toError(e); + } + return instance; + } + } else { + try { + return field.get(enclosing); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + } else { + assert parent instanceof MapContainer; + final MapContainer parentMap = (MapContainer) parent; + Map map = getMap(parentMap, ni); + // the base group is a map, so the previous segment is the key of the map + String key = ni.getPreviousSegment(); + Object instance = map.get(key); + if (instance == null) { + instance = recreateGroup(ni, definition, configurationClass); + map.put(key, instance); + } + // cursor restored to after group member key + if (consume) { + ni.next(); + } + return instance; + } + } + + /** + * Get a matched map. The tree node points to the position after the map key. + * + * @param matched the matcher tree node + * @param ni the name iterator, positioned just after the map key; restored on exit + * @return the map + */ + private Map getMap(MapContainer matched, NameIterator ni) { + Container parent = matched.getParent(); + if (parent instanceof FieldContainer) { + FieldContainer parentClass = (FieldContainer) parent; + Field field = parentClass.findField(); + ni.previous(); + // now the cursor is before our map key and after the enclosing group property (if any) + Object instance = getGroup(parentClass, ni); + ni.next(); + // cursor restored + try { + return getFieldAsMap(field, instance); + } catch (IllegalAccessException e) { + throw toError(e); + } + } else { + assert parent instanceof MapContainer; + ni.previous(); + // now the cursor is before our map key and after the enclosing map key + Map map = getMap((MapContainer) parent, ni); + ni.next(); + // cursor restored + String key = ni.getPreviousSegment(); + Map instance = getAsMap(map, key); + if (instance == null) { + instance = new HashMap<>(); + map.put(key, instance); + } + return instance; + } + } + + private Object recreateGroup(final NameIterator ni, final ClassDefinition definition, + final Class configurationClass) { + // re-create this config group + final Object instance; + try { + instance = configurationClass.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + // the name includes everything up to (but not including) the member key + final StringBuilder nameBuilder = new StringBuilder(ni.getAllPreviousSegments()); + readConfigGroup(definition, instance, nameBuilder); + return instance; + } + + @SuppressWarnings("unchecked") + private Map getAsMap(final Map map, final String key) { + return (Map) map.get(key); + } + + @SuppressWarnings("unchecked") + private Map getFieldAsMap(final Field field, final Object instance) throws IllegalAccessException { + return (Map) field.get(instance); + } + + /** + * Read a configuration group, recursing into nested groups and instantiating empty maps. + * + * @param definition the definition of the configuration group + * @param instance the group instance + * @param nameBuilder the name builder (set to the last segment before the current group's property names) + */ + private void readConfigGroup(ClassDefinition definition, Object instance, final StringBuilder nameBuilder) { + for (ClassDefinition.ClassMember member : definition.getMembers()) { + Field field = member.getField(); + if (member instanceof ClassDefinition.MapMember) { + // get these on the sweep-up + try { + field.set(instance, new TreeMap<>()); + } catch (IllegalAccessException e) { + throw toError(e); + } + continue; + } + String propertyName = member.getPropertyName(); + if (member instanceof ClassDefinition.ItemMember) { + ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + int len = nameBuilder.length(); + try { + if (!propertyName.isEmpty()) { + nameBuilder.append('.').append(propertyName); + } + String fullName = nameBuilder.toString(); + if (processedNames.add(fullName)) { + readConfigValue(fullName, leafMember, instance); + } + } finally { + nameBuilder.setLength(len); + } + } else { + assert member instanceof ClassDefinition.GroupMember; + // construct the nested instance + ClassDefinition.GroupMember groupMember = (ClassDefinition.GroupMember) member; + if (groupMember.isOptional()) { + try { + field.set(instance, Optional.empty()); + } catch (IllegalAccessException e) { + throw toError(e); + } + } else { + Class clazz = groupMember.getGroupDefinition().getConfigurationClass(); + Object nestedInstance; + try { + nestedInstance = clazz.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } + try { + field.set(instance, nestedInstance); + } catch (IllegalAccessException e) { + throw toError(e); + } + if (propertyName.isEmpty()) { + readConfigGroup( + groupMember.getGroupDefinition(), nestedInstance, nameBuilder); + } else { + int len = nameBuilder.length(); + try { + nameBuilder.append('.').append(propertyName); + readConfigGroup( + groupMember.getGroupDefinition(), nestedInstance, nameBuilder); + } finally { + nameBuilder.setLength(len); + } + } + } + } + } + } + + private void readConfigValue(String fullName, ClassDefinition.ItemMember member, Object instance) { + Field field = member.getField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + Object val = config.getValue(fullName, converter); + try { + field.set(instance, val); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Converter getConverter(SmallRyeConfig config, Field field, ConverterType valueType) { + Converter converter = convByType.get(valueType); + if (converter != null) { + return converter; + } + if (valueType instanceof ArrayOf) { + ArrayOf arrayOf = (ArrayOf) valueType; + converter = Converters.newArrayConverter( + getConverter(config, field, arrayOf.getElementType()), + arrayOf.getArrayType()); + } else if (valueType instanceof CollectionOf) { + CollectionOf collectionOf = (CollectionOf) valueType; + Class collectionClass = collectionOf.getCollectionClass(); + final Converter nested = getConverter(config, field, collectionOf.getElementType()); + if (collectionClass == List.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.listFactory()); + } else if (collectionClass == Set.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.setFactory()); + } else if (collectionClass == SortedSet.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.sortedSetFactory()); + } else { + throw reportError(field, "Unsupported configuration collection type: %s", collectionClass); + } + } else if (valueType instanceof Leaf) { + Leaf leaf = (Leaf) valueType; + Class> convertWith = leaf.getConvertWith(); + if (convertWith != null) { + try { + final Constructor> ctor; + // TODO: temporary until type param inference is in + if (convertWith == HyphenateEnumConverter.class.asSubclass(Converter.class)) { + ctor = convertWith.getConstructor(Class.class); + converter = ctor.newInstance(valueType.getLeafType()); + } else { + ctor = convertWith.getConstructor(); + converter = ctor.newInstance(); + } + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + } else { + converter = config.getConverter(leaf.getLeafType()); + } + } else if (valueType instanceof LowerBoundCheckOf) { + // todo: add in bounds checker + converter = getConverter(config, field, ((LowerBoundCheckOf) valueType).getClassConverterType()); + } else if (valueType instanceof UpperBoundCheckOf) { + // todo: add in bounds checker + converter = getConverter(config, field, ((UpperBoundCheckOf) valueType).getClassConverterType()); + } else if (valueType instanceof MinMaxValidated) { + MinMaxValidated minMaxValidated = (MinMaxValidated) valueType; + String min = minMaxValidated.getMin(); + boolean minInclusive = minMaxValidated.isMinInclusive(); + String max = minMaxValidated.getMax(); + boolean maxInclusive = minMaxValidated.isMaxInclusive(); + Converter nestedConverter = getConverter(config, field, minMaxValidated.getNestedType()); + if (min != null) { + if (max != null) { + converter = Converters.rangeValueStringConverter((Converter) nestedConverter, min, minInclusive, max, + maxInclusive); + } else { + converter = Converters.minimumValueStringConverter((Converter) nestedConverter, min, minInclusive); + } + } else { + assert min == null && max != null; + converter = Converters.maximumValueStringConverter((Converter) nestedConverter, max, maxInclusive); + } + } else if (valueType instanceof OptionalOf) { + OptionalOf optionalOf = (OptionalOf) valueType; + converter = Converters.newOptionalConverter(getConverter(config, field, optionalOf.getNestedType())); + } else if (valueType instanceof PatternValidated) { + PatternValidated patternValidated = (PatternValidated) valueType; + converter = Converters.patternValidatingConverter(getConverter(config, field, patternValidated.getNestedType()), + patternValidated.getPatternString()); + } else { + throw Assert.unreachableCode(); + } + convByType.put(valueType, converter); + return converter; + } + } + + public static final class ReadResult { + final Map, Object> objectsByRootClass; + final Map specifiedRunTimeDefaultValues; + final Map buildTimeRunTimeVisibleValues; + final ConfigPatternMap buildTimePatternMap; + final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap runTimePatternMap; + final Map, RootDefinition> runTimeRootsByClass; + final List allRoots; + + ReadResult(final Map, Object> objectsByRootClass, final Map specifiedRunTimeDefaultValues, + final Map buildTimeRunTimeVisibleValues, + final ConfigPatternMap buildTimePatternMap, + final ConfigPatternMap buildTimeRunTimePatternMap, + final ConfigPatternMap runTimePatternMap, final List allRoots) { + this.objectsByRootClass = objectsByRootClass; + this.specifiedRunTimeDefaultValues = specifiedRunTimeDefaultValues; + this.buildTimeRunTimeVisibleValues = buildTimeRunTimeVisibleValues; + this.buildTimePatternMap = buildTimePatternMap; + this.buildTimeRunTimePatternMap = buildTimeRunTimePatternMap; + this.runTimePatternMap = runTimePatternMap; + this.allRoots = allRoots; + Map, RootDefinition> map = new HashMap<>(); + for (RootDefinition root : allRoots) { + map.put(root.getConfigurationClass(), root); + } + runTimeRootsByClass = map; + } + + public Map, Object> getObjectsByRootClass() { + return objectsByRootClass; + } + + public Object requireRootObjectForClass(Class clazz) { + Object obj = objectsByRootClass.get(clazz); + if (obj == null) { + throw new IllegalStateException("No root found for " + clazz); + } + return obj; + } + + public Map getSpecifiedRunTimeDefaultValues() { + return specifiedRunTimeDefaultValues; + } + + public Map getBuildTimeRunTimeVisibleValues() { + return buildTimeRunTimeVisibleValues; + } + + public ConfigPatternMap getBuildTimePatternMap() { + return buildTimePatternMap; + } + + public ConfigPatternMap getBuildTimeRunTimePatternMap() { + return buildTimeRunTimePatternMap; + } + + public ConfigPatternMap getRunTimePatternMap() { + return runTimePatternMap; + } + + public List getAllRoots() { + return allRoots; + } + + public RootDefinition requireRootDefinitionForClass(Class clazz) { + final RootDefinition def = runTimeRootsByClass.get(clazz); + if (def == null) { + throw new IllegalStateException("No root definition found for " + clazz); + } + return def; + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java deleted file mode 100644 index 4cc3734a8d0d4..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.quarkus.deployment.configuration; - -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A node which contains other nodes. - */ -public abstract class CompoundConfigType extends ConfigType { - CompoundConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - super(containingName, container, consumeSegment); - } - - /** - * Get or create a child instance of this node. - * - * @param name the property name of the child instance (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @param self the instance of this node (must not be {@code null}) - * @param childName the static child name, or {@code null} if the child name is dynamic - * @return the child instance - */ - abstract Object getChildObject(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config, - Object self, String childName); - - abstract ResultHandle generateGetChildObject(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config, - ResultHandle self, String childName); - - /** - * Set a child object on the given instance. - * - * @param name the child property name iterator - * @param self the instance of this configuration type - * @param containingName the child property name - * @param value the child property value - */ - abstract void setChildObject(NameIterator name, Object self, String containingName, Object value); - - abstract void generateSetChildObject(BytecodeCreator body, ResultHandle name, ResultHandle self, String containingName, - ResultHandle value); - - /** - * Get or create the instance of this root, recursively adding it to its parent if necessary. - * - * @param name the name of this property node (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @return the possibly new object instance - */ - abstract Object getOrCreate(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config); - - abstract ResultHandle generateGetOrCreate(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config); - - abstract void acceptConfigurationValueIntoLeaf(LeafConfigType leafType, NameIterator name, - final ExpandingConfigSource.Cache cache, SmallRyeConfig config); - - abstract void generateAcceptConfigurationValueIntoLeaf(BytecodeCreator body, LeafConfigType leafType, ResultHandle name, - final ResultHandle cache, ResultHandle config); -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java deleted file mode 100644 index 4fdd91a4c30ce..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java +++ /dev/null @@ -1,610 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; -import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; -import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; -import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; -import static io.quarkus.runtime.util.StringUtil.hyphenate; -import static io.quarkus.runtime.util.StringUtil.join; -import static io.quarkus.runtime.util.StringUtil.lowerCase; -import static io.quarkus.runtime.util.StringUtil.lowerCaseFirst; -import static io.quarkus.runtime.util.StringUtil.withoutSuffix; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.OptionalLong; -import java.util.Set; -import java.util.TreeMap; - -import org.eclipse.microprofile.config.spi.Converter; -import org.jboss.logging.Logger; -import org.objectweb.asm.Opcodes; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.DescriptorUtils; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; -import io.quarkus.runtime.annotations.ConvertWith; -import io.quarkus.runtime.annotations.DefaultConverter; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.HyphenateEnumConverter; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A configuration definition. This class represents the configuration space as trees of nodes, where each tree - * has a root which recursively contains all of the elements within the configuration. - */ -public class ConfigDefinition extends CompoundConfigType { - private static final Logger log = Logger.getLogger("io.quarkus.config"); - - public static final String NO_CONTAINING_NAME = "<>"; - - private static final String QUARKUS_NAMESPACE = "quarkus"; - - // for now just list the values manually - private static final List FALSE_POSITIVE_QUARKUS_CONFIG_MISSES = Arrays - .asList(QUARKUS_NAMESPACE + ".live-reload.password", QUARKUS_NAMESPACE + ".live-reload.url", - QUARKUS_NAMESPACE + ".debug.generated-classes-dir", QUARKUS_NAMESPACE + ".debug.reflection", - QUARKUS_NAMESPACE + ".build.skip", - QUARKUS_NAMESPACE + ".platform.group-id", - QUARKUS_NAMESPACE + ".platform.artifact-id", - QUARKUS_NAMESPACE + ".platform.version", - QUARKUS_NAMESPACE + ".version", QUARKUS_NAMESPACE + ".profile", QUARKUS_NAMESPACE + ".test.profile", - QUARKUS_NAMESPACE + ".test.native-image-wait-time", - QUARKUS_NAMESPACE + ".test.native-image-profile"); - - private final TreeMap rootObjectsByContainingName = new TreeMap<>(); - private final HashMap, Object> rootObjectsByClass = new HashMap<>(); - private final ConfigPatternMap leafPatterns = new ConfigPatternMap<>(); - private final IdentityHashMap realizedInstances = new IdentityHashMap<>(); - private final TreeMap rootTypesByContainingName = new TreeMap<>(); - private final FieldDescriptor rootField; - private final TreeMap loadedProperties = new TreeMap<>(); - private final boolean deferResolution; - - public ConfigDefinition(final FieldDescriptor rootField, final boolean deferResolution) { - super(null, null, false); - this.deferResolution = deferResolution; - Assert.checkNotNullParam("rootField", rootField); - this.rootField = rootField; - } - - public ConfigDefinition(final FieldDescriptor rootField) { - this(rootField, false); - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - // primitive/leaf values without a config group - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - // primitive/leaf values without a config group - throw Assert.unsupported(); - } - - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - return rootObjectsByContainingName.get(childName); - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - return body.readInstanceField(rootTypesByContainingName.get(childName).getFieldDescriptor(), self); - } - - TreeMap getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - return rootObjectsByContainingName; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - return body.readStaticField(rootField); - } - - void setChildObject(final NameIterator name, final Object self, final String childName, final Object value) { - if (self != rootObjectsByContainingName) - throw new IllegalStateException("Wrong self pointer: " + self); - final RootInfo rootInfo = rootTypesByContainingName.get(childName); - assert rootInfo != null : "Unknown child: " + childName; - assert !rootObjectsByContainingName.containsKey(childName) : "Child added twice: " + childName; - rootObjectsByContainingName.put(childName, value); - rootObjectsByClass.put(rootInfo.getRootClass(), value); - realizedInstances.put(value, new ValueInfo(childName, rootInfo)); - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - // objects should always be pre-initialized - throw Assert.unsupported(); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - throw Assert.unsupported(); - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - throw Assert.unsupported(); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - throw Assert.unsupported(); - } - - public void load() { - loadFrom(leafPatterns); - } - - public void initialize(final SmallRyeConfig config, final ExpandingConfigSource.Cache cache) { - for (Map.Entry entry : rootTypesByContainingName.entrySet()) { - final RootInfo rootInfo = entry.getValue(); - // name iterator and config are always ignored because no root types are ever stored in a map node and no conversion is ever done - // TODO: make a separate create method for root types just to avoid this kind of thing - rootInfo.getRootType().getOrCreate(new NameIterator("ignored", true), cache, config); - } - } - - public void registerConfigRoot(Class configRoot) { - final AccessorFinder accessorFinder = new AccessorFinder(); - final ConfigRoot configRootAnnotation = configRoot.getAnnotation(ConfigRoot.class); - final ConfigPhase configPhase = configRootAnnotation.phase(); - if (configRoot.isAnnotationPresent(ConfigGroup.class)) { - throw reportError(configRoot, "Roots cannot have a @ConfigGroup annotation"); - } - final String containingName; - if (configPhase == ConfigPhase.RUN_TIME) { - containingName = join( - withoutSuffix(lowerCaseFirst(camelHumpsIterator(configRoot.getSimpleName())), "Config", "Configuration", - "RunTimeConfig", "RunTimeConfiguration")); - } else { - containingName = join( - withoutSuffix(lowerCaseFirst(camelHumpsIterator(configRoot.getSimpleName())), "Config", "Configuration", - "BuildTimeConfig", "BuildTimeConfiguration")); - } - final String name = configRootAnnotation.name(); - final String rootName; - if (name.equals(ConfigItem.PARENT)) { - throw reportError(configRoot, "Root cannot inherit parent name because it has no parent"); - } else if (name.equals(ConfigItem.ELEMENT_NAME)) { - rootName = containingName; - } else if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { - rootName = join("-", - withoutSuffix(lowerCase(camelHumpsIterator(configRoot.getSimpleName())), "config", "configuration")); - } else { - rootName = name; - } - if (rootTypesByContainingName.containsKey(containingName)) - throw reportError(configRoot, "Duplicate configuration root name \"" + containingName + "\""); - final GroupConfigType configGroup = processConfigGroup(containingName, this, true, rootName, configRoot, - accessorFinder); - final RootInfo rootInfo = new RootInfo(configRoot, configGroup, FieldDescriptor - .of(DescriptorUtils.getTypeStringFromDescriptorFormat(rootField.getType()), containingName, Object.class), - configPhase); - rootTypesByContainingName.put(containingName, rootInfo); - } - - private GroupConfigType processConfigGroup(final String containingName, final CompoundConfigType container, - final boolean consumeSegment, final String baseKey, final Class configGroupClass, - final AccessorFinder accessorFinder) { - GroupConfigType gct = new GroupConfigType(containingName, container, consumeSegment, configGroupClass, accessorFinder); - final Field[] fields = configGroupClass.getDeclaredFields(); - for (Field field : fields) { - String javadocKey = field.getDeclaringClass().getName().replace("$", ".") + "." + field.getName(); - final int mods = field.getModifiers(); - if (Modifier.isStatic(mods)) { - // ignore static fields - continue; - } - if (Modifier.isFinal(mods)) { - // ignore final fields - continue; - } - final ConfigItem configItemAnnotation = field.getAnnotation(ConfigItem.class); - final String name = configItemAnnotation == null ? hyphenate(field.getName()) : configItemAnnotation.name(); - String subKey; - boolean consume; - if (name.equals(ConfigItem.PARENT)) { - subKey = baseKey; - consume = false; - } else if (name.equals(ConfigItem.ELEMENT_NAME)) { - subKey = baseKey + "." + field.getName(); - consume = true; - } else if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { - subKey = baseKey + "." + hyphenate(field.getName()); - consume = true; - } else { - subKey = baseKey + "." + name; - consume = true; - } - final String defaultValue = configItemAnnotation == null ? ConfigItem.NO_DEFAULT - : configItemAnnotation.defaultValue(); - final Type fieldType = field.getGenericType(); - final Class fieldClass = field.getType(); - if (fieldClass.isAnnotationPresent(ConfigGroup.class)) { - if (!defaultValue.equals(ConfigItem.NO_DEFAULT)) { - throw reportError(field, "Unsupported default value"); - } - gct.addField(processConfigGroup(field.getName(), gct, consume, subKey, fieldClass, accessorFinder)); - } else if (fieldClass.isPrimitive()) { - final LeafConfigType leaf; - if (fieldClass == boolean.class) { - gct.addField(leaf = new BooleanConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "false" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Boolean.class, subKey))); - } else if (fieldClass == int.class) { - gct.addField(leaf = new IntConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Integer.class, subKey))); - } else if (fieldClass == long.class) { - gct.addField(leaf = new LongConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Long.class, subKey))); - } else if (fieldClass == double.class) { - gct.addField(leaf = new DoubleConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Double.class, subKey))); - } else if (fieldClass == float.class) { - gct.addField(leaf = new FloatConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Float.class, subKey))); - } else { - throw reportError(field, "Unsupported primitive field type"); - } - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (fieldClass == Map.class) { - if (rawTypeOfParameter(fieldType, 0) != String.class) { - throw reportError(field, "Map key must be " + String.class); - } - - Type mapValueType = typeOfParameter(fieldType, 1); - Class mapValueRawType = rawTypeOf(mapValueType); - addMapField(field, gct, consume, subKey, mapValueType, accessorFinder, javadocKey, mapValueRawType); - } else if (fieldClass == List.class) { - // list leaf class - final LeafConfigType leaf; - ObjectListConfigType objectListConfigType = newObjectListConfigType(field, gct, consume, defaultValue, - javadocKey, subKey); - gct.addField(leaf = objectListConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (fieldClass == Optional.class) { - final LeafConfigType leaf; - // optional config property - OptionalObjectConfigType optionalObjectConfigType = newOptionalObjectConfigType(field, gct, consume, - defaultValue, javadocKey, subKey); - gct.addField(leaf = optionalObjectConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else { - final LeafConfigType leaf; - // it's a plain config property - ObjectConfigType objectConfigType = newObjectConfigType(field, gct, consume, defaultValue, javadocKey, - subKey); - gct.addField(leaf = objectConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } - } - return gct; - } - - private void addMapField(Field field, GroupConfigType gct, boolean consume, String subKey, Type mapValueType, - AccessorFinder accessorFinder, String javadocKey, Class mapValueRawType) { - final Class> converterClass = loadEnhancedConverter(field, mapValueRawType, subKey); - gct.addField(processMap(field.getName(), gct, field, consume, subKey, mapValueType, accessorFinder, javadocKey, - converterClass)); - } - - private ObjectConfigType newObjectConfigType(Field field, GroupConfigType gct, boolean consume, String defaultValue, - String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - Class fieldClass = (Class) field.getType(); - return new ObjectConfigType<>(field.getName(), gct, consume, - mapDefaultValue(defaultValue, fieldClass), fieldClass, javadocKey, subKey, - loadEnhancedConverter(field, fieldClass, subKey)); - } - - private OptionalObjectConfigType newOptionalObjectConfigType(Field field, GroupConfigType gct, boolean consume, - String defaultValue, String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - final Class optionalType = (Class) rawTypeOfParameter(field.getGenericType(), 0); - return new OptionalObjectConfigType<>(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "" : defaultValue, optionalType, javadocKey, subKey, - loadEnhancedConverter(field, optionalType, subKey)); - } - - private ObjectListConfigType newObjectListConfigType(Field field, GroupConfigType gct, boolean consume, - String defaultValue, String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - final Class listType = (Class) rawTypeOfParameter(field.getGenericType(), 0); - return new ObjectListConfigType<>(field.getName(), gct, consume, mapDefaultValue(defaultValue, listType), listType, - javadocKey, subKey, loadEnhancedConverter(field, listType, subKey)); - } - - private Class> loadEnhancedConverter(Field field, Class clazz, String configProperty) { - final DefaultConverter defaultConverter = field.getAnnotation(DefaultConverter.class); - final ConvertWith convertWith = field.getAnnotation(ConvertWith.class); - - if (defaultConverter != null && convertWith != null) { - throw new IllegalArgumentException(String.format( - "Duplicate conversion behaviour specified on property %s : %s annotation and %s annotation given", - configProperty, DefaultConverter.class.getName(), ConvertWith.class.getName())); - } - - if (defaultConverter != null) { - return null; // use built in MP converters or custom converters - } - - if (convertWith != null) { - @SuppressWarnings("unchecked") - final Class> converterClass = (Class>) convertWith.value(); - try { - final Method method = converterClass.getMethod("convert", String.class); - final Type type = method.getAnnotatedReturnType().getType(); - if (clazz.isAssignableFrom(rawTypeOf(type))) { - return converterClass; - } - throw new IllegalArgumentException(String.format( - "Invalid converter supplied. Cannot convert %s to %s using the given converter %s", - configProperty, clazz, converterClass)); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(e); - } - } - - if (clazz.isEnum()) { - // clean up with SmallRye Config upgrade - @SuppressWarnings({ "unchecked", "RedundantCast" }) - final Class> converterClass = (Class>) (Class) HyphenateEnumConverter.class; - return converterClass; - } - - return null; // use built in MP converters or custom converters - } - - private MapConfigType processMap(final String containingName, final CompoundConfigType container, - final AnnotatedElement containingElement, final boolean consumeSegment, final String baseKey, - final Type mapValueType, final AccessorFinder accessorFinder, String javadocKey, - Class> converterClass) { - MapConfigType mct = new MapConfigType(containingName, container, consumeSegment); - final Class valueClass = rawTypeOf(mapValueType); - final String subKey = baseKey + ".{*}"; - if (valueClass == Map.class) { - if (!(mapValueType instanceof ParameterizedType)) - throw reportError(containingElement, "Map must be parameterized"); - processMap(NO_CONTAINING_NAME, mct, containingElement, true, subKey, typeOfParameter(mapValueType, 1), - accessorFinder, javadocKey, converterClass); - } else if (valueClass.isAnnotationPresent(ConfigGroup.class)) { - processConfigGroup(NO_CONTAINING_NAME, mct, true, subKey, valueClass, accessorFinder); - } else if (valueClass == List.class) { - if (!(mapValueType instanceof ParameterizedType)) - throw reportError(containingElement, "List must be parameterized"); - @SuppressWarnings("unchecked") - Class listType = (Class) rawTypeOfParameter(mapValueType, 0); - final ObjectListConfigType leaf = new ObjectListConfigType<>(NO_CONTAINING_NAME, mct, consumeSegment, "", - listType, javadocKey, subKey, converterClass); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (valueClass == Optional.class || valueClass == OptionalInt.class || valueClass == OptionalDouble.class - || valueClass == OptionalLong.class) { - throw reportError(containingElement, "Optionals are not allowed as a map value type"); - } else { - // treat as a plain object - @SuppressWarnings("unchecked") - final ObjectConfigType leaf = new ObjectConfigType<>(NO_CONTAINING_NAME, mct, true, "", (Class) valueClass, - javadocKey, subKey, converterClass); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } - return mct; - } - - private String mapDefaultValue(String defaultValue, Class fieldClass) { - String mappedDefault = defaultValue; - if (defaultValue.equals(ConfigItem.NO_DEFAULT)) { - if (Number.class.isAssignableFrom(fieldClass)) { - mappedDefault = "0"; - } else { - mappedDefault = ""; - } - } - return mappedDefault; - } - - private static IllegalArgumentException reportError(AnnotatedElement e, String msg) { - if (e instanceof Member) { - return new IllegalArgumentException(msg + " at " + e + " of " + ((Member) e).getDeclaringClass()); - } else if (e instanceof Parameter) { - return new IllegalArgumentException(msg + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " - + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); - } else { - return new IllegalArgumentException(msg + " at " + e); - } - } - - public void generateConfigRootClass(ClassOutput classOutput, AccessorFinder accessorFinder) { - try (ClassCreator cc = ClassCreator.builder().classOutput(classOutput) - .className(DescriptorUtils.getTypeStringFromDescriptorFormat(rootField.getType())).superClass(Object.class) - .build()) { - try (MethodCreator ctor = cc.getMethodCreator("", void.class, SmallRyeConfig.class)) { - ctor.setModifiers(Opcodes.ACC_PUBLIC); - final ResultHandle self = ctor.getThis(); - final ResultHandle config = ctor.getMethodParam(0); - ctor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), self); - final ResultHandle cache = ctor.newInstance(ECS_CACHE_CTOR); - // initialize all fields to defaults - for (RootInfo value : rootTypesByContainingName.values()) { - if (value.getConfigPhase().isAvailableAtRun()) { - final CompoundConfigType rootType = value.getRootType(); - final String containingName = rootType.getContainingName(); - final FieldDescriptor fieldDescriptor = cc.getFieldCreator(containingName, Object.class) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL).getFieldDescriptor(); - ctor.writeInstanceField(fieldDescriptor, self, - rootType.writeInitialization(ctor, accessorFinder, cache, config)); - } - } - ctor.returnValue(null); - } - } - } - - public static void loadConfiguration(final ExpandingConfigSource.Cache cache, SmallRyeConfig config, - final Set unmatched, - ConfigDefinition... definitions) { - for (ConfigDefinition definition : definitions) { - definition.initialize(config, cache); - } - outer: for (String propertyName : config.getPropertyNames()) { - final NameIterator name = new NameIterator(propertyName); - if (name.hasNext()) { - if (name.nextSegmentEquals(QUARKUS_NAMESPACE)) { - name.next(); - for (ConfigDefinition definition : definitions) { - final LeafConfigType leafType = definition.leafPatterns.match(name); - if (leafType != null) { - name.goToEnd(); - final String nameString = name.toString(); - if (definition.deferResolution) { - boolean old = ExpandingConfigSource.setExpanding(false); - try { - leafType.acceptConfigurationValue(name, cache, config); - definition.loadedProperties.put(nameString, - config.getOptionalValue(nameString, String.class).orElse("")); - } finally { - ExpandingConfigSource.setExpanding(old); - } - } else { - leafType.acceptConfigurationValue(name, cache, config); - definition.loadedProperties.put(nameString, - config.getOptionalValue(nameString, String.class).orElse("")); - } - continue outer; - } - } - for (String entry : FALSE_POSITIVE_QUARKUS_CONFIG_MISSES) { - if (propertyName.equals(entry)) { - continue outer; - } - } - log.warnf("Unrecognized configuration key \"%s\" provided", propertyName); - } else { - // non-Quarkus value; capture it in the unmatched map for storage as a default value - unmatched.add(propertyName); - } - } - } - } - - public ConfigPatternMap getLeafPatterns() { - return leafPatterns; - } - - public ConfigDefinition getConfigDefinition() { - return this; - } - - public TreeMap getLoadedProperties() { - return loadedProperties; - } - - private void loadFrom(ConfigPatternMap map) { - final LeafConfigType matched = map.getMatched(); - if (matched != null) { - matched.load(); - } - for (String name : map.childNames()) { - loadFrom(map.getChild(name)); - } - } - - public Object getRealizedInstance(final Class rootClass) { - final Object obj = rootObjectsByClass.get(rootClass); - if (obj == null) { - throw new IllegalArgumentException("Unknown root class: " + rootClass); - } - return obj; - } - - public RootInfo getInstanceInfo(final Object obj) { - final ValueInfo valueInfo = realizedInstances.get(obj); - if (valueInfo == null) - return null; - return valueInfo.getRootInfo(); - } - - public static final class RootInfo { - private final Class rootClass; - private final GroupConfigType rootType; - private final FieldDescriptor fieldDescriptor; - private final ConfigPhase configPhase; - - RootInfo(final Class rootClass, final GroupConfigType rootType, final FieldDescriptor fieldDescriptor, - final ConfigPhase configPhase) { - this.rootClass = rootClass; - this.rootType = rootType; - this.fieldDescriptor = fieldDescriptor; - this.configPhase = configPhase; - } - - public Class getRootClass() { - return rootClass; - } - - public GroupConfigType getRootType() { - return rootType; - } - - public FieldDescriptor getFieldDescriptor() { - return fieldDescriptor; - } - - public ConfigPhase getConfigPhase() { - return configPhase; - } - } - - static final class ValueInfo { - private final String key; - private final RootInfo rootInfo; - - ValueInfo(final String key, final RootInfo rootInfo) { - this.key = key; - this.rootInfo = rootInfo; - } - - String getKey() { - return key; - } - - RootInfo getRootInfo() { - return rootInfo; - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java deleted file mode 100644 index e5f744b32c035..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public abstract class ConfigType { - static final MethodDescriptor NI_PREV_METHOD = MethodDescriptor.ofMethod(NameIterator.class, "previous", void.class); - - static final MethodDescriptor NI_NEXT_METHOD = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); - - static final MethodDescriptor NI_GET_NEXT_SEGMENT = MethodDescriptor.ofMethod(NameIterator.class, "getNextSegment", - String.class); - - static final MethodDescriptor OBJ_TO_STRING_METHOD = MethodDescriptor.ofMethod(Object.class, "toString", String.class); - - static final MethodDescriptor OPT_OR_ELSE_METHOD = MethodDescriptor.ofMethod(Optional.class, "orElse", Object.class, - Object.class); - - static final MethodDescriptor OPT_OF_NULLABLE_METHOD = MethodDescriptor.ofMethod(Optional.class, "ofNullable", - Optional.class, Object.class); - - static final MethodDescriptor OPT_EMPTY_METHOD = MethodDescriptor.ofMethod(Optional.class, "empty", Optional.class); - - static final MethodDescriptor MAP_PUT_METHOD = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, - Object.class); - - static final MethodDescriptor ECS_CACHE_CTOR = MethodDescriptor.ofConstructor(ExpandingConfigSource.Cache.class); - - /** - * Containing name. This is a field name or a map key, not a configuration key segment; as such, it is - * never {@code null} unless the containing name is intentionally dynamic. - */ - private final String containingName; - /** - * The containing node, or {@code null} if the node is a root. - */ - private final CompoundConfigType container; - /** - * Consume a segment of the name when traversing this node. Always {@code true} if the containing name is dynamic, - * otherwise only {@code true} if the node is a configuration group node with an empty relative name. - */ - private final boolean consumeSegment; - - ConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - this.containingName = containingName; - this.container = container; - this.consumeSegment = consumeSegment; - } - - static IllegalAccessError toError(final IllegalAccessException e) { - IllegalAccessError e2 = new IllegalAccessError(e.getMessage()); - e2.setStackTrace(e.getStackTrace()); - return e2; - } - - static InstantiationError toError(final InstantiationException e) { - InstantiationError e2 = new InstantiationError(e.getMessage()); - e2.setStackTrace(e.getStackTrace()); - return e2; - } - - public String getContainingName() { - return containingName; - } - - public CompoundConfigType getContainer() { - return container; - } - - public T getContainer(Class expect) { - final CompoundConfigType container = getContainer(); - if (expect.isInstance(container)) - return expect.cast(container); - throw new IllegalStateException( - "Container is not a supported type; expected " + expect + " but got " + container.getClass()); - } - - public boolean isConsumeSegment() { - return consumeSegment; - } - - /** - * Load all configuration classes to enable configuration to be instantiated. - * - * @throws ClassNotFoundException if a required class was not found - */ - public abstract void load() throws ClassNotFoundException; - - /** - * A reusable method which returns an exception that can be thrown when a configuration - * node is used without its class being loaded. - * - * @return the not-loaded exception - */ - protected static IllegalStateException notLoadedException() { - return new IllegalStateException("Configuration tree classes not loaded"); - } - - /** - * Get the default value of this type into the enclosing element. - * - * @param enclosing the instance of the enclosing type (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @param field the field to read the value into - */ - abstract void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field); - - abstract void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config); - - public abstract ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig); - - public ConfigDefinition getConfigDefinition() { - return container.getConfigDefinition(); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java index a4b851c0d2b00..289f7d4d74a70 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java @@ -5,13 +5,17 @@ import org.eclipse.microprofile.config.spi.ConfigSource; +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; + /** * */ public class DefaultValuesConfigurationSource implements ConfigSource { - private final ConfigPatternMap leafs; + private final ConfigPatternMap leafs; - public DefaultValuesConfigurationSource(final ConfigPatternMap leafs) { + public DefaultValuesConfigurationSource(final ConfigPatternMap leafs) { this.leafs = leafs; } @@ -20,15 +24,19 @@ public Map getProperties() { } public String getValue(final String propertyName) { - final LeafConfigType match = leafs.match(propertyName); - if (match == null) { + if (!propertyName.startsWith("quarkus.")) { return null; } - final String defaultValueString = match.getDefaultValueString(); - if (defaultValueString == null || defaultValueString.isEmpty()) { + final Container match = leafs.match(propertyName.substring(8)); + if (match == null) { return null; } - return defaultValueString; + final ClassDefinition.ClassMember member = match.getClassMember(); + if (member instanceof ClassDefinition.ItemMember) { + final ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + return leafMember.getDefaultValue(); + } + return null; } public String getName() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java deleted file mode 100644 index 709ed9210176e..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class DoubleConfigType extends LeafConfigType { - private static final MethodDescriptor DOUBLE_VALUE_METHOD = MethodDescriptor.ofMethod(Double.class, "doubleValue", - double.class); - - final String defaultValue; - private final Class> converterClass; - - public DoubleConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Double value = ConfigUtils.getValue(config, name.toString(), Double.class, converterClass); - field.setDouble(enclosing, value != null ? value.doubleValue() : 0d); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Double doubleValue = ConfigUtils.getValue(config, name.toString(), Double.class, converterClass); - // final double d = doubleValue != null ? doubleValue.doubleValue() : 0d; - final AssignableResultHandle result = body.createVariable(double.class); - final ResultHandle doubleValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Double.class), loadConverterClass(body)), Double.class); - final BranchResult ifNull = body.ifNull(doubleValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0d)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - DOUBLE_VALUE_METHOD, - doubleValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return double.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Double value = ConfigUtils.convert(config, - ExpandingConfigSource.expandValue(defaultValue, cache), Double.class, converterClass); - field.setDouble(enclosing, value != null ? value.doubleValue() : 0d); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Double.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java deleted file mode 100644 index 70bf1c039ffa2..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class FloatConfigType extends LeafConfigType { - - private static final MethodDescriptor FLOAT_VALUE_METHOD = MethodDescriptor.ofMethod(Float.class, "floatValue", - float.class); - - final String defaultValue; - private final Class> converterClass; - - public FloatConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - final Float value = ConfigUtils.getValue(config, name.toString(), Float.class, converterClass); - field.setFloat(enclosing, value != null ? value.floatValue() : 0f); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Float floatValue = ConfigUtils.getValue(config, name.toString(), Float.class, converterClass); - // final float f = floatValue != null ? floatValue.floatValue() : 0f; - final AssignableResultHandle result = body.createVariable(float.class); - final ResultHandle floatValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Float.class), loadConverterClass(body)), Float.class); - final BranchResult ifNull = body.ifNull(floatValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0f)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - FLOAT_VALUE_METHOD, - floatValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return float.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - final Float value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Float.class, - converterClass); - field.setFloat(enclosing, value != null ? value.floatValue() : 0f); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Float.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java deleted file mode 100644 index a26c35c3323c5..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java +++ /dev/null @@ -1,300 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeSet; - -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A configuration definition node describing a configuration group. - */ -public class GroupConfigType extends CompoundConfigType { - - private final Map fields; - private final Class class_; - private final Constructor constructor; - private final MethodDescriptor constructorAccessor; - private final Map fieldInfos; - - public GroupConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final Class class_, final AccessorFinder accessorFinder) { - super(containingName, container, consumeSegment); - Assert.checkNotNullParam("containingName", containingName); - Assert.checkNotNullParam("container", container); - Assert.checkNotNullParam("class_", class_); - Assert.checkNotNullParam("accessorFinder", accessorFinder); - fields = new HashMap<>(); - this.class_ = class_; - try { - constructor = class_.getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Constructor of " + class_ + " is missing"); - } - if ((constructor.getModifiers() & Modifier.PRIVATE) != 0) { - throw new IllegalArgumentException("Constructor of " + class_ + " must not be private"); - } else if ((constructor.getModifiers() & Modifier.PUBLIC) == 0) { - constructor.setAccessible(true); - } - constructorAccessor = accessorFinder.getConstructorFor(MethodDescriptor.ofConstructor(class_)); - fieldInfos = new HashMap<>(); - for (Field field : class_.getDeclaredFields()) { - int modifiers = field.getModifiers(); - if ((modifiers & Modifier.STATIC) == 0) { - // consider this one - if ((modifiers & Modifier.PRIVATE) != 0) { - throw new IllegalArgumentException( - "Field \"" + field.getName() + "\" of " + class_ + " must not be private"); - } - field.setAccessible(true); - final FieldDescriptor descr = FieldDescriptor.of(field); - fieldInfos.put(field.getName(), - new FieldInfo(field, accessorFinder.getSetterFor(descr), accessorFinder.getGetterFor(descr))); - } - } - } - - public void load() throws ClassNotFoundException { - assert class_ != null && constructor != null; - if (!fieldInfos.keySet().containsAll(fields.keySet())) { - final TreeSet missing = new TreeSet<>(fields.keySet()); - missing.removeAll(fieldInfos.keySet()); - throw new IllegalArgumentException("Fields missing from " + class_ + ": " + missing); - } - if (!fields.keySet().containsAll(fieldInfos.keySet())) { - final TreeSet extra = new TreeSet<>(fieldInfos.keySet()); - extra.removeAll(fields.keySet()); - throw new IllegalArgumentException("Extra unknown fields on " + class_ + ": " + extra); - } - for (ConfigType node : fields.values()) { - node.load(); - } - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - final ResultHandle instance = body - .invokeStaticMethod(accessorFinder.getConstructorFor(MethodDescriptor.ofConstructor(class_))); - for (Map.Entry entry : fields.entrySet()) { - final String fieldName = entry.getKey(); - final ConfigType fieldType = entry.getValue(); - final FieldDescriptor fieldDescriptor = FieldDescriptor.of(fieldInfos.get(fieldName).getField()); - final ResultHandle value = fieldType.writeInitialization(body, accessorFinder, cache, smallRyeConfig); - body.invokeStaticMethod(accessorFinder.getSetterFor(fieldDescriptor), instance, value); - } - return instance; - } - - public ConfigType getField(String name) { - return fields.get(name); - } - - public void addField(ConfigType node) { - final String containingName = node.getContainingName(); - final ConfigType existing = fields.putIfAbsent(containingName, node); - if (existing != null) { - throw new IllegalArgumentException("Cannot add duplicate field \"" + containingName + "\" to " + this); - } - } - - private Field findField(final String name) { - if (class_ == null) - throw notLoadedException(); - final FieldInfo fieldInfo = fieldInfos.get(name); - if (fieldInfo == null) - throw new IllegalStateException("Missing field " + name + " on " + class_); - return fieldInfo.getField(); - } - - private Object create(final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - Object self; - try { - self = constructor.newInstance(); - } catch (InstantiationException e) { - throw toError(e); - } catch (IllegalAccessException e) { - throw toError(e); - } catch (InvocationTargetException e) { - try { - throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; - } catch (Throwable t) { - throw new UndeclaredThrowableException(t); - } - } - for (Map.Entry entry : fields.entrySet()) { - entry.getValue().getDefaultValueIntoEnclosingGroup(self, cache, config, findField(entry.getKey())); - } - return self; - } - - private ResultHandle generateCreate(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - final ResultHandle self = body.invokeStaticMethod(constructorAccessor); - for (Map.Entry entry : fields.entrySet()) { - final ConfigType childType = entry.getValue(); - final MethodDescriptor setter = fieldInfos.get(entry.getKey()).getSetter(); - childType.generateGetDefaultValueIntoEnclosingGroup(body, self, setter, cache, config); - } - return self; - } - - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - final Field field = findField(childName); - Object val = getFromField(field, self); - if (val == null) { - final ConfigType childType = getField(childName); - childType.getDefaultValueIntoEnclosingGroup(self, cache, config, field); - val = getFromField(field, self); - } - return val; - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - final AssignableResultHandle val = body.createVariable(Object.class); - final FieldInfo fieldInfo = fieldInfos.get(childName); - body.assign(val, body.invokeStaticMethod(fieldInfo.getGetter(), self)); - try (BytecodeCreator isNull = body.ifNull(val).trueBranch()) { - final ConfigType childType = getField(childName); - childType.generateGetDefaultValueIntoEnclosingGroup(isNull, self, fieldInfo.getSetter(), cache, config); - isNull.assign(val, isNull.invokeStaticMethod(fieldInfo.getGetter(), self)); - } - return val; - } - - private static Object getFromField(Field field, Object obj) { - try { - return field.get(obj); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - Object getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - final Object enclosing = container.getOrCreate(name, cache, config); - Object self = container.getChildObject(name, cache, config, enclosing, getContainingName()); - if (isConsumeSegment()) - name.next(); - if (self == null) { - // it's a map, and it doesn't contain our key. - self = create(cache, config); - if (isConsumeSegment()) - name.previous(); - container.setChildObject(name, enclosing, getContainingName(), self); - if (isConsumeSegment()) - name.next(); - } - return self; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - final ResultHandle enclosing = container.generateGetOrCreate(body, name, cache, config); - final AssignableResultHandle var = body.createVariable(Object.class); - body.assign(var, container.generateGetChildObject(body, name, cache, config, enclosing, getContainingName())); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_NEXT_METHOD, name); - if (container.getClass() == MapConfigType.class) { - // it could be null - try (BytecodeCreator createBranch = body.ifNull(var).trueBranch()) { - createBranch.assign(var, generateCreate(createBranch, cache, config)); - if (isConsumeSegment()) - createBranch.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateSetChildObject(createBranch, name, enclosing, getContainingName(), var); - if (isConsumeSegment()) - createBranch.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - } - return var; - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - final FieldInfo fieldInfo = fieldInfos.get(leafType.getContainingName()); - leafType.acceptConfigurationValueIntoGroup(getOrCreate(name, cache, config), fieldInfo.getField(), name, config); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - final FieldInfo fieldInfo = fieldInfos.get(leafType.getContainingName()); - leafType.generateAcceptConfigurationValueIntoGroup(body, generateGetOrCreate(body, name, cache, config), - fieldInfo.getSetter(), - name, config); - } - - void setChildObject(final NameIterator name, final Object self, final String containingName, final Object value) { - try { - findField(containingName).set(self, value); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - body.invokeStaticMethod(fieldInfos.get(containingName).getSetter(), self, value); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - field.set(enclosing, create(cache, config)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle self = generateCreate(body, cache, config); - body.invokeStaticMethod(setter, enclosing, self); - } - - static final class FieldInfo { - private final Field field; - private final MethodDescriptor setter; - private final MethodDescriptor getter; - - public FieldInfo(final Field field, final MethodDescriptor setter, final MethodDescriptor getter) { - this.field = field; - this.setter = setter; - this.getter = getter; - } - - public Field getField() { - return field; - } - - public MethodDescriptor getSetter() { - return setter; - } - - public MethodDescriptor getGetter() { - return getter; - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java deleted file mode 100644 index f4e85da179f40..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class IntConfigType extends LeafConfigType { - private static final MethodDescriptor INT_VALUE_METHOD = MethodDescriptor.ofMethod(Integer.class, "intValue", int.class); - - final String defaultValue; - private final Class> converterClass; - - public IntConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Integer value = ConfigUtils.getValue(config, name.toString(), Integer.class, converterClass); - field.setInt(enclosing, value != null ? value.intValue() : 0); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Integer integerValue = ConfigUtils.getValue(config, name.toString(), Integer.class, converterClass); - // final int i = integerValue != null ? integerValue.intValue() : 0; - final AssignableResultHandle result = body.createVariable(int.class); - final ResultHandle integerValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Integer.class), loadConverterClass(body)), Integer.class); - final BranchResult ifNull = body.ifNull(integerValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - INT_VALUE_METHOD, - integerValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return int.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Integer value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), - Integer.class, converterClass); - field.setInt(enclosing, value != null ? value.intValue() : 0); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Integer.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java deleted file mode 100644 index 4ff48cdcc5ac6..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; -import org.wildfly.common.annotation.NotNull; - -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A node which contains a regular value. Leaf nodes can never be directly acquired. - */ -public abstract class LeafConfigType extends ConfigType { - static final MethodDescriptor CU_CONVERT = MethodDescriptor.ofMethod(ConfigUtils.class, "convert", Object.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - static final MethodDescriptor CU_GET_VALUE = MethodDescriptor.ofMethod(ConfigUtils.class, "getValue", Object.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - static final MethodDescriptor CU_GET_OPT_VALUE = MethodDescriptor.ofMethod(ConfigUtils.class, "getOptionalValue", - Optional.class, SmallRyeConfig.class, String.class, Class.class, Class.class); - - private final String javadocKey; - private final String configKey; - - LeafConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - String javadocKey, String configKey) { - super(containingName, container, consumeSegment); - this.javadocKey = javadocKey; - this.configKey = configKey; - } - - /** - * - * @return the key that the javadoc was saved under - */ - public String getJavadocKey() { - return javadocKey; - } - - public String getConfigKey() { - return configKey; - } - - public void load() { - } - - /** - * Get the class of the individual item. This is the unwrapped type of {@code Optional}, {@code Collection}, etc. - * - * @return the item class (must not be {@code null}) - */ - public abstract Class getItemClass(); - - /** - * Handle a configuration key from the input file. - * - * @param name the configuration property name - * @param cache - * @param config the source configuration - */ - public abstract void acceptConfigurationValue(@NotNull NameIterator name, final ExpandingConfigSource.Cache cache, - @NotNull SmallRyeConfig config); - - public abstract void generateAcceptConfigurationValue(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config); - - abstract void acceptConfigurationValueIntoGroup(Object enclosing, Field field, NameIterator name, SmallRyeConfig config); - - abstract void generateAcceptConfigurationValueIntoGroup(BytecodeCreator body, ResultHandle enclosing, - final MethodDescriptor setter, ResultHandle name, ResultHandle config); - - void acceptConfigurationValueIntoMap(Map enclosing, NameIterator name, SmallRyeConfig config) { - // only non-primitives are supported - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoMap(BytecodeCreator body, ResultHandle enclosing, - ResultHandle name, ResultHandle config) { - throw Assert.unsupported(); - } - - public abstract String getDefaultValueString(); - - public abstract Class> getConverterClass(); - - protected final ResultHandle loadConverterClass(BytecodeCreator body) { - Class> converterClass = getConverterClass(); - ResultHandle converter = body.loadNull(); - if (converterClass != null) { - converter = body.loadClass(converterClass); - } - return converter; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java deleted file mode 100644 index 1ca42e42a4c75..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class LongConfigType extends LeafConfigType { - private static final MethodDescriptor LONG_VALUE_METHOD = MethodDescriptor.ofMethod(Long.class, "longValue", long.class); - - final String defaultValue; - private final Class> converterClass; - - public LongConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Long value = ConfigUtils.getValue(config, name.toString(), Long.class, converterClass); - field.setLong(enclosing, value != null ? value.longValue() : 0L); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Long longValue = ConfigUtils.getValue(config, name.toString(), Long.class, converterClass); - // final long l = longValue != null ? longValue.longValue() : 0l; - final AssignableResultHandle result = body.createVariable(long.class); - final ResultHandle longValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Long.class), loadConverterClass(body)), Long.class); - final BranchResult ifNull = body.ifNull(longValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0L)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - LONG_VALUE_METHOD, - longValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return long.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Long value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Long.class, - converterClass); - field.setLong(enclosing, value != null ? value.longValue() : 0L); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Long.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java deleted file mode 100644 index a2ce0e360c52a..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.TreeMap; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class MapConfigType extends CompoundConfigType { - - private static final MethodDescriptor TREE_MAP_CTOR = MethodDescriptor.ofConstructor(TreeMap.class); - private static final MethodDescriptor MAP_GET_METHOD = MethodDescriptor.ofMethod(Map.class, "get", Object.class, - Object.class); - private static final MethodDescriptor MAP_PUT_METHOD = MethodDescriptor.ofMethod(Map.class, "put", Object.class, - Object.class, Object.class); - - public MapConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - super(containingName, container, consumeSegment); - } - - public void load() { - } - - @SuppressWarnings("unchecked") - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - return ((TreeMap) self).get(name.getNextSegment()); - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - return body.invokeInterfaceMethod(MAP_GET_METHOD, body.checkCast(self, Map.class), - body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name)); - } - - @SuppressWarnings("unchecked") - void setChildObject(final NameIterator name, final Object self, final String childName, final Object value) { - ((TreeMap) self).put(name.getNextSegment(), value); - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, body.checkCast(self, Map.class), - body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), value); - } - - TreeMap getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - TreeMap self; - if (container != null) { - if (isConsumeSegment()) - name.previous(); - final Object enclosing = container.getOrCreate(name, cache, config); - self = (TreeMap) container.getChildObject(name, cache, config, enclosing, getContainingName()); - if (self == null) { - self = new TreeMap<>(); - container.setChildObject(name, enclosing, getContainingName(), self); - } - if (isConsumeSegment()) - name.next(); - } else { - self = new TreeMap<>(); - } - return self; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (container != null) { - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - final ResultHandle enclosing = container.generateGetOrCreate(body, name, cache, config); - final AssignableResultHandle self = body.createVariable(TreeMap.class); - body.assign(self, body.checkCast( - container.generateGetChildObject(body, name, cache, config, enclosing, getContainingName()), Map.class)); - try (BytecodeCreator selfIsNull = body.ifNull(self).trueBranch()) { - selfIsNull.assign(self, selfIsNull.newInstance(TREE_MAP_CTOR)); - container.generateSetChildObject(selfIsNull, name, enclosing, getContainingName(), self); - } - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_NEXT_METHOD, name); - return self; - } else { - return body.newInstance(TREE_MAP_CTOR); - } - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - leafType.acceptConfigurationValueIntoMap(getOrCreate(name, cache, config), name, config); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - leafType.generateAcceptConfigurationValueIntoMap(body, generateGetOrCreate(body, name, cache, config), name, config); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.newInstance(TREE_MAP_CTOR); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - field.set(enclosing, new TreeMap<>()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, body.newInstance(TREE_MAP_CTOR)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java deleted file mode 100644 index 90a3c95de3146..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.Map; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class ObjectConfigType extends LeafConfigType { - final String defaultValue; - final Class expectedType; - Class> converterClass; - - public ObjectConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, final Class expectedType, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - this.defaultValue = defaultValue; - this.expectedType = expectedType; - this.converterClass = converterClass; - } - - @Override - public Class getItemClass() { - return expectedType; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - String value = ExpandingConfigSource.expandValue(defaultValue, cache); - field.set(enclosing, ConfigUtils.convert(config, value, expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - ResultHandle resultHandle = getResultHandle(body, cache, config); - body.invokeStaticMethod(setter, enclosing, resultHandle); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - ResultHandle resultHandle = getResultHandle(body, cache, smallRyeConfig); - return body.checkCast(resultHandle, expectedType); - } - - private ResultHandle getResultHandle(BytecodeCreator body, ResultHandle cache, ResultHandle smallRyeConfig) { - ResultHandle clazz = body.loadClass(expectedType); - ResultHandle cacheResultHandle = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - - return body.invokeStaticMethod(CU_CONVERT, smallRyeConfig, cacheResultHandle, clazz, loadConverterClass(body)); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - if (isConsumeSegment()) - name.previous(); - getContainer().acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - getContainer().generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, - ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass) - .orElse(null)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, generateGetValue(body, name, config)); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - enclosing.put(name.getNextSegment(), - ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass).orElse(null)); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, enclosing, body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), - generateGetValue(body, name, config)); - } - - public String getDefaultValueString() { - return defaultValue; - } - - private ResultHandle generateGetValue(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { - final ResultHandle optionalValue = body.invokeStaticMethod( - CU_GET_OPT_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(expectedType), loadConverterClass(body)); - return body.invokeVirtualMethod(OPT_OR_ELSE_METHOD, optionalValue, body.loadNull()); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java deleted file mode 100644 index d956fe8ece863..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.IntFunction; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ArrayListFactory; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class ObjectListConfigType extends ObjectConfigType { - static final MethodDescriptor ALF_GET_INST_METHOD = MethodDescriptor.ofMethod(ArrayListFactory.class, "getInstance", - ArrayListFactory.class); - static final MethodDescriptor EMPTY_LIST_METHOD = MethodDescriptor.ofMethod(Collections.class, "emptyList", List.class); - static final MethodDescriptor CU_GET_DEFAULTS_METHOD = MethodDescriptor.ofMethod(ConfigUtils.class, "getDefaults", - Collection.class, SmallRyeConfig.class, String.class, Class.class, Class.class, IntFunction.class); - - static final MethodDescriptor GET_VALUES = MethodDescriptor.ofMethod(ConfigUtils.class, "getValues", ArrayList.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - - public ObjectListConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, final Class expectedType, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, defaultValue, expectedType, javadocKey, configKey, converterClass); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - if (defaultValue.isEmpty()) { - field.set(enclosing, Collections.emptyList()); - } else { - final ArrayList defaults = ConfigUtils.getDefaults( - config, - ExpandingConfigSource.expandValue(defaultValue, cache), - expectedType, - converterClass, - ArrayListFactory.getInstance()); - field.set(enclosing, defaults); - } - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle value; - if (defaultValue.isEmpty()) { - value = body.invokeStaticMethod(EMPTY_LIST_METHOD); - } else { - ResultHandle cacheValue = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - value = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, cacheValue, body.loadClass(expectedType), - loadConverterClass(body), - body.invokeStaticMethod(ALF_GET_INST_METHOD)); - } - body.invokeStaticMethod(setter, enclosing, value); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, ConfigUtils.getValues(config, name.toString(), expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, generateGetValues(body, name, config)); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - enclosing.put(name.getNextSegment(), - ConfigUtils.getValues(config, name.toString(), expectedType, converterClass)); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, enclosing, body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), - generateGetValues(body, name, config)); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle config) { - ResultHandle arrayListFactory = body.invokeStaticMethod(ALF_GET_INST_METHOD); - final ResultHandle resultHandle = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, body.load(defaultValue), - body.loadClass(expectedType), loadConverterClass(body), arrayListFactory); - return body.checkCast(resultHandle, List.class); - } - - private ResultHandle generateGetValues(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { - ResultHandle propertyName = body.invokeVirtualMethod(OBJ_TO_STRING_METHOD, name); - return body.invokeStaticMethod(GET_VALUES, config, propertyName, body.loadClass(expectedType), - loadConverterClass(body)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java deleted file mode 100644 index 385269fe68a28..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java +++ /dev/null @@ -1,120 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class OptionalObjectConfigType extends ObjectConfigType { - - public OptionalObjectConfigType(final String containingName, final CompoundConfigType container, - final boolean consumeSegment, final String defaultValue, final Class expectedType, String javadocKey, - String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, defaultValue, expectedType, javadocKey, configKey, converterClass); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - if (defaultValue.isEmpty()) { - field.set(enclosing, Optional.empty()); - } else { - String value = ExpandingConfigSource.expandValue(defaultValue, cache); - field.set(enclosing, - Optional.ofNullable(ConfigUtils.convert(config, value, expectedType, converterClass))); - } - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle optValue; - if (defaultValue.isEmpty()) { - optValue = body.invokeStaticMethod(OPT_EMPTY_METHOD); - } else { - optValue = body.invokeStaticMethod(OPT_OF_NULLABLE_METHOD, body.invokeStaticMethod(CU_CONVERT, config, - body.load(defaultValue), body.loadClass(expectedType), loadConverterClass(body))); - } - body.invokeStaticMethod(setter, enclosing, optValue); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - ResultHandle propertyName = body.invokeVirtualMethod(OBJ_TO_STRING_METHOD, name); - final ResultHandle optionalValue = body.invokeStaticMethod(CU_GET_OPT_VALUE, config, propertyName, - body.loadClass(expectedType), loadConverterClass(body)); - body.invokeStaticMethod(setter, enclosing, optionalValue); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - throw Assert.unsupported(); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle config) { - if (defaultValue.isEmpty()) { - return body.invokeStaticMethod(OPT_EMPTY_METHOD); - } else { - ResultHandle classResultHandle = body.loadClass(expectedType); - ResultHandle cacheResultHandle = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - ResultHandle resultHandle = body.invokeStaticMethod(CU_CONVERT, config, cacheResultHandle, classResultHandle, - loadConverterClass(body)); - return body.invokeStaticMethod(OPT_OF_NULLABLE_METHOD, resultHandle); - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java index 4b20a9f154444..c8eb215cdb1f6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java @@ -4,20 +4,20 @@ public class PropertiesUtil { private PropertiesUtil() { } - public static boolean escape(int codePoint) { + public static boolean needsEscape(int codePoint) { return codePoint == '#' || codePoint == '!' || codePoint == '=' || codePoint == ':'; } - public static boolean escapeForKey(int codePoint) { - return Character.isSpaceChar(codePoint) || escape(codePoint); + public static boolean needsEscapeForKey(int codePoint) { + return Character.isSpaceChar(codePoint) || needsEscape(codePoint); } - public static boolean escapeForValueFirst(int codePoint) { - return escapeForKey(codePoint); + public static boolean needsEscapeForValueFirst(int codePoint) { + return needsEscapeForKey(codePoint); } - public static boolean escapeForValueSubsequent(int codePoint) { - return escape(codePoint); + public static boolean needsEscapeForValueSubsequent(int codePoint) { + return needsEscape(codePoint); } public static String quotePropertyName(String name) { @@ -25,7 +25,7 @@ public static String quotePropertyName(String name) { int cp; for (int i = 0; i < length; i = name.offsetByCodePoints(i, 1)) { cp = name.codePointAt(i); - if (escapeForKey(cp)) { + if (needsEscapeForKey(cp)) { final StringBuilder b = new StringBuilder(length + (length >> 2)); // get leading section b.append(name, 0, i); @@ -33,7 +33,7 @@ public static String quotePropertyName(String name) { b.append('\\').appendCodePoint(cp); for (i = name.offsetByCodePoints(i, 1); i < length; i = name.offsetByCodePoints(i, 1)) { cp = name.codePointAt(i); - if (escapeForKey(cp)) { + if (needsEscapeForKey(cp)) { b.append('\\'); } b.appendCodePoint(cp); @@ -50,7 +50,7 @@ public static String quotePropertyValue(String value) { int cp; for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { cp = value.codePointAt(i); - if (i == 0 ? escapeForValueFirst(cp) : escapeForValueSubsequent(cp)) { + if (i == 0 ? needsEscapeForValueFirst(cp) : needsEscapeForValueSubsequent(cp)) { final StringBuilder b = new StringBuilder(length + (length >> 2)); // get leading section b.append(value, 0, i); @@ -58,7 +58,7 @@ public static String quotePropertyValue(String value) { b.append('\\').appendCodePoint(cp); for (i = value.offsetByCodePoints(i, 1); i < length; i = value.offsetByCodePoints(i, 1)) { cp = value.codePointAt(i); - if (escapeForValueSubsequent(cp)) { + if (needsEscapeForValueSubsequent(cp)) { b.append('\\'); } b.appendCodePoint(cp); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java new file mode 100644 index 0000000000000..d34f5ade45216 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -0,0 +1,1270 @@ +package io.quarkus.deployment.configuration; + +import static io.quarkus.deployment.util.ReflectUtil.reportError; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.function.BiFunction; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.Converter; +import org.objectweb.asm.Opcodes; +import org.wildfly.common.Assert; + +import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.GroupDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.deployment.configuration.matching.FieldContainer; +import io.quarkus.deployment.configuration.matching.MapContainer; +import io.quarkus.deployment.configuration.type.ArrayOf; +import io.quarkus.deployment.configuration.type.CollectionOf; +import io.quarkus.deployment.configuration.type.ConverterType; +import io.quarkus.deployment.configuration.type.Leaf; +import io.quarkus.deployment.configuration.type.LowerBoundCheckOf; +import io.quarkus.deployment.configuration.type.MinMaxValidated; +import io.quarkus.deployment.configuration.type.OptionalOf; +import io.quarkus.deployment.configuration.type.PatternValidated; +import io.quarkus.deployment.configuration.type.UpperBoundCheckOf; +import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.configuration.AbstractRawDefaultConfigSource; +import io.quarkus.runtime.configuration.ConfigDiagnostic; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; +import io.quarkus.runtime.configuration.NameIterator; +import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.Converters; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * + */ +public final class RunTimeConfigurationGenerator { + + public static final String CONFIG_CLASS_NAME = "io.quarkus.runtime.generated.Config"; + static final String RTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.RunTimeDefaultValuesConfigSource"; + static final String BTRTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BuildTimeRunTimeDefaultValuesConfigSource"; + + // member descriptors + + static final MethodDescriptor BTRTDVCS_NEW = MethodDescriptor.ofConstructor(BTRTDVCS_CLASS_NAME); + + static final FieldDescriptor C_BUILD_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "buildTimeConfigSource", + ConfigSource.class); + static final FieldDescriptor C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "buildTimeRunTimeDefaultsConfigSource", ConfigSource.class); + public static final MethodDescriptor C_CREATE_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + "createRunTimeConfig", void.class); + public static final MethodDescriptor C_ENSURE_INITIALIZED = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + "ensureInitialized", void.class); + static final FieldDescriptor C_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "runTimeDefaultsConfigSource", ConfigSource.class); + static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class); + static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "specifiedRunTimeConfigSource", + ConfigSource.class); + + static final MethodDescriptor CD_INVALID_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "invalidValue", + void.class, String.class, IllegalArgumentException.class); + static final MethodDescriptor CD_IS_ERROR = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "isError", + boolean.class); + static final MethodDescriptor CD_MISSING_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "missingValue", + void.class, String.class, NoSuchElementException.class); + static final MethodDescriptor CD_RESET_ERROR = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "resetError", void.class); + static final MethodDescriptor CD_UNKNOWN = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "unknown", + void.class, NameIterator.class); + static final MethodDescriptor CD_UNKNOWN_RT = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "unknownRunTime", + void.class, NameIterator.class); + + static final MethodDescriptor CONVS_NEW_ARRAY_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newArrayConverter", Converter.class, Converter.class, Class.class); + static final MethodDescriptor CONVS_NEW_COLLECTION_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newCollectionConverter", Converter.class, Converter.class, IntFunction.class); + static final MethodDescriptor CONVS_NEW_OPTIONAL_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newOptionalConverter", Converter.class, Converter.class); + static final MethodDescriptor CONVS_RANGE_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "rangeValueStringConverter", Converter.class, Converter.class, String.class, boolean.class, String.class, + boolean.class); + static final MethodDescriptor CONVS_MINIMUM_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "minimumValueStringConverter", Converter.class, Converter.class, String.class, boolean.class); + static final MethodDescriptor CONVS_MAXIMUM_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "maximumValueStringConverter", Converter.class, Converter.class, String.class, boolean.class); + static final MethodDescriptor CONVS_PATTERN_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "patternConverter", Converter.class, Converter.class, Pattern.class); + + static final MethodDescriptor CPR_GET_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "getConfig", + Config.class); + static final MethodDescriptor CPR_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "instance", + ConfigProviderResolver.class); + static final MethodDescriptor CPR_RELEASE_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "releaseConfig", + void.class, Config.class); + + static final MethodDescriptor CU_LIST_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "listFactory", + IntFunction.class); + static final MethodDescriptor CU_SET_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "setFactory", + IntFunction.class); + static final MethodDescriptor CU_SORTED_SET_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "sortedSetFactory", + IntFunction.class); + static final MethodDescriptor CU_CONFIG_BUILDER = MethodDescriptor.ofMethod(ConfigUtils.class, "configBuilder", + SmallRyeConfigBuilder.class, boolean.class); + + static final MethodDescriptor HM_NEW = MethodDescriptor.ofConstructor(HashMap.class); + static final MethodDescriptor HM_PUT = MethodDescriptor.ofMethod(HashMap.class, "put", Object.class, Object.class, + Object.class); + + static final MethodDescriptor ITRA_ITERATOR = MethodDescriptor.ofMethod(Iterable.class, "iterator", Iterator.class); + + static final MethodDescriptor ITR_HAS_NEXT = MethodDescriptor.ofMethod(Iterator.class, "hasNext", boolean.class); + static final MethodDescriptor ITR_NEXT = MethodDescriptor.ofMethod(Iterator.class, "next", Object.class); + + static final MethodDescriptor MAP_GET = MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class); + static final MethodDescriptor MAP_PUT = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, + Object.class); + + static final MethodDescriptor NI_GET_ALL_PREVIOUS_SEGMENTS = MethodDescriptor.ofMethod(NameIterator.class, + "getAllPreviousSegments", String.class); + static final MethodDescriptor NI_GET_NAME = MethodDescriptor.ofMethod(NameIterator.class, "getName", String.class); + static final MethodDescriptor NI_GET_PREVIOUS_SEGMENT = MethodDescriptor.ofMethod(NameIterator.class, "getPreviousSegment", + String.class); + static final MethodDescriptor NI_HAS_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "hasNext", boolean.class); + static final MethodDescriptor NI_NEW_STRING = MethodDescriptor.ofConstructor(NameIterator.class, String.class); + static final MethodDescriptor NI_NEXT_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "nextSegmentEquals", + boolean.class, String.class); + static final MethodDescriptor NI_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); + static final MethodDescriptor NI_PREVIOUS = MethodDescriptor.ofMethod(NameIterator.class, "previous", void.class); + static final MethodDescriptor NI_PREVIOUS_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "previousSegmentEquals", + boolean.class, String.class); + + static final MethodDescriptor OBJ_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); + + static final MethodDescriptor OPT_EMPTY = MethodDescriptor.ofMethod(Optional.class, "empty", Optional.class); + static final MethodDescriptor OPT_GET = MethodDescriptor.ofMethod(Optional.class, "get", Object.class); + static final MethodDescriptor OPT_IS_PRESENT = MethodDescriptor.ofMethod(Optional.class, "isPresent", boolean.class); + static final MethodDescriptor OPT_OF = MethodDescriptor.ofMethod(Optional.class, "of", Optional.class, Object.class); + + static final MethodDescriptor PCS_NEW = MethodDescriptor.ofConstructor(PropertiesConfigSource.class, + Map.class, String.class, int.class); + + static final MethodDescriptor PM_SET_RUNTIME_DEFAULT_PROFILE = MethodDescriptor.ofMethod(ProfileManager.class, + "setRuntimeDefaultProfile", void.class, String.class); + + static final MethodDescriptor SB_NEW = MethodDescriptor.ofConstructor(StringBuilder.class); + static final MethodDescriptor SB_NEW_STR = MethodDescriptor.ofConstructor(StringBuilder.class, String.class); + static final MethodDescriptor SB_APPEND_STRING = MethodDescriptor.ofMethod(StringBuilder.class, "append", + StringBuilder.class, String.class); + static final MethodDescriptor SB_APPEND_CHAR = MethodDescriptor.ofMethod(StringBuilder.class, "append", + StringBuilder.class, char.class); + static final MethodDescriptor SB_LENGTH = MethodDescriptor.ofMethod(StringBuilder.class, "length", + int.class); + static final MethodDescriptor SB_SET_LENGTH = MethodDescriptor.ofMethod(StringBuilder.class, "setLength", + void.class, int.class); + + static final MethodDescriptor QCF_SET_CONFIG = MethodDescriptor.ofMethod(QuarkusConfigFactory.class, "setConfig", + void.class, SmallRyeConfig.class); + + static final MethodDescriptor RTDVCS_NEW = MethodDescriptor.ofConstructor(RTDVCS_CLASS_NAME); + + static final MethodDescriptor SRC_GET_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getConverter", + Converter.class, Class.class); + static final MethodDescriptor SRC_GET_PROPERTY_NAMES = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getPropertyNames", + Iterable.class); + static final MethodDescriptor SRC_GET_VALUE = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getValue", + Object.class, String.class, Converter.class); + + static final MethodDescriptor SRCB_WITH_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, + "withConverter", ConfigBuilder.class, Class.class, int.class, Converter.class); + static final MethodDescriptor SRCB_WITH_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, + "withSources", ConfigBuilder.class, ConfigSource[].class); + static final MethodDescriptor SRCB_BUILD = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, "build", + SmallRyeConfig.class); + + // todo: more space-efficient sorted map impl + static final MethodDescriptor TM_NEW = MethodDescriptor.ofConstructor(TreeMap.class); + + private RunTimeConfigurationGenerator() { + } + + public static void generate(BuildTimeConfigurationReader.ReadResult readResult, final ClassOutput classOutput, + final Map runTimeDefaults, List> additionalTypes) { + new GenerateOperation.Builder().setBuildTimeReadResult(readResult).setClassOutput(classOutput) + .setRunTimeDefaults(runTimeDefaults).setAdditionalTypes(additionalTypes).build().run(); + } + + static final class GenerateOperation implements AutoCloseable { + final AccessorFinder accessorFinder; + final ClassOutput classOutput; + final ClassCreator cc; + final MethodCreator clinit; + final BytecodeCreator converterSetup; + final MethodCreator readConfig; + final ResultHandle readConfigNameBuilder; + final ResultHandle clinitNameBuilder; + final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult; + final ConfigPatternMap runTimePatternMap; + final List roots; + // default values given in the build configuration + final Map specifiedRunTimeDefaultValues; + final Map buildTimeRunTimeVisibleValues; + // default values produced by extensions via build item + final Map runTimeDefaults; + final Map enclosingMemberMethods = new HashMap<>(); + final Map, MethodDescriptor> groupInitMethods = new HashMap<>(); + final Map, FieldDescriptor> configRootsByType = new HashMap<>(); + final ResultHandle clinitConfig; + final Map> convertersToRegister = new HashMap<>(); + final List> additionalTypes; + /** + * Regular converters organized by type. Each converter is stored in a separate field. Some are used + * only at build time, some only at run time, and some at both times. + * Producing a native image will automatically delete the converters which are not used at run time from the + * final image. + */ + final Map convertersByType = new HashMap<>(); + /** + * Cache of things created in `clinit` which are then stored in fields, including config roots and converter + * instances. The result handles are usable only from `clinit`. + */ + final Map instanceCache = new HashMap<>(); + /** + * Converter fields have numeric names to keep space down. + */ + int converterIndex = 0; + + GenerateOperation(Builder builder) { + final BuildTimeConfigurationReader.ReadResult buildTimeReadResult = builder.buildTimeReadResult; + buildTimeConfigResult = Assert.checkNotNullParam("buildTimeReadResult", buildTimeReadResult); + specifiedRunTimeDefaultValues = Assert.checkNotNullParam("specifiedRunTimeDefaultValues", + buildTimeReadResult.getSpecifiedRunTimeDefaultValues()); + buildTimeRunTimeVisibleValues = Assert.checkNotNullParam("buildTimeRunTimeVisibleValues", + buildTimeReadResult.getBuildTimeRunTimeVisibleValues()); + classOutput = Assert.checkNotNullParam("classOutput", builder.getClassOutput()); + roots = Assert.checkNotNullParam("builder.roots", builder.getBuildTimeReadResult().getAllRoots()); + runTimeDefaults = Assert.checkNotNullParam("runTimeDefaults", builder.getRunTimeDefaults()); + additionalTypes = Assert.checkNotNullParam("additionalTypes", builder.getAdditionalTypes()); + cc = ClassCreator.builder().classOutput(classOutput).className(CONFIG_CLASS_NAME).setFinal(true).build(); + // not instantiable + try (MethodCreator mc = cc.getMethodCreator(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME))) { + mc.setModifiers(Opcodes.ACC_PRIVATE); + mc.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), mc.getThis()); + mc.returnValue(null); + } + + // create + clinit = cc.getMethodCreator(MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "", void.class)); + clinit.setModifiers(Opcodes.ACC_STATIC); + clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); + clinitNameBuilder = clinit.newInstance(SB_NEW); + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load("quarkus.")); + + // create the map for build time config source + final ResultHandle buildTimeValues = clinit.newInstance(HM_NEW); + for (Map.Entry entry : buildTimeRunTimeVisibleValues.entrySet()) { + clinit.invokeVirtualMethod(HM_PUT, buildTimeValues, clinit.load(entry.getKey()), clinit.load(entry.getValue())); + } + + // the build time config source field, to feed into the run time config + cc.getFieldCreator(C_BUILD_TIME_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + final ResultHandle buildTimeConfigSource = clinit.newInstance(PCS_NEW, buildTimeValues, + clinit.load("Build time config"), clinit.load(100)); + clinit.writeStaticField(C_BUILD_TIME_CONFIG_SOURCE, buildTimeConfigSource); + + // the build time run time visible default values config source + cc.getFieldCreator(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(BTRTDVCS_NEW)); + + // the run time default values config source + cc.getFieldCreator(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(RTDVCS_NEW)); + + // the build time config, which is for user use only (not used by us other than for loading converters) + final ResultHandle buildTimeBuilder = clinit.invokeStaticMethod(CU_CONFIG_BUILDER, clinit.load(true)); + final ResultHandle array = clinit.newArray(ConfigSource[].class, clinit.load(2)); + // build time values + clinit.writeArrayValue(array, 0, buildTimeConfigSource); + // build time defaults + clinit.writeArrayValue(array, 1, clinit.newInstance(BTRTDVCS_NEW)); + clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, buildTimeBuilder, array); + clinitConfig = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, buildTimeBuilder), + SmallRyeConfig.class); + + // block for converter setup + converterSetup = clinit.createScope(); + // create readConfig + readConfig = cc.getMethodCreator(C_READ_CONFIG); + // the readConfig name builder + readConfigNameBuilder = readConfig.newInstance(SB_NEW); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus.")); + runTimePatternMap = buildTimeReadResult.getRunTimePatternMap(); + accessorFinder = new AccessorFinder(); + } + + public void run() { + // in clinit, load the build-time config + + // make the build time config global until we read the run time config - + // at run time (when we're ready) we update the factory and then release the build time config + clinit.invokeStaticMethod(QCF_SET_CONFIG, clinitConfig); + // release any previous configuration + final ResultHandle clinitCpr = clinit.invokeStaticMethod(CPR_INSTANCE); + try (TryBlock getConfigTry = clinit.tryBlock()) { + final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, + clinitCpr); + getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, clinitCpr, initialConfigHandle); + // ignore + getConfigTry.addCatch(IllegalStateException.class); + } + + // fill roots map + for (RootDefinition root : roots) { + configRootsByType.put(root.getConfigurationClass(), root.getDescriptor()); + } + + // generate the parse methods and populate converters + + final ConfigPatternMap buildTimePatternMap = buildTimeConfigResult.getBuildTimePatternMap(); + final ConfigPatternMap buildTimeRunTimePatternMap = buildTimeConfigResult + .getBuildTimeRunTimePatternMap(); + final ConfigPatternMap runTimePatternMap = buildTimeConfigResult.getRunTimePatternMap(); + + final BiFunction combinator = (a, b) -> a == null ? b : a; + final ConfigPatternMap buildTimeRunTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, + runTimePatternMap, combinator); + final ConfigPatternMap runTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, + buildTimeRunTimePatternMap, combinator); + + final MethodDescriptor siParserBody = generateParserBody(buildTimeRunTimePatternMap, buildTimeRunTimeIgnored, + new StringBuilder("siParseKey"), false, false); + final MethodDescriptor rtParserBody = generateParserBody(runTimePatternMap, runTimeIgnored, + new StringBuilder("rtParseKey"), false, true); + + // create the run time config + final ResultHandle runTimeBuilder = readConfig.invokeStaticMethod(CU_CONFIG_BUILDER, readConfig.load(true)); + + // create the map for run time specified values config source + final ResultHandle specifiedRunTimeValues = clinit.newInstance(HM_NEW); + for (Map.Entry entry : specifiedRunTimeDefaultValues.entrySet()) { + clinit.invokeVirtualMethod(HM_PUT, specifiedRunTimeValues, clinit.load(entry.getKey()), + clinit.load(entry.getValue())); + } + for (Map.Entry entry : runTimeDefaults.entrySet()) { + if (!specifiedRunTimeDefaultValues.containsKey(entry.getKey())) { + // only add entry if the user didn't override it + clinit.invokeVirtualMethod(HM_PUT, specifiedRunTimeValues, clinit.load(entry.getKey()), + clinit.load(entry.getValue())); + } + } + final ResultHandle specifiedRunTimeSource = clinit.newInstance(PCS_NEW, specifiedRunTimeValues, + clinit.load("Specified default values"), clinit.load(Integer.MIN_VALUE + 100)); + cc.getFieldCreator(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE, specifiedRunTimeSource); + + // add in our custom sources + final ResultHandle array = readConfig.newArray(ConfigSource[].class, readConfig.load(4)); + // build time config (expanded values) + readConfig.writeArrayValue(array, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); + // specified run time config default values + readConfig.writeArrayValue(array, 1, readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); + // run time config default values + readConfig.writeArrayValue(array, 2, readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); + // build time run time visible default config source + readConfig.writeArrayValue(array, 3, readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); + + // add in known converters + for (Class additionalType : additionalTypes) { + ConverterType type = new Leaf(additionalType, null); + FieldDescriptor fd = convertersByType.get(type); + if (fd == null) { + // it's an unknown + final ResultHandle clazzHandle = converterSetup.loadClass(additionalType); + fd = FieldDescriptor.of(cc.getClassName(), "conv$" + converterIndex++, Converter.class); + ResultHandle converter = converterSetup.invokeVirtualMethod(SRC_GET_CONVERTER, clinitConfig, clazzHandle); + cc.getFieldCreator(fd).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + converterSetup.writeStaticField(fd, converter); + convertersByType.put(type, fd); + instanceCache.put(fd, converter); + convertersToRegister.put(fd, additionalType); + } + } + if (!convertersToRegister.isEmpty()) { + for (Map.Entry> entry : convertersToRegister.entrySet()) { + final FieldDescriptor descriptor = entry.getKey(); + final Class type = entry.getValue(); + readConfig.invokeVirtualMethod(SRCB_WITH_CONVERTER, runTimeBuilder, readConfig.loadClass(type), + readConfig.load(100), readConfig.readStaticField(descriptor)); + } + } + + // put them in the builder + readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, array); + + final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder); + // install run time config + readConfig.invokeStaticMethod(QCF_SET_CONFIG, runTimeConfig); + // now invalidate the cached config, so the next one to load the config gets the new one + final ResultHandle configProviderResolver = readConfig.invokeStaticMethod(CPR_INSTANCE); + try (TryBlock getConfigTry = readConfig.tryBlock()) { + final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, + configProviderResolver); + getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle); + // ignore + getConfigTry.addCatch(IllegalStateException.class); + } + + final ResultHandle clInitOldLen = clinit.invokeVirtualMethod(SB_LENGTH, clinitNameBuilder); + final ResultHandle rcOldLen = readConfig.invokeVirtualMethod(SB_LENGTH, readConfigNameBuilder); + + // generate eager config read (both build and run time at once) + for (RootDefinition root : roots) { + // common things for all config phases + final Class configurationClass = root.getConfigurationClass(); + FieldDescriptor rootFieldDescriptor = root.getDescriptor(); + + // Get or generate group init method + MethodDescriptor initGroup = generateInitGroup(root); + + final MethodDescriptor ctor = accessorFinder + .getConstructorFor(MethodDescriptor.ofConstructor(configurationClass)); + + // specific actions based on config phase + if (root.getConfigPhase() == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + // config root field is final; we initialize it from clinit + cc.getFieldCreator(rootFieldDescriptor) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + // construct instance in + final ResultHandle instance = clinit.invokeStaticMethod(ctor); + // assign instance to field + clinit.writeStaticField(rootFieldDescriptor, instance); + instanceCache.put(rootFieldDescriptor, instance); + // eager init as appropriate + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load(root.getRootName())); + clinit.invokeStaticMethod(initGroup, clinitConfig, clinitNameBuilder, instance); + clinit.invokeVirtualMethod(SB_SET_LENGTH, clinitNameBuilder, clInitOldLen); + } else if (root.getConfigPhase() == ConfigPhase.RUN_TIME) { + // config root field is volatile; we initialize and read config from the readConfig method + cc.getFieldCreator(rootFieldDescriptor) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); + // construct instance in readConfig + final ResultHandle instance = readConfig.invokeStaticMethod(ctor); + // assign instance to field + readConfig.writeStaticField(rootFieldDescriptor, instance); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, + readConfig.load(root.getRootName())); + readConfig.invokeStaticMethod(initGroup, runTimeConfig, readConfigNameBuilder, instance); + readConfig.invokeVirtualMethod(SB_SET_LENGTH, readConfigNameBuilder, rcOldLen); + } else { + assert root.getConfigPhase() == ConfigPhase.BUILD_TIME; + // ignore explicitly for now (no eager read for these) + } + } + + ResultHandle nameSet; + ResultHandle iterator; + + // generate sweep for clinit + nameSet = clinit.invokeVirtualMethod(SRC_GET_PROPERTY_NAMES, clinitConfig); + iterator = clinit.invokeInterfaceMethod(ITRA_ITERATOR, nameSet); + + try (BytecodeCreator sweepLoop = clinit.createScope()) { + try (BytecodeCreator hasNext = sweepLoop.ifNonZero(sweepLoop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)) + .trueBranch()) { + + final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); + // NameIterator keyIter = new NameIterator(key); + final ResultHandle keyIter = hasNext.newInstance(NI_NEW_STRING, key); + // if (! keyIter.hasNext()) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(sweepLoop); + // if (! keyIter.nextSegmentEquals("quarkus")) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))) + .falseBranch().continueScope(sweepLoop); + // keyIter.next(); // skip "quarkus" + hasNext.invokeVirtualMethod(NI_NEXT, keyIter); + // parse(config, keyIter); + hasNext.invokeStaticMethod(siParserBody, clinitConfig, keyIter); + // continue sweepLoop; + hasNext.continueScope(sweepLoop); + } + } + + // generate sweep for run time + nameSet = readConfig.invokeVirtualMethod(SRC_GET_PROPERTY_NAMES, runTimeConfig); + iterator = readConfig.invokeInterfaceMethod(ITRA_ITERATOR, nameSet); + + try (BytecodeCreator sweepLoop = readConfig.createScope()) { + try (BytecodeCreator hasNext = sweepLoop.ifNonZero(sweepLoop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)) + .trueBranch()) { + + final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); + // NameIterator keyIter = new NameIterator(key); + final ResultHandle keyIter = hasNext.newInstance(NI_NEW_STRING, key); + // if (! keyIter.hasNext()) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(sweepLoop); + // if (! keyIter.nextSegmentEquals("quarkus")) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))) + .falseBranch().continueScope(sweepLoop); + // keyIter.next(); // skip "quarkus" + hasNext.invokeVirtualMethod(NI_NEXT, keyIter); + // parse(config, keyIter); + hasNext.invokeStaticMethod(rtParserBody, runTimeConfig, keyIter); + // continue sweepLoop; + hasNext.continueScope(sweepLoop); + } + } + + // generate ensure-initialized method + try (MethodCreator mc = cc.getMethodCreator(C_ENSURE_INITIALIZED)) { + mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); + mc.returnValue(null); + } + + // generate run time entry point + try (MethodCreator mc = cc.getMethodCreator(C_CREATE_RUN_TIME_CONFIG)) { + mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); + ResultHandle instance = mc.newInstance(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME)); + mc.invokeVirtualMethod(C_READ_CONFIG, instance); + mc.returnValue(null); + } + + // wrap it up + final BytecodeCreator isError = readConfig.ifNonZero(readConfig.invokeStaticMethod(CD_IS_ERROR)).trueBranch(); + readConfig.invokeStaticMethod(CD_RESET_ERROR); + isError.throwException(ConfigurationException.class, + "One or more configuration errors has prevented the application from starting"); + readConfig.returnValue(null); + readConfig.close(); + clinit.returnValue(null); + clinit.close(); + cc.close(); + + // generate run time default values config source class + try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(RTDVCS_CLASS_NAME) + .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { + final ResultHandle keyIter = mc.getMethodParam(0); + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) + mc.addAnnotation(Override.class); + final MethodDescriptor md = generateDefaultValueParse(dvcc, runTimePatternMap, + new StringBuilder("getDefaultFor")); + if (md != null) { + // there is at least one default value + final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter)); + try (BytecodeCreator true1 = if1.trueBranch()) { + true1.invokeVirtualMethod(NI_NEXT, keyIter); + final BranchResult if2 = true1 + .ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus"))); + try (BytecodeCreator true2 = if2.trueBranch()) { + final ResultHandle result = true2.invokeVirtualMethod( + md, mc.getThis(), keyIter); + true2.returnValue(result); + } + } + } + + mc.returnValue(mc.loadNull()); + } + } + + // generate build time run time visible default values config source class + try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(BTRTDVCS_CLASS_NAME) + .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { + final ResultHandle keyIter = mc.getMethodParam(0); + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) + mc.addAnnotation(Override.class); + final MethodDescriptor md = generateDefaultValueParse(dvcc, buildTimeRunTimePatternMap, + new StringBuilder("getDefaultFor")); + if (md != null) { + // there is at least one default value + final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter)); + try (BytecodeCreator true1 = if1.trueBranch()) { + true1.invokeVirtualMethod(NI_NEXT, keyIter); + final BranchResult if2 = true1 + .ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus"))); + try (BytecodeCreator true2 = if2.trueBranch()) { + final ResultHandle result = true2.invokeVirtualMethod( + md, mc.getThis(), keyIter); + true2.returnValue(result); + } + } + } + + mc.returnValue(mc.loadNull()); + } + } + } + + private MethodDescriptor generateInitGroup(ClassDefinition definition) { + final Class clazz = definition.getConfigurationClass(); + MethodDescriptor methodDescriptor = groupInitMethods.get(clazz); + if (methodDescriptor != null) { + return methodDescriptor; + } + methodDescriptor = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "initGroup$" + clazz.getName().replace('.', '$'), + void.class, SmallRyeConfig.class, StringBuilder.class, Object.class); + final MethodCreator bc = cc.getMethodCreator(methodDescriptor).setModifiers(Opcodes.ACC_STATIC); + final ResultHandle config = bc.getMethodParam(0); + // on entry, nameBuilder is our name + final ResultHandle nameBuilder = bc.getMethodParam(1); + final ResultHandle instance = bc.getMethodParam(2); + final ResultHandle length = bc.invokeVirtualMethod(SB_LENGTH, nameBuilder); + for (ClassDefinition.ClassMember member : definition.getMembers()) { + // common setup + final String propertyName = member.getPropertyName(); + final MethodDescriptor setter = accessorFinder.getSetterFor(member.getDescriptor()); + if (!propertyName.isEmpty()) { + // append the property name + bc.invokeVirtualMethod(SB_APPEND_CHAR, nameBuilder, bc.load('.')); + bc.invokeVirtualMethod(SB_APPEND_STRING, nameBuilder, bc.load(propertyName)); + } + if (member instanceof ClassDefinition.ItemMember) { + ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + final FieldDescriptor convField = getOrCreateConverterInstance(leafMember.getField()); + final ResultHandle name = bc.invokeVirtualMethod(OBJ_TO_STRING, nameBuilder); + final ResultHandle converter = bc.readStaticField(convField); + try (TryBlock tryBlock = bc.tryBlock()) { + final ResultHandle val = tryBlock.invokeVirtualMethod(SRC_GET_VALUE, config, name, converter); + tryBlock.invokeStaticMethod(setter, instance, val); + try (CatchBlockCreator catchBadValue = tryBlock.addCatch(IllegalArgumentException.class)) { + catchBadValue.invokeStaticMethod(CD_INVALID_VALUE, name, catchBadValue.getCaughtException()); + } + try (CatchBlockCreator catchNoValue = tryBlock.addCatch(NoSuchElementException.class)) { + catchNoValue.invokeStaticMethod(CD_MISSING_VALUE, name, catchNoValue.getCaughtException()); + } + } + } else if (member instanceof ClassDefinition.GroupMember) { + ClassDefinition.GroupMember groupMember = (ClassDefinition.GroupMember) member; + if (groupMember.isOptional()) { + bc.invokeStaticMethod(setter, instance, bc.invokeStaticMethod(OPT_EMPTY)); + } else { + final GroupDefinition groupDefinition = groupMember.getGroupDefinition(); + final MethodDescriptor nested = generateInitGroup(groupDefinition); + final MethodDescriptor ctor = accessorFinder + .getConstructorFor(MethodDescriptor.ofConstructor(groupDefinition.getConfigurationClass())); + final ResultHandle nestedInstance = bc.invokeStaticMethod(ctor); + bc.invokeStaticMethod(nested, config, nameBuilder, nestedInstance); + bc.invokeStaticMethod(setter, instance, nestedInstance); + } + } else { + assert member instanceof ClassDefinition.MapMember; + final ResultHandle map = bc.newInstance(TM_NEW); + bc.invokeStaticMethod(setter, instance, map); + } + if (!propertyName.isEmpty()) { + // restore length + bc.invokeVirtualMethod(SB_SET_LENGTH, nameBuilder, length); + } + } + bc.returnValue(null); + groupInitMethods.put(clazz, methodDescriptor); + return methodDescriptor; + } + + private static MethodDescriptor generateDefaultValueParse(final ClassCreator dvcc, + final ConfigPatternMap keyMap, final StringBuilder methodName) { + + final Container matched = keyMap.getMatched(); + final boolean hasDefault; + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + hasDefault = itemMember.getDefaultValue() != null; + } else { + hasDefault = false; + } + + final Iterable names = keyMap.childNames(); + final Map children = new HashMap<>(); + MethodDescriptor wildCard = null; + for (String name : names) { + final int length = methodName.length(); + if (name.equals(ConfigPatternMap.WILD_CARD)) { + methodName.append(":*"); + wildCard = generateDefaultValueParse(dvcc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName); + } else { + methodName.append(':').append(name); + final MethodDescriptor value = generateDefaultValueParse(dvcc, keyMap.getChild(name), methodName); + if (value != null) { + children.put(name, value); + } + } + methodName.setLength(length); + } + if (children.isEmpty() && wildCard == null && !hasDefault) { + // skip parse trees with no default values in them + return null; + } + + try (MethodCreator body = dvcc.getMethodCreator(methodName.toString(), String.class, NameIterator.class)) { + body.setModifiers(Opcodes.ACC_PRIVATE); + + final ResultHandle keyIter = body.getMethodParam(0); + // if we've matched the whole thing... + // if (! keyIter.hasNext()) { + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .falseBranch()) { + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + // match? + final String defaultValue = itemMember.getDefaultValue(); + if (defaultValue != null) { + // matched with default value + // return "defaultValue"; + matchedBody.returnValue(matchedBody.load(defaultValue)); + } else { + // matched but no default value + // return null; + matchedBody.returnValue(matchedBody.loadNull()); + } + } else { + // no match + // return null; + matchedBody.returnValue(matchedBody.loadNull()); + } + } + // } + // branches for each next-string + for (String name : children.keySet()) { + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))).trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + // result = getDefault$..$name(keyIter); + ResultHandle result = nameMatched.invokeVirtualMethod(children.get(name), body.getThis(), keyIter); + // return result; + nameMatched.returnValue(result); + } + // } + } + if (wildCard != null) { + // consume and parse + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .trueBranch()) { + // keyIter.next(); + matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + // result = getDefault$..$*(keyIter); + final ResultHandle result = matchedBody.invokeVirtualMethod(wildCard, body.getThis(), keyIter); + // return result; + matchedBody.returnValue(result); + } + } + // unknown + // return null; + body.returnValue(body.loadNull()); + + return body.getMethodDescriptor(); + } + } + + private MethodDescriptor generateParserBody(final ConfigPatternMap keyMap, + final ConfigPatternMap ignoredMap, final StringBuilder methodName, final boolean dynamic, + final boolean isRunTime) { + try (MethodCreator body = cc.getMethodCreator(methodName.toString(), void.class, + SmallRyeConfig.class, NameIterator.class)) { + body.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + final ResultHandle config = body.getMethodParam(0); + final ResultHandle keyIter = body.getMethodParam(1); + final Container matched = keyMap == null ? null : keyMap.getMatched(); + final Object ignoreMatched = ignoredMap == null ? null : ignoredMap.getMatched(); + // if (! keyIter.hasNext()) { + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .falseBranch()) { + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + + if (matched instanceof FieldContainer) { + final FieldContainer fieldContainer = (FieldContainer) matched; + if (dynamic) { + if (!itemMember.getPropertyName().isEmpty()) { + // consume segment + matchedBody.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + // we have to get or create all containing (and contained) groups of this member + matchedBody.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), keyIter, + config); + } + // else ignore (already populated eagerly) + } else { + assert matched instanceof MapContainer; + MapContainer mapContainer = (MapContainer) matched; + // map leafs are always dynamic + final ResultHandle lastSeg = matchedBody.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + matchedBody.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle mapHandle = matchedBody + .invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, config); + // populate the map + final Field field = mapContainer.findField(); + final FieldDescriptor fd = getOrCreateConverterInstance(field); + final ResultHandle key = matchedBody.invokeVirtualMethod(NI_GET_NAME, keyIter); + final ResultHandle converter = matchedBody.readStaticField(fd); + final ResultHandle value = matchedBody.invokeVirtualMethod(SRC_GET_VALUE, config, key, converter); + matchedBody.invokeInterfaceMethod(MAP_PUT, mapHandle, lastSeg, value); + } + } else if (ignoreMatched == null) { + // name is unknown + matchedBody.invokeStaticMethod(isRunTime ? CD_UNKNOWN_RT : CD_UNKNOWN, keyIter); + } + // return; + matchedBody.returnValue(null); + } + // } + boolean hasWildCard = false; + // branches for each next-string + if (keyMap != null) { + final Iterable names = keyMap.childNames(); + for (String name : names) { + if (name.equals(ConfigPatternMap.WILD_CARD)) { + hasWildCard = true; + } else { + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))) + .trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(':').append(name); + nameMatched.invokeStaticMethod( + generateParserBody(keyMap.getChild(name), + ignoredMap == null ? null : ignoredMap.getChild(name), methodName, dynamic, + isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + nameMatched.returnValue(null); + } + // } + } + } + } + // branches for each ignored child + if (ignoredMap != null) { + final Iterable names = ignoredMap.childNames(); + for (String name : names) { + if (name.equals(ConfigPatternMap.WILD_CARD)) { + hasWildCard = true; + } else { + final ConfigPatternMap keyChildMap = keyMap == null ? null : keyMap.getChild(name); + if (keyChildMap != null) { + // we already did this one + continue; + } + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))) + .trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(':').append(name); + nameMatched.invokeStaticMethod( + generateParserBody(null, ignoredMap.getChild(name), methodName, false, isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + nameMatched.returnValue(null); + } + // } + } + } + } + if (hasWildCard) { + assert keyMap != null || ignoredMap != null; + // consume and parse + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .trueBranch()) { + // keyIter.next(); + matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(":*"); + matchedBody.invokeStaticMethod( + generateParserBody(keyMap == null ? null : keyMap.getChild(ConfigPatternMap.WILD_CARD), + ignoredMap == null ? null : ignoredMap.getChild(ConfigPatternMap.WILD_CARD), + methodName, + true, isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + matchedBody.returnValue(null); + } + } + body.invokeStaticMethod(isRunTime ? CD_UNKNOWN_RT : CD_UNKNOWN, keyIter); + body.returnValue(null); + return body.getMethodDescriptor(); + } + } + + private MethodDescriptor generateGetEnclosing(final FieldContainer matchNode, final boolean isRunTime) { + // name iterator cursor is placed BEFORE the field name on entry + MethodDescriptor md = enclosingMemberMethods.get(matchNode); + if (md != null) { + return md; + } + md = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + (isRunTime ? "rt" : "si") + "GetEnclosing:" + matchNode.getCombinedName(), Object.class, + NameIterator.class, SmallRyeConfig.class); + try (MethodCreator mc = cc.getMethodCreator(md)) { + mc.setModifiers(Opcodes.ACC_STATIC); + final ResultHandle keyIter = mc.getMethodParam(0); + final ResultHandle config = mc.getMethodParam(1); + final ClassDefinition.ClassMember member = matchNode.getClassMember(); + final Container parent = matchNode.getParent(); + if (parent == null) { + // it's a root + final RootDefinition definition = (RootDefinition) member.getEnclosingDefinition(); + FieldDescriptor fieldDescriptor = configRootsByType.get(definition.getConfigurationClass()); + assert fieldDescriptor != null : "Field descriptor defined for " + definition.getConfigurationClass(); + mc.returnValue(mc.readStaticField(fieldDescriptor)); + } else if (parent instanceof FieldContainer) { + // get the parent + final FieldContainer fieldContainer = (FieldContainer) parent; + final ClassDefinition.ClassMember classMember = fieldContainer.getClassMember(); + if (!classMember.getPropertyName().isEmpty()) { + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + final ResultHandle enclosing = mc.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), + keyIter, config); + final MethodDescriptor getter = accessorFinder.getGetterFor(classMember.getDescriptor()); + final ResultHandle fieldVal = mc.invokeStaticMethod(getter, enclosing); + final AssignableResultHandle group = mc.createVariable(Object.class); + if (classMember instanceof ClassDefinition.GroupMember + && ((ClassDefinition.GroupMember) classMember).isOptional()) { + final BranchResult isPresent = mc.ifNonZero(mc.invokeVirtualMethod(OPT_IS_PRESENT, fieldVal)); + final BytecodeCreator trueBranch = isPresent.trueBranch(); + final BytecodeCreator falseBranch = isPresent.falseBranch(); + // it already exists + trueBranch.assign(group, trueBranch.invokeVirtualMethod(OPT_GET, fieldVal)); + // it doesn't exist, recreate it + final MethodDescriptor ctor = accessorFinder.getConstructorFor( + MethodDescriptor.ofConstructor(member.getEnclosingDefinition().getConfigurationClass())); + final ResultHandle instance = falseBranch.invokeStaticMethod(ctor); + final ResultHandle precedingKey = falseBranch.invokeVirtualMethod(NI_GET_ALL_PREVIOUS_SEGMENTS, + keyIter); + final ResultHandle nameBuilder = falseBranch.newInstance(SB_NEW_STR, precedingKey); + falseBranch.invokeStaticMethod(generateInitGroup(member.getEnclosingDefinition()), config, nameBuilder, + instance); + final MethodDescriptor setter = accessorFinder.getSetterFor(classMember.getDescriptor()); + falseBranch.invokeStaticMethod(setter, fieldVal, falseBranch.invokeStaticMethod(OPT_OF, instance)); + falseBranch.assign(group, instance); + } else { + mc.assign(group, fieldVal); + } + if (!classMember.getPropertyName().isEmpty()) { + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + } + mc.returnValue(group); + } else { + assert parent instanceof MapContainer; + // the map might or might not contain this group + final MapContainer mapContainer = (MapContainer) parent; + final ResultHandle key = mc.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle map = mc.invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, + config); + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + final ResultHandle existing = mc.invokeInterfaceMethod(MAP_GET, map, key); + mc.ifNull(existing).falseBranch().returnValue(existing); + // add the map key and initialize the enclosed item + final MethodDescriptor ctor = accessorFinder.getConstructorFor( + MethodDescriptor.ofConstructor(member.getEnclosingDefinition().getConfigurationClass())); + final ResultHandle instance = mc.invokeStaticMethod(ctor); + final ResultHandle precedingKey = mc.invokeVirtualMethod(NI_GET_ALL_PREVIOUS_SEGMENTS, keyIter); + final ResultHandle nameBuilder = mc.newInstance(SB_NEW_STR, precedingKey); + mc.invokeStaticMethod(generateInitGroup(member.getEnclosingDefinition()), config, nameBuilder, instance); + mc.invokeInterfaceMethod(MAP_PUT, map, key, instance); + mc.returnValue(instance); + } + } + enclosingMemberMethods.put(matchNode, md); + return md; + } + + private MethodDescriptor generateGetEnclosing(final MapContainer matchNode, final boolean isRunTime) { + // name iterator cursor is placed BEFORE the map key on entry + MethodDescriptor md = enclosingMemberMethods.get(matchNode); + if (md != null) { + return md; + } + md = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + (isRunTime ? "rt" : "si") + "GetEnclosing:" + matchNode.getCombinedName(), Object.class, + NameIterator.class, SmallRyeConfig.class); + try (MethodCreator mc = cc.getMethodCreator(md)) { + mc.setModifiers(Opcodes.ACC_STATIC); + final ResultHandle keyIter = mc.getMethodParam(0); + final ResultHandle config = mc.getMethodParam(1); + final Container parent = matchNode.getParent(); + if (parent instanceof FieldContainer) { + // get the parent + final FieldContainer fieldContainer = (FieldContainer) parent; + if (!fieldContainer.getClassMember().getPropertyName().isEmpty()) { + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + final ResultHandle enclosing = mc.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), + keyIter, config); + if (!fieldContainer.getClassMember().getPropertyName().isEmpty()) { + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + } + final MethodDescriptor getter = accessorFinder + .getGetterFor(fieldContainer.getClassMember().getDescriptor()); + mc.returnValue(mc.invokeStaticMethod(getter, enclosing)); + } else { + assert parent instanceof MapContainer; + // the map might or might not contain this map + final MapContainer mapContainer = (MapContainer) parent; + final ResultHandle key = mc.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + // consume enclosing map key + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle map = mc.invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, + config); + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + final ResultHandle existing = mc.invokeInterfaceMethod(MAP_GET, map, key); + mc.ifNull(existing).falseBranch().returnValue(existing); + // add the map key and initialize the enclosed item + final ResultHandle instance = mc.newInstance(TM_NEW); + mc.invokeInterfaceMethod(MAP_PUT, map, key, instance); + mc.returnValue(instance); + } + } + enclosingMemberMethods.put(matchNode, md); + return md; + } + + private FieldDescriptor getOrCreateConverterInstance(Field field) { + return getOrCreateConverterInstance(field, ConverterType.of(field)); + } + + private FieldDescriptor getOrCreateConverterInstance(Field field, ConverterType type) { + FieldDescriptor fd = convertersByType.get(type); + if (fd != null) { + return fd; + } + + fd = FieldDescriptor.of(cc.getClassName(), "conv$" + converterIndex++, Converter.class); + ResultHandle converter; + boolean storeConverter = false; + if (type instanceof Leaf) { + // simple type + final Leaf leaf = (Leaf) type; + final Class> convertWith = leaf.getConvertWith(); + if (convertWith != null) { + // TODO: temporary until type param inference is in + if (convertWith == HyphenateEnumConverter.class.asSubclass(Converter.class)) { + converter = converterSetup.newInstance(MethodDescriptor.ofConstructor(convertWith, Class.class), + converterSetup.loadClass(type.getLeafType())); + } else { + converter = converterSetup.newInstance(MethodDescriptor.ofConstructor(convertWith)); + } + } else { + final ResultHandle clazzHandle = converterSetup.loadClass(leaf.getLeafType()); + converter = converterSetup.invokeVirtualMethod(SRC_GET_CONVERTER, clinitConfig, clazzHandle); + storeConverter = true; + } + } else if (type instanceof ArrayOf) { + final ArrayOf arrayOf = (ArrayOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, arrayOf.getElementType())); + converter = converterSetup.invokeStaticMethod(CONVS_NEW_ARRAY_CONVERTER, nestedConv, + converterSetup.loadClass(arrayOf.getArrayType())); + } else if (type instanceof CollectionOf) { + final CollectionOf collectionOf = (CollectionOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, collectionOf.getElementType())); + final ResultHandle factory; + final Class collectionClass = collectionOf.getCollectionClass(); + if (collectionClass == List.class) { + factory = converterSetup.invokeStaticMethod(CU_LIST_FACTORY); + } else if (collectionClass == Set.class) { + factory = converterSetup.invokeStaticMethod(CU_SET_FACTORY); + } else if (collectionClass == SortedSet.class) { + factory = converterSetup.invokeStaticMethod(CU_SORTED_SET_FACTORY); + } else { + throw reportError(field, "Unsupported configuration collection type: %s", collectionClass); + } + converter = converterSetup.invokeStaticMethod(CONVS_NEW_COLLECTION_CONVERTER, nestedConv, factory); + } else if (type instanceof LowerBoundCheckOf) { + final LowerBoundCheckOf boundCheckOf = (LowerBoundCheckOf) type; + // todo: add in bounds checker + converter = instanceCache + .get(getOrCreateConverterInstance(field, boundCheckOf.getClassConverterType())); + } else if (type instanceof UpperBoundCheckOf) { + final UpperBoundCheckOf boundCheckOf = (UpperBoundCheckOf) type; + // todo: add in bounds checker + converter = instanceCache + .get(getOrCreateConverterInstance(field, boundCheckOf.getClassConverterType())); + } else if (type instanceof MinMaxValidated) { + MinMaxValidated minMaxValidated = (MinMaxValidated) type; + String min = minMaxValidated.getMin(); + boolean minInclusive = minMaxValidated.isMinInclusive(); + String max = minMaxValidated.getMax(); + boolean maxInclusive = minMaxValidated.isMaxInclusive(); + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, minMaxValidated.getNestedType())); + if (min != null) { + if (max != null) { + converter = converterSetup.invokeStaticMethod( + CONVS_RANGE_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(min), + converterSetup.load(minInclusive), + converterSetup.load(max), + converterSetup.load(maxInclusive)); + } else { + converter = converterSetup.invokeStaticMethod( + CONVS_MINIMUM_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(min), + converterSetup.load(minInclusive)); + } + } else { + assert min == null && max != null; + converter = converterSetup.invokeStaticMethod( + CONVS_MAXIMUM_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(max), + converterSetup.load(maxInclusive)); + } + } else if (type instanceof OptionalOf) { + OptionalOf optionalOf = (OptionalOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, optionalOf.getNestedType())); + converter = converterSetup.invokeStaticMethod(CONVS_NEW_OPTIONAL_CONVERTER, nestedConv); + } else if (type instanceof PatternValidated) { + PatternValidated patternValidated = (PatternValidated) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, patternValidated.getNestedType())); + final ResultHandle patternStr = converterSetup.load(patternValidated.getPatternString()); + converter = converterSetup.invokeStaticMethod(CONVS_PATTERN_CONVERTER, nestedConv, patternStr); + } else { + throw Assert.unreachableCode(); + } + cc.getFieldCreator(fd).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + converterSetup.writeStaticField(fd, converter); + convertersByType.put(type, fd); + instanceCache.put(fd, converter); + if (storeConverter) { + convertersToRegister.put(fd, type.getLeafType()); + } + return fd; + } + + public void close() { + try { + clinit.close(); + } catch (Throwable t) { + try { + cc.close(); + } catch (Throwable t2) { + t2.addSuppressed(t); + throw t2; + } + throw t; + } + cc.close(); + } + + static final class Builder { + private ClassOutput classOutput; + private BuildTimeConfigurationReader.ReadResult buildTimeReadResult; + private Map runTimeDefaults; + private List> additionalTypes; + + Builder() { + } + + ClassOutput getClassOutput() { + return classOutput; + } + + Builder setClassOutput(final ClassOutput classOutput) { + this.classOutput = classOutput; + return this; + } + + BuildTimeConfigurationReader.ReadResult getBuildTimeReadResult() { + return buildTimeReadResult; + } + + Builder setBuildTimeReadResult(final BuildTimeConfigurationReader.ReadResult buildTimeReadResult) { + this.buildTimeReadResult = buildTimeReadResult; + return this; + } + + Map getRunTimeDefaults() { + return runTimeDefaults; + } + + Builder setRunTimeDefaults(final Map runTimeDefaults) { + this.runTimeDefaults = runTimeDefaults; + return this; + } + + List> getAdditionalTypes() { + return additionalTypes; + } + + Builder setAdditionalTypes(final List> additionalTypes) { + this.additionalTypes = additionalTypes; + return this; + } + + GenerateOperation build() { + return new GenerateOperation(this); + } + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java new file mode 100644 index 0000000000000..3049c764e71a4 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java @@ -0,0 +1,279 @@ +package io.quarkus.deployment.configuration.definition; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.wildfly.common.Assert; + +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.util.StringUtil; + +/** + * + */ +public abstract class ClassDefinition extends Definition { + private final Class configurationClass; + private final Map members; + + ClassDefinition(final Builder builder) { + super(); + final Class configurationClass = builder.configurationClass; + if (configurationClass == null) { + throw new IllegalArgumentException("No configuration class given"); + } + this.configurationClass = configurationClass; + final LinkedHashMap map = new LinkedHashMap<>(builder.members.size()); + for (Map.Entry entry : builder.members.entrySet()) { + map.put(entry.getKey(), entry.getValue().construct(this)); + } + this.members = Collections.unmodifiableMap(map); + } + + public final int getMemberCount() { + return members.size(); + } + + public final Iterable getMemberNames() { + return members.keySet(); + } + + public final Iterable getMembers() { + return members.values(); + } + + public Class getConfigurationClass() { + return configurationClass; + } + + public final ClassMember getMember(String name) { + final ClassMember member = members.get(name); + if (member == null) { + throw new IllegalArgumentException("No member named \"" + name + "\" is present on " + configurationClass); + } + return member; + } + + public static abstract class ClassMember extends Member { + public abstract ClassDefinition getEnclosingDefinition(); + + public final String getName() { + return getField().getName(); + } + + public abstract Field getField(); + + public abstract FieldDescriptor getDescriptor(); + + public abstract String getPropertyName(); + + public static abstract class Specification { + Specification() { + } + + abstract Field getField(); + + abstract ClassMember construct(ClassDefinition enclosing); + } + } + + static abstract class LeafMember extends ClassMember { + private final ClassDefinition classDefinition; + private final Field field; + private final FieldDescriptor descriptor; + private final String propertyName; + + LeafMember(final ClassDefinition classDefinition, final Field field) { + this.classDefinition = Assert.checkNotNullParam("classDefinition", classDefinition); + this.field = Assert.checkNotNullParam("field", field); + final Class declaringClass = field.getDeclaringClass(); + final Class configurationClass = classDefinition.configurationClass; + if (declaringClass != configurationClass) { + throw new IllegalArgumentException( + "Member declaring " + declaringClass + " does not match configuration " + configurationClass); + } + descriptor = FieldDescriptor.of(field); + final ConfigItem configItem = field.getAnnotation(ConfigItem.class); + String propertyName = ConfigItem.HYPHENATED_ELEMENT_NAME; + if (configItem != null) { + propertyName = configItem.name(); + if (propertyName.isEmpty()) { + throw reportError(field, "Invalid empty property name"); + } + } + if (propertyName.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { + this.propertyName = StringUtil.hyphenate(field.getName()); + } else if (propertyName.equals(ConfigItem.ELEMENT_NAME)) { + this.propertyName = field.getName(); + } else if (propertyName.equals(ConfigItem.PARENT)) { + this.propertyName = ""; + } else { + this.propertyName = propertyName; + } + } + + public Field getField() { + return field; + } + + public FieldDescriptor getDescriptor() { + return descriptor; + } + + public ClassDefinition getEnclosingDefinition() { + return classDefinition; + } + + public String getPropertyName() { + return propertyName; + } + + public static abstract class Specification extends ClassMember.Specification { + final Field field; + + Specification(final Field field) { + this.field = Assert.checkNotNullParam("field", field); + } + + Field getField() { + return field; + } + } + } + + public static final class GroupMember extends LeafMember { + private final GroupDefinition groupDefinition; + private final boolean optional; + + GroupMember(final ClassDefinition classDefinition, final Field field, final GroupDefinition groupDefinition, + final boolean optional) { + super(classDefinition, field); + this.groupDefinition = groupDefinition; + this.optional = optional; + } + + public GroupDefinition getGroupDefinition() { + return groupDefinition; + } + + public boolean isOptional() { + return optional; + } + + public static final class Specification extends LeafMember.Specification { + private final GroupDefinition groupDefinition; + private final boolean optional; + + public Specification(final Field field, final GroupDefinition groupDefinition, final boolean optional) { + super(field); + this.groupDefinition = Assert.checkNotNullParam("groupDefinition", groupDefinition); + this.optional = optional; + } + + public boolean isOptional() { + return optional; + } + + ClassMember construct(final ClassDefinition enclosing) { + return new GroupMember(enclosing, field, groupDefinition, optional); + } + } + } + + public static final class ItemMember extends LeafMember { + private final String defaultValue; + + ItemMember(final ClassDefinition classDefinition, final Field field, final String defaultValue) { + super(classDefinition, field); + this.defaultValue = defaultValue; + } + + public String getDefaultValue() { + return defaultValue; + } + + public static final class Specification extends LeafMember.Specification { + private final String defaultValue; + + public Specification(final Field field, final String defaultValue) { + super(field); + // nullable + this.defaultValue = defaultValue; + } + + ClassMember construct(final ClassDefinition enclosing) { + return new ItemMember(enclosing, field, defaultValue); + } + } + } + + public static final class MapMember extends ClassMember { + private final ClassMember nested; + + MapMember(final ClassMember nested) { + this.nested = nested; + } + + public ClassMember getNested() { + return nested; + } + + public ClassDefinition getEnclosingDefinition() { + return nested.getEnclosingDefinition(); + } + + public Field getField() { + return nested.getField(); + } + + public FieldDescriptor getDescriptor() { + return nested.getDescriptor(); + } + + public String getPropertyName() { + return nested.getPropertyName(); + } + + public static final class Specification extends ClassMember.Specification { + private final ClassMember.Specification nested; + + public Specification(final ClassMember.Specification nested) { + this.nested = Assert.checkNotNullParam("nested", nested); + } + + Field getField() { + return nested.getField(); + } + + ClassMember construct(final ClassDefinition enclosing) { + return new MapMember(nested.construct(enclosing)); + } + } + } + + public static abstract class Builder extends Definition.Builder { + Builder() { + } + + private Class configurationClass; + private final Map members = new LinkedHashMap<>(); + + public Builder setConfigurationClass(final Class configurationClass) { + this.configurationClass = configurationClass; + return this; + } + + public Class getConfigurationClass() { + return configurationClass; + } + + public void addMember(ClassMember.Specification spec) { + Assert.checkNotNullParam("spec", spec); + members.put(spec.getField().getName(), spec); + } + + public abstract ClassDefinition build(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java new file mode 100644 index 0000000000000..a6919959dd487 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java @@ -0,0 +1,35 @@ +package io.quarkus.deployment.configuration.definition; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Parameter; + +/** + * A configuration definition. Definitions always contain links to the things they contain, but not to their own + * containers. + */ +public abstract class Definition { + Definition() { + } + + public static abstract class Builder { + Builder() { + } + + public abstract Definition build(); + } + + static IllegalArgumentException reportError(AnnotatedElement e, String msg) { + if (e instanceof Member) { + return new IllegalArgumentException(msg + " at " + e + " of " + ((Member) e).getEnclosingDefinition()); + } else if (e instanceof Parameter) { + return new IllegalArgumentException(msg + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " + + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); + } else { + return new IllegalArgumentException(msg + " at " + e); + } + } + + public static abstract class Member { + public abstract Definition getEnclosingDefinition(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java new file mode 100644 index 0000000000000..549e4f49dedba --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.configuration.definition; + +/** + * + */ +public final class GroupDefinition extends ClassDefinition { + GroupDefinition(final Builder builder) { + super(builder); + } + + public static final class Builder extends ClassDefinition.Builder { + public Builder() { + } + + public GroupDefinition build() { + return new GroupDefinition(this); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java new file mode 100644 index 0000000000000..636d28efbba31 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java @@ -0,0 +1,108 @@ +package io.quarkus.deployment.configuration.definition; + +import static io.quarkus.deployment.configuration.RunTimeConfigurationGenerator.CONFIG_CLASS_NAME; +import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; +import static io.quarkus.runtime.util.StringUtil.lowerCase; +import static io.quarkus.runtime.util.StringUtil.lowerCaseFirst; +import static io.quarkus.runtime.util.StringUtil.toList; +import static io.quarkus.runtime.util.StringUtil.withoutSuffix; + +import java.util.List; + +import org.wildfly.common.Assert; + +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; + +/** + * + */ +public final class RootDefinition extends ClassDefinition { + private final ConfigPhase configPhase; + private final String rootName; + private final FieldDescriptor descriptor; + + RootDefinition(final Builder builder) { + super(builder); + this.configPhase = builder.configPhase; + String rootName = builder.rootName; + final Class configClass = getConfigurationClass(); + final List segments = toList(camelHumpsIterator(configClass.getSimpleName())); + final List trimmedSegments; + if (configPhase == ConfigPhase.RUN_TIME) { + trimmedSegments = withoutSuffix( + withoutSuffix( + withoutSuffix( + withoutSuffix( + segments, + "Run", "Time", "Configuration"), + "Run", "Time", "Config"), + "Configuration"), + "Config"); + } else { + trimmedSegments = withoutSuffix( + withoutSuffix( + withoutSuffix( + withoutSuffix( + segments, + "Build", "Time", "Configuration"), + "Build", "Time", "Config"), + "Configuration"), + "Config"); + } + if (rootName.equals(ConfigItem.PARENT)) { + throw reportError(configClass, "Root cannot inherit parent name because it has no parent"); + } else if (rootName.equals(ConfigItem.ELEMENT_NAME)) { + rootName = String.join("", (Iterable) () -> lowerCaseFirst(trimmedSegments.iterator())); + } else if (rootName.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { + rootName = String.join("-", (Iterable) () -> lowerCase(trimmedSegments.iterator())); + } + this.rootName = rootName; + this.descriptor = FieldDescriptor.of(CONFIG_CLASS_NAME, String.join("", segments), Object.class); + } + + public ConfigPhase getConfigPhase() { + return configPhase; + } + + public String getRootName() { + return rootName; + } + + public FieldDescriptor getDescriptor() { + return descriptor; + } + + public static final class Builder extends ClassDefinition.Builder { + private ConfigPhase configPhase = ConfigPhase.BUILD_TIME; + private String rootName = ConfigItem.HYPHENATED_ELEMENT_NAME; + + public Builder() { + } + + public ConfigPhase getConfigPhase() { + return configPhase; + } + + public Builder setConfigPhase(final ConfigPhase configPhase) { + Assert.checkNotNullParam("configPhase", configPhase); + this.configPhase = configPhase; + return this; + } + + public String getRootName() { + return rootName; + } + + public Builder setRootName(final String rootName) { + Assert.checkNotNullParam("rootName", rootName); + this.rootName = rootName; + return this; + } + + public RootDefinition build() { + return new RootDefinition(this); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java similarity index 62% rename from core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java rename to core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java index e2ef97f22b4dd..fbf525fc1c6bf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java @@ -1,9 +1,10 @@ -package io.quarkus.deployment.configuration; +package io.quarkus.deployment.configuration.matching; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Objects; import java.util.TreeMap; +import java.util.function.BiFunction; import org.wildfly.common.Assert; @@ -127,6 +128,89 @@ public void addChild(final String childName, final ConfigPatternMap child) { children.put(childName, child); } + public static ConfigPatternMap merge(final ConfigPatternMap param0, + final ConfigPatternMap param1, final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final T matched0 = param0.getMatched(); + final U matched1 = param1.getMatched(); + result.setMatched(combinator.apply(matched0, matched1)); + + // they're sorted; combine them in order + final Iterator iter0 = param0.childNames().iterator(); + final Iterator iter1 = param1.childNames().iterator(); + String next0; + String next1; + if (iter0.hasNext() && iter1.hasNext()) { + next0 = iter0.next(); + next1 = iter1.next(); + for (;;) { + if (next0.compareTo(next1) < 0) { + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + if (iter0.hasNext()) { + next0 = iter0.next(); + } else { + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + break; + } + } else if (next0.compareTo(next1) > 0) { + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + if (iter1.hasNext()) { + next1 = iter1.next(); + } else { + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + break; + } + } else { + assert next0.compareTo(next1) == 0; + result.addChild(next0, merge(param0.getChild(next0), param1.getChild(next1), combinator)); + if (iter0.hasNext() && iter1.hasNext()) { + next0 = iter0.next(); + next1 = iter1.next(); + } else { + break; + } + } + } + } + while (iter0.hasNext()) { + next0 = iter0.next(); + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + } + while (iter1.hasNext()) { + next1 = iter1.next(); + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + } + return result; + } + + private static ConfigPatternMap merge0(final ConfigPatternMap param0, + final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final T matched0 = param0.getMatched(); + result.setMatched(combinator.apply(matched0, null)); + final Iterator iter0 = param0.childNames().iterator(); + String next0; + while (iter0.hasNext()) { + next0 = iter0.next(); + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + } + return result; + } + + private static ConfigPatternMap merge1(final ConfigPatternMap param1, + final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final U matched1 = param1.getMatched(); + result.setMatched(combinator.apply(null, matched1)); + final Iterator iter1 = param1.childNames().iterator(); + String next1; + while (iter1.hasNext()) { + next1 = iter1.next(); + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + } + return result; + } + public static class PatternIterator implements Iterator { ConfigPatternMap current; ConfigPatternMap next; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java new file mode 100644 index 0000000000000..b1e87d7ae7770 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.configuration.matching; + +import java.lang.reflect.Field; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; + +/** + * A container for a configuration key path. + */ +public abstract class Container { + Container() { + } + + /** + * Get the parent container, or {@code null} if the container is a root. Presently only + * field containers may be roots. + * + * @return the parent container + */ + public abstract Container getParent(); + + /** + * Find the field that will ultimately hold this value. + * + * @return the field (must not be {@code null}) + */ + public final Field findField() { + return getClassMember().getField(); + } + + /** + * Find the enclosing class definition that will ultimately hold this value. + * + * @return the class definition (must not be {@code null}) + */ + public final ClassDefinition findEnclosingClass() { + return getClassMember().getEnclosingDefinition(); + } + + /** + * Find the enclosing class member. + * + * @return the enclosing class member + */ + public abstract ClassDefinition.ClassMember getClassMember(); + + /** + * Get the combined name of this item. + * + * @return the combined name (must not be {@code null}) + */ + public final String getCombinedName() { + return getCombinedName(new StringBuilder()).toString(); + } + + abstract StringBuilder getCombinedName(StringBuilder sb); + + public final String getPropertyName() { + return getPropertyName(new StringBuilder()).toString(); + } + + abstract StringBuilder getPropertyName(StringBuilder sb); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java new file mode 100644 index 0000000000000..820aa00a0a214 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java @@ -0,0 +1,62 @@ +package io.quarkus.deployment.configuration.matching; + +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; + +/** + * + */ +public final class FieldContainer extends Container { + private final Container parent; + private final ClassDefinition.ClassMember member; + + public FieldContainer(final Container parent, final ClassDefinition.ClassMember member) { + this.parent = parent; + this.member = Assert.checkNotNullParam("member", member); + } + + public Container getParent() { + return parent; + } + + public ClassDefinition.ClassMember getClassMember() { + return member; + } + + StringBuilder getCombinedName(final StringBuilder sb) { + Container parent = getParent(); + if (parent != null) { + parent.getCombinedName(sb); + } + final ClassDefinition enclosing = member.getEnclosingDefinition(); + if (enclosing instanceof RootDefinition) { + sb.append(((RootDefinition) enclosing).getRootName().replace('.', ':')); + } + if (sb.length() > 0) { + sb.append(':'); + } + sb.append(member.getName()); + return sb; + } + + StringBuilder getPropertyName(final StringBuilder sb) { + Container parent = getParent(); + if (parent != null) { + parent.getPropertyName(sb); + } + final ClassDefinition enclosing = member.getEnclosingDefinition(); + if (enclosing instanceof RootDefinition) { + sb.append(((RootDefinition) enclosing).getRootName()); + } + final String propertyName = member.getPropertyName(); + if (!propertyName.isEmpty()) { + if (sb.length() > 0) { + sb.append('.'); + } + sb.append(propertyName); + } + return sb; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java new file mode 100644 index 0000000000000..9360e81635e07 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java @@ -0,0 +1,46 @@ +package io.quarkus.deployment.configuration.matching; + +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; + +/** + * A map container. + */ +public final class MapContainer extends Container { + private final Container parent; + private final ClassDefinition.ClassMember mapMember; + + public MapContainer(final Container parent, final ClassDefinition.ClassMember mapMember) { + this.parent = Assert.checkNotNullParam("parent", parent); + this.mapMember = mapMember; + } + + public ClassDefinition.ClassMember getClassMember() { + return mapMember; + } + + public Container getParent() { + return parent; + } + + StringBuilder getCombinedName(final StringBuilder sb) { + // maps always have a parent + getParent().getCombinedName(sb); + if (sb.length() > 0) { + sb.append(':'); + } + sb.append('*'); + return sb; + } + + StringBuilder getPropertyName(final StringBuilder sb) { + // maps always have a parent + getParent().getPropertyName(sb); + if (sb.length() > 0) { + sb.append('.'); + } + sb.append('*'); + return sb; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java new file mode 100644 index 0000000000000..53a5190632b11 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java @@ -0,0 +1,85 @@ +package io.quarkus.deployment.configuration.matching; + +import java.util.List; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.runtime.configuration.NameIterator; + +/** + * + */ +public final class PatternMapBuilder { + + private PatternMapBuilder() { + } + + public static ConfigPatternMap makePatterns(List rootDefinitions) { + ConfigPatternMap patternMap = new ConfigPatternMap<>(); + for (RootDefinition rootDefinition : rootDefinitions) { + final String rootName = rootDefinition.getRootName(); + ConfigPatternMap addTo = patternMap, child; + assert !rootName.isEmpty(); + NameIterator ni = new NameIterator(rootName); + assert ni.hasNext(); + do { + final String seg = ni.getNextSegment(); + child = addTo.getChild(seg); + ni.next(); + if (child == null) { + addTo.addChild(seg, child = new ConfigPatternMap<>()); + } + addTo = child; + } while (ni.hasNext()); + addGroup(addTo, rootDefinition, null); + } + return patternMap; + } + + private static void addGroup(ConfigPatternMap patternMap, ClassDefinition current, + Container parent) { + for (ClassDefinition.ClassMember member : current.getMembers()) { + final String propertyName = member.getPropertyName(); + ConfigPatternMap addTo = patternMap; + FieldContainer newNode; + if (!propertyName.isEmpty()) { + NameIterator ni = new NameIterator(propertyName); + assert ni.hasNext(); + do { + final String seg = ni.getNextSegment(); + ConfigPatternMap child = addTo.getChild(seg); + if (child == null) { + addTo.addChild(seg, child = new ConfigPatternMap<>()); + } + addTo = child; + ni.next(); + } while (ni.hasNext()); + } + newNode = new FieldContainer(parent, member); + addMember(addTo, member, newNode); + } + } + + private static void addMember(ConfigPatternMap patternMap, ClassDefinition.ClassMember member, + Container container) { + if (member instanceof ClassDefinition.ItemMember) { + Container matched = patternMap.getMatched(); + if (matched != null) { + throw new IllegalArgumentException( + "Multiple matching properties for name \"" + matched.getPropertyName() + "\""); + } + patternMap.setMatched(container); + } else if (member instanceof ClassDefinition.MapMember) { + ClassDefinition.MapMember mapMember = (ClassDefinition.MapMember) member; + ConfigPatternMap addTo = patternMap.getChild(ConfigPatternMap.WILD_CARD); + if (addTo == null) { + patternMap.addChild(ConfigPatternMap.WILD_CARD, addTo = new ConfigPatternMap<>()); + } + final ClassDefinition.ClassMember nestedMember = mapMember.getNested(); + addMember(addTo, nestedMember, new MapContainer(container, nestedMember)); + } else { + assert member instanceof ClassDefinition.GroupMember; + addGroup(patternMap, ((ClassDefinition.GroupMember) member).getGroupDefinition(), container); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java new file mode 100644 index 0000000000000..0e050069f6455 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java @@ -0,0 +1,53 @@ +package io.quarkus.deployment.configuration.type; + +import java.lang.reflect.Array; +import java.util.Objects; + +/** + * + */ +public final class ArrayOf extends ConverterType { + private final ConverterType type; + private int hashCode; + private Class arrayType; + + public ArrayOf(final ConverterType type) { + this.type = type; + } + + public ConverterType getElementType() { + return type; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public Class getArrayType() { + Class arrayType = this.arrayType; + if (arrayType == null) { + this.arrayType = arrayType = Array.newInstance(getLeafType(), 0).getClass(); + } + return arrayType; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, ArrayOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof ArrayOf && equals((ArrayOf) obj); + } + + public boolean equals(final ArrayOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java new file mode 100644 index 0000000000000..842ba505b1546 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class CollectionOf extends ConverterType { + private final ConverterType type; + private final Class collectionClass; + private int hashCode; + + public CollectionOf(final ConverterType type, final Class collectionClass) { + this.type = type; + this.collectionClass = collectionClass; + } + + public ConverterType getElementType() { + return type; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public Class getCollectionClass() { + return collectionClass; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, collectionClass, CollectionOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof CollectionOf && equals((CollectionOf) obj); + } + + public boolean equals(final CollectionOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java new file mode 100644 index 0000000000000..15805bf22e0ab --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java @@ -0,0 +1,112 @@ +package io.quarkus.deployment.configuration.type; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; + +import io.quarkus.deployment.util.ReflectUtil; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.annotations.DefaultConverter; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; + +/** + * + */ +public abstract class ConverterType { + ConverterType() { + } + + public abstract Class getLeafType(); + + public static ConverterType of(Field member) { + return of(member.getGenericType(), member); + } + + public static ConverterType of(Parameter parameter) { + return of(parameter.getParameterizedType(), parameter); + } + + public static ConverterType of(Type type, AnnotatedElement element) { + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return new ArrayOf(of(genericArrayType.getGenericComponentType(), element)); + } else if (type instanceof Class) { + // simple type + Class clazz = (Class) type; + if (clazz.isArray()) { + return new ArrayOf(of(clazz.getComponentType(), element)); + } + ConvertWith convertWith = element.getAnnotation(ConvertWith.class); + Leaf leaf; + if (convertWith == null && element.getAnnotation(DefaultConverter.class) == null && clazz.isEnum()) { + // use our hyphenated converter by default + leaf = new Leaf(clazz, HyphenateEnumConverter.class); + } else { + leaf = new Leaf(clazz, convertWith == null ? null : convertWith.value()); + } + // vvv todo: add validations here vvv + // return result + return leaf; + } else if (type instanceof ParameterizedType) { + final ParameterizedType paramType = (ParameterizedType) type; + final Class rawType = ReflectUtil.rawTypeOf(paramType); + final Type[] args = paramType.getActualTypeArguments(); + if (args.length == 1) { + final Type arg = args[0]; + if (rawType == Class.class) { + ConverterType result = of(Class.class, element); + if (arg instanceof WildcardType) { + final WildcardType wcType = (WildcardType) arg; + // gather bounds for validation + Class[] upperBounds = ReflectUtil.rawTypesOfDestructive(wcType.getUpperBounds()); + Class[] lowerBounds = ReflectUtil.rawTypesOfDestructive(wcType.getLowerBounds()); + for (Class upperBound : upperBounds) { + if (upperBound != Object.class) { + result = new UpperBoundCheckOf(upperBound, result); + } + } + for (Class lowerBound : lowerBounds) { + result = new LowerBoundCheckOf(lowerBound, result); + } + return result; + } + throw new IllegalArgumentException("Class configuration item types cannot be invariant"); + } + final ConverterType nested = of(arg, element); + if (rawType == List.class || rawType == Set.class || rawType == SortedSet.class + || rawType == NavigableSet.class) { + return new CollectionOf(nested, rawType); + } else if (rawType == Optional.class) { + return new OptionalOf(nested); + } else { + throw unsupportedType(type); + } + } else if (args.length == 2) { + if (rawType == Map.class) { + // the real converter is the converter for the value type + return of(ReflectUtil.typeOfParameter(paramType, 1), element); + } else { + throw unsupportedType(type); + } + } else { + throw unsupportedType(type); + } + } else { + throw unsupportedType(type); + } + } + + private static IllegalArgumentException unsupportedType(final Type type) { + return new IllegalArgumentException("Unsupported type: " + type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java new file mode 100644 index 0000000000000..4a0073e33660f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java @@ -0,0 +1,47 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * + */ +public final class Leaf extends ConverterType { + private final Class type; + private final Class> convertWith; + private int hashCode; + + public Leaf(final Class type, final Class convertWith) { + this.type = type; + this.convertWith = (Class>) convertWith; + } + + public Class getLeafType() { + return type; + } + + public Class> getConvertWith() { + return convertWith; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, convertWith); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof Leaf && equals((Leaf) obj); + } + + public boolean equals(final Leaf obj) { + return obj == this || obj != null && type == obj.type && convertWith == obj.convertWith; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java new file mode 100644 index 0000000000000..ea37614dfc7ac --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class LowerBoundCheckOf extends ConverterType { + private final Class lowerBound; + private final ConverterType classConverterType; + private int hashCode; + + public LowerBoundCheckOf(final Class lowerBound, final ConverterType classConverterType) { + this.lowerBound = lowerBound; + this.classConverterType = classConverterType; + } + + public Class getLowerBound() { + return lowerBound; + } + + public ConverterType getClassConverterType() { + return classConverterType; + } + + public Class getLeafType() { + return classConverterType.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(classConverterType, lowerBound, LowerBoundCheckOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof LowerBoundCheckOf && equals((LowerBoundCheckOf) obj); + } + + public boolean equals(final LowerBoundCheckOf obj) { + return obj == this || obj != null && lowerBound == obj.lowerBound && classConverterType.equals(obj.classConverterType); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java new file mode 100644 index 0000000000000..320c29923ad7c --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java @@ -0,0 +1,69 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class MinMaxValidated extends ConverterType { + private final ConverterType type; + private final String min; + private final boolean minInclusive; + private final String max; + private final boolean maxInclusive; + private int hashCode; + + public MinMaxValidated(final ConverterType type, final String min, final boolean minInclusive, final String max, + final boolean maxInclusive) { + this.type = type; + this.min = min; + this.minInclusive = minInclusive; + this.max = max; + this.maxInclusive = maxInclusive; + } + + public ConverterType getNestedType() { + return type; + } + + public String getMin() { + return min; + } + + public boolean isMinInclusive() { + return minInclusive; + } + + public String getMax() { + return max; + } + + public boolean isMaxInclusive() { + return maxInclusive; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, min, Boolean.valueOf(minInclusive), max, Boolean.valueOf(maxInclusive)); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof MinMaxValidated && equals((MinMaxValidated) obj); + } + + public boolean equals(final MinMaxValidated obj) { + return this == obj || obj != null && Objects.equals(type, obj.type) && Objects.equals(min, obj.min) + && Objects.equals(max, obj.max) && maxInclusive == obj.maxInclusive && minInclusive == obj.minInclusive; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java new file mode 100644 index 0000000000000..dcbc01ddfa902 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java @@ -0,0 +1,43 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class OptionalOf extends ConverterType { + private final ConverterType type; + private int hashCode; + + public OptionalOf(final ConverterType type) { + this.type = type; + } + + public ConverterType getNestedType() { + return type; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, OptionalOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof OptionalOf && equals((OptionalOf) obj); + } + + public boolean equals(final OptionalOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } + + public Class getLeafType() { + return type.getLeafType(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java new file mode 100644 index 0000000000000..cba5df78daf1f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class PatternValidated extends ConverterType { + private final ConverterType type; + private final String patternString; + private int hashCode; + + public PatternValidated(final ConverterType type, final String patternString) { + this.type = type; + this.patternString = patternString; + } + + public ConverterType getNestedType() { + return type; + } + + public String getPatternString() { + return patternString; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, patternString); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof PatternValidated && equals((PatternValidated) obj); + } + + public boolean equals(final PatternValidated obj) { + return obj == this || obj != null && type.equals(obj.type) && patternString.equals(obj.patternString); + } + + public Class getLeafType() { + return type.getLeafType(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java new file mode 100644 index 0000000000000..7a328de862dd7 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class UpperBoundCheckOf extends ConverterType { + private final Class upperBound; + private final ConverterType classConverterType; + private int hashCode; + + public UpperBoundCheckOf(final Class upperBound, final ConverterType classConverterType) { + this.upperBound = upperBound; + this.classConverterType = classConverterType; + } + + public Class getUpperBound() { + return upperBound; + } + + public ConverterType getClassConverterType() { + return classConverterType; + } + + public Class getLeafType() { + return classConverterType.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(classConverterType, upperBound, UpperBoundCheckOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof UpperBoundCheckOf && equals((UpperBoundCheckOf) obj); + } + + public boolean equals(final UpperBoundCheckOf obj) { + return obj == this || obj != null && upperBound == obj.upperBound && classConverterType.equals(obj.classConverterType); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index ecc33b77a2b89..df72dd3f5401a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.pkg; import java.io.File; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -16,7 +15,7 @@ public class NativeConfig { * Additional arguments to pass to the build process */ @ConfigItem - public List additionalBuildArgs; + public Optional> additionalBuildArgs; /** * If the HTTP url handler should be enabled, allowing you to do URL.openConnection() for HTTP URLs @@ -52,7 +51,7 @@ public class NativeConfig { * The location of the Graal distribution */ @ConfigItem(defaultValue = "${GRAALVM_HOME:}") - public String graalvmHome; + public Optional graalvmHome; /** * The location of the JDK @@ -141,13 +140,13 @@ public class NativeConfig { * a container build is always done. */ @ConfigItem - public String containerRuntime = ""; + public Optional containerRuntime; /** * Options to pass to the container runtime */ @ConfigItem - public List containerRuntimeOptions = new ArrayList<>(); + public Optional> containerRuntimeOptions; /** * If the resulting image should allow VM introspection diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java index c98f1a080e7a8..3e922e93ad2cb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java @@ -1,6 +1,7 @@ package io.quarkus.deployment.pkg; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -41,7 +42,7 @@ public class PackageConfig { * Files that should not be copied to the output artifact */ @ConfigItem - public List userConfiguredIgnoredEntries; + public Optional> userConfiguredIgnoredEntries; /** * The suffix that is applied to the runner jar and native images diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index 958b3021905ea..be70dcaae3f95 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -180,7 +180,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem, final StringBuilder classPath = new StringBuilder(); final Map> services = new HashMap<>(); Set finalIgnoredEntries = new HashSet<>(IGNORED_ENTRIES); - finalIgnoredEntries.addAll(packageConfig.userConfiguredIgnoredEntries); + packageConfig.userConfiguredIgnoredEntries.ifPresent(finalIgnoredEntries::addAll); final List appDeps = curateOutcomeBuildItem.getEffectiveModel().getUserDependencies(); @@ -567,6 +567,7 @@ private void generateManifest(FileSystem runnerZipFs, final String classPath, Pa } else { Files.createDirectories(runnerZipFs.getPath("META-INF")); } + Files.createDirectories(manifestPath.getParent()); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); if (attributes.containsKey(Attributes.Name.CLASS_PATH)) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 00e4ee8d224a5..bf8769bb7598c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -79,8 +80,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa String noPIE = ""; - if (!"".equals(nativeConfig.containerRuntime) || nativeConfig.containerBuild) { - String containerRuntime = nativeConfig.containerRuntime.isEmpty() ? "docker" : nativeConfig.containerRuntime; + if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) { + String containerRuntime = nativeConfig.containerRuntime.orElse("docker"); // E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image" nativeImage = new ArrayList<>(); Collections.addAll(nativeImage, containerRuntime, "run", "-v", @@ -98,7 +99,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeImage.add("--userns=keep-id"); } } - nativeImage.addAll(nativeConfig.containerRuntimeOptions); + nativeConfig.containerRuntimeOptions.ifPresent(nativeImage::addAll); if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) { // publish the debug port onto the host if asked for nativeImage.add("--publish=" + DEBUG_BUILD_PROCESS_PORT + ":" + DEBUG_BUILD_PROCESS_PORT); @@ -109,12 +110,10 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa noPIE = detectNoPIE(); } - String graal = nativeConfig.graalvmHome; + Optional graal = nativeConfig.graalvmHome; File java = nativeConfig.javaHome; - if (graal != null) { - env.put(GRAALVM_HOME, graal); - } else { - graal = env.get(GRAALVM_HOME); + if (graal.isPresent()) { + env.put(GRAALVM_HOME, graal.get()); } if (java == null) { // try system property first - it will be the JAVA_HOME used by the current JVM @@ -171,9 +170,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeConfig.enableAllSecurityServices = true; } - if (nativeConfig.additionalBuildArgs != null) { - command.addAll(nativeConfig.additionalBuildArgs); - } + nativeConfig.additionalBuildArgs.ifPresent(command::addAll); command.add("--initialize-at-build-time="); command.add("-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time command.add("-jar"); @@ -305,10 +302,10 @@ private boolean isThisGraalVMVersionObsolete() { return false; } - private static File getNativeImageExecutable(String graalVmHome, File javaHome, Map env) { + private static File getNativeImageExecutable(Optional graalVmHome, File javaHome, Map env) { String imageName = IS_WINDOWS ? "native-image.cmd" : "native-image"; - if (graalVmHome != null) { - File file = Paths.get(graalVmHome, "bin", imageName).toFile(); + if (graalVmHome.isPresent()) { + File file = Paths.get(graalVmHome.get(), "bin", imageName).toFile(); if (file.exists()) { return file; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java new file mode 100644 index 0000000000000..fd5089b4eba02 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java @@ -0,0 +1,107 @@ +package io.quarkus.deployment.steps; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.Set; + +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; +import org.eclipse.microprofile.config.spi.Converter; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationSourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.runtime.graal.InetRunTime; +import io.smallrye.config.SmallRyeConfigProviderResolver; + +class ConfigBuildSteps { + + static final String PROVIDER_CLASS_NAME = "io.quarkus.runtime.generated.ConfigSourceProviderImpl"; + + static final String SERVICES_PREFIX = "META-INF/services/"; + + @BuildStep + void generateConfigSources(List runTimeSources, + final BuildProducer generatedClass, + final BuildProducer generatedResource) { + ClassOutput classOutput = new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); + } + }; + + try (ClassCreator cc = ClassCreator.builder().interfaces(ConfigSourceProvider.class).setFinal(true) + .className(PROVIDER_CLASS_NAME) + .classOutput(classOutput).build()) { + try (MethodCreator mc = cc.getMethodCreator(MethodDescriptor.ofMethod(ConfigSourceProvider.class, + "getConfigSources", Iterable.class, ClassLoader.class))) { + + final ResultHandle array = mc.newArray(ConfigSource.class, mc.load(runTimeSources.size())); + for (int i = 0; i < runTimeSources.size(); i++) { + final RunTimeConfigurationSourceBuildItem runTimeSource = runTimeSources.get(i); + final String className = runTimeSource.getClassName(); + final OptionalInt priority = runTimeSource.getPriority(); + ResultHandle value; + if (priority.isPresent()) { + value = mc.newInstance(MethodDescriptor.ofConstructor(className, int.class), + mc.load(priority.getAsInt())); + } else { + value = mc.newInstance(MethodDescriptor.ofConstructor(className)); + } + mc.writeArrayValue(array, i, value); + } + final ResultHandle list = mc.invokeStaticMethod( + MethodDescriptor.ofMethod(Arrays.class, "asList", List.class, Object[].class), array); + mc.returnValue(list); + } + } + + generatedResource.produce(new GeneratedResourceBuildItem( + SERVICES_PREFIX + ConfigSourceProvider.class.getName(), + PROVIDER_CLASS_NAME.getBytes(StandardCharsets.UTF_8))); + } + + // XXX replace this with constant-folded service loader impl + @BuildStep + void nativeServiceProviders( + final DeploymentClassLoaderBuildItem classLoaderItem, + final BuildProducer providerProducer) throws IOException { + providerProducer.produce(new ServiceProviderBuildItem(ConfigSourceProvider.class.getName(), PROVIDER_CLASS_NAME)); + providerProducer.produce(new ServiceProviderBuildItem(ConfigProviderResolver.class.getName(), + SmallRyeConfigProviderResolver.class.getName())); + final ClassLoader classLoader = classLoaderItem.getClassLoader(); + classLoader.getResources(SERVICES_PREFIX + ConfigSourceProvider.class.getName()); + for (Class serviceClass : Arrays.asList( + ConfigSource.class, + ConfigSourceProvider.class, + Converter.class)) { + final String serviceName = serviceClass.getName(); + final Set names = ServiceUtil.classNamesNamedIn(classLoader, SERVICES_PREFIX + serviceName); + if (!names.isEmpty()) { + providerProducer.produce(new ServiceProviderBuildItem(serviceName, new ArrayList<>(names))); + } + } + } + + @BuildStep + RuntimeInitializedClassBuildItem runtimeInitializedClass() { + return new RuntimeInitializedClassBuildItem(InetRunTime.class.getName()); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java index c2cb9e9da1a6c..c2f72b3a82a83 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java @@ -1,6 +1,7 @@ package io.quarkus.deployment.steps; import java.io.InputStream; +import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; @@ -8,21 +9,20 @@ import java.util.Properties; import java.util.function.Consumer; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.BuildTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; -import io.quarkus.deployment.configuration.LeafConfigType; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.runtime.annotations.ConfigItem; public class ConfigDescriptionBuildStep { @BuildStep List createConfigDescriptions( - RunTimeConfigurationBuildItem runtimeConfig, - BuildTimeConfigurationBuildItem buildTimeConfig, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRuntimeConfig) throws Exception { + ConfigurationBuildItem config) throws Exception { Properties javadoc = new Properties(); Enumeration resources = Thread.currentThread().getContextClassLoader() .getResources("META-INF/quarkus-javadoc.properties"); @@ -32,20 +32,46 @@ List createConfigDescriptions( } } List ret = new ArrayList<>(); - processConfig(runtimeConfig.getConfigDefinition(), ret, javadoc); - processConfig(buildTimeConfig.getConfigDefinition(), ret, javadoc); - processConfig(buildTimeRuntimeConfig.getConfigDefinition(), ret, javadoc); + processConfig(config.getReadResult().getBuildTimePatternMap(), ret, javadoc); + processConfig(config.getReadResult().getBuildTimeRunTimePatternMap(), ret, javadoc); + processConfig(config.getReadResult().getRunTimePatternMap(), ret, javadoc); return ret; } - private void processConfig(ConfigDefinition configDefinition, List ret, Properties javadoc) { + private void processConfig(ConfigPatternMap patterns, List ret, + Properties javadoc) { - configDefinition.getLeafPatterns().forEach(new Consumer() { + patterns.forEach(new Consumer() { @Override - public void accept(LeafConfigType leafConfigType) { - ret.add(new ConfigDescriptionBuildItem("quarkus." + leafConfigType.getConfigKey(), - leafConfigType.getItemClass(), - leafConfigType.getDefaultValueString(), javadoc.getProperty(leafConfigType.getJavadocKey()))); + public void accept(Container node) { + Field field = node.findField(); + ConfigItem configItem = field.getAnnotation(ConfigItem.class); + final ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class); + String defaultDefault; + final Class valueClass = field.getType(); + if (valueClass == boolean.class) { + defaultDefault = "false"; + } else if (valueClass.isPrimitive() && valueClass != char.class) { + defaultDefault = "0"; + } else { + defaultDefault = null; + } + String defVal = defaultDefault; + if (configItem != null) { + final String itemDefVal = configItem.defaultValue(); + if (!itemDefVal.equals(ConfigItem.NO_DEFAULT)) { + defVal = itemDefVal; + } + } else if (configProperty != null) { + final String propDefVal = configProperty.defaultValue(); + if (!propDefVal.equals(ConfigProperty.UNCONFIGURED_VALUE)) { + defVal = propDefVal; + } + } + String javadocKey = field.getDeclaringClass().getName().replace("$", ".") + "." + field.getName(); + ret.add(new ConfigDescriptionBuildItem("quarkus." + node.getPropertyName(), + node.findEnclosingClass().getConfigurationClass(), + defVal, javadoc.getProperty(javadocKey))); } }); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java deleted file mode 100644 index e72455a90b75a..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java +++ /dev/null @@ -1,830 +0,0 @@ -package io.quarkus.deployment.steps; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.OptionalInt; -import java.util.Properties; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.UnaryOperator; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.ConfigSourceProvider; -import org.eclipse.microprofile.config.spi.Converter; -import org.graalvm.nativeimage.ImageInfo; -import org.jboss.logging.Logger; -import org.objectweb.asm.Opcodes; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.ApplicationArchive; -import io.quarkus.deployment.GeneratedClassGizmoAdaptor; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; -import io.quarkus.deployment.builditem.ArchiveRootBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; -import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; -import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; -import io.quarkus.deployment.builditem.ExtensionClassLoaderBuildItem; -import io.quarkus.deployment.builditem.GeneratedClassBuildItem; -import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationSourceBuildItem; -import io.quarkus.deployment.builditem.UnmatchedConfigBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; -import io.quarkus.deployment.configuration.ConfigPatternMap; -import io.quarkus.deployment.configuration.LeafConfigType; -import io.quarkus.deployment.recording.ObjectLoader; -import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.annotations.ConfigRoot; -import io.quarkus.runtime.configuration.AbstractRawDefaultConfigSource; -import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSource; -import io.quarkus.runtime.configuration.BuildTimeConfigFactory; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ConverterSupport; -import io.quarkus.runtime.configuration.DefaultConfigSource; -import io.quarkus.runtime.configuration.DeploymentProfileConfigSource; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.HyphenateEnumConverter; -import io.quarkus.runtime.configuration.NameIterator; -import io.quarkus.runtime.configuration.ProfileManager; -import io.quarkus.runtime.configuration.SimpleConfigurationProviderResolver; -import io.quarkus.runtime.graal.InetRunTime; -import io.smallrye.config.Converters; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; - -/** - * Setup steps for configuration purposes. - */ -public class ConfigurationSetup { - - private static final Logger log = Logger.getLogger("io.quarkus.configuration"); - - public static final String BUILD_TIME_CONFIG = "io.quarkus.runtime.generated.BuildTimeConfig"; - public static final String BUILD_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.BuildTimeConfigRoot"; - public static final String RUN_TIME_CONFIG = "io.quarkus.runtime.generated.RunTimeConfig"; - public static final String RUN_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.RunTimeConfigRoot"; - public static final String RUN_TIME_DEFAULTS = "io.quarkus.runtime.generated.RunTimeDefaultConfigSource"; - - public static final MethodDescriptor CREATE_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(RUN_TIME_CONFIG, - "getRunTimeConfiguration", void.class); - public static final MethodDescriptor ECS_EXPAND_VALUE = MethodDescriptor.ofMethod(ExpandingConfigSource.class, - "expandValue", - String.class, String.class, ExpandingConfigSource.Cache.class); - - private static final FieldDescriptor RUN_TIME_CONFIG_FIELD = FieldDescriptor.of(RUN_TIME_CONFIG, "runConfig", - RUN_TIME_CONFIG_ROOT); - private static final FieldDescriptor BUILD_TIME_CONFIG_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "buildConfig", - BUILD_TIME_CONFIG_ROOT); - private static final FieldDescriptor CONVERTERS_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "converters", - Converter[].class); - - private static final MethodDescriptor NI_HAS_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "hasNext", boolean.class); - private static final MethodDescriptor NI_NEXT_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "nextSegmentEquals", - boolean.class, String.class); - private static final MethodDescriptor NI_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); - private static final MethodDescriptor ITR_HAS_NEXT = MethodDescriptor.ofMethod(Iterator.class, "hasNext", boolean.class); - private static final MethodDescriptor ITR_NEXT = MethodDescriptor.ofMethod(Iterator.class, "next", Object.class); - private static final MethodDescriptor C_GET_IMPLICIT_CONVERTER = MethodDescriptor.ofMethod(Converters.class, - "getImplicitConverter", Converter.class, Class.class); - private static final MethodDescriptor CPR_SET_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "setInstance", void.class, ConfigProviderResolver.class); - private static final MethodDescriptor CPR_REGISTER_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "registerConfig", void.class, Config.class, ClassLoader.class); - private static final MethodDescriptor CPR_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "instance", ConfigProviderResolver.class); - private static final MethodDescriptor SCPR_CONSTRUCT = MethodDescriptor - .ofConstructor(SimpleConfigurationProviderResolver.class); - private static final MethodDescriptor SRCB_BUILD = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, "build", - Config.class); - private static final MethodDescriptor SRCB_WITH_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withConverter", ConfigBuilder.class, Class.class, int.class, Converter.class); - private static final MethodDescriptor SRCB_WITH_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withSources", ConfigBuilder.class, ConfigSource[].class); - private static final MethodDescriptor SRCB_ADD_DEFAULT_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "addDefaultSources", ConfigBuilder.class); - private static final MethodDescriptor SRCB_ADD_DISCOVERED_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "addDiscoveredSources", ConfigBuilder.class); - private static final MethodDescriptor SRCB_CONSTRUCT = MethodDescriptor.ofConstructor(SmallRyeConfigBuilder.class); - private static final MethodDescriptor II_IN_IMAGE_RUN = MethodDescriptor.ofMethod(ImageInfo.class, "inImageRuntimeCode", - boolean.class); - private static final MethodDescriptor SRCB_WITH_WRAPPER = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withWrapper", SmallRyeConfigBuilder.class, UnaryOperator.class); - - private static final MethodDescriptor BTCF_GET_CONFIG_SOURCE = MethodDescriptor.ofMethod(BuildTimeConfigFactory.class, - "getBuildTimeConfigSource", ConfigSource.class); - private static final MethodDescriptor ECS_CACHE_CONSTRUCT = MethodDescriptor - .ofConstructor(ExpandingConfigSource.Cache.class); - private static final MethodDescriptor ECS_WRAPPER = MethodDescriptor.ofMethod(ExpandingConfigSource.class, "wrapper", - UnaryOperator.class, ExpandingConfigSource.Cache.class); - - private static final MethodDescriptor PROFILE_WRAPPER = MethodDescriptor.ofMethod(DeploymentProfileConfigSource.class, - "wrapper", - UnaryOperator.class); - - private static final MethodDescriptor RTD_CTOR = MethodDescriptor.ofConstructor(RUN_TIME_DEFAULTS); - private static final MethodDescriptor RTD_GET_VALUE = MethodDescriptor.ofMethod(RUN_TIME_DEFAULTS, "getValue", String.class, - NameIterator.class); - private static final MethodDescriptor ARDCS_CTOR = MethodDescriptor.ofConstructor(AbstractRawDefaultConfigSource.class); - - private static final MethodDescriptor CS_POPULATE_CONVERTERS = MethodDescriptor.ofMethod(ConverterSupport.class, - "populateConverters", void.class, ConfigBuilder.class); - - private static final MethodDescriptor SET_RUNTIME_DEFAULT_PROFILE = MethodDescriptor.ofMethod(ProfileManager.class, - "setRuntimeDefaultProfile", void.class, String.class); - private static final MethodDescriptor HYPHENATED_ENUM_CONVERTER_CTOR = MethodDescriptor - .ofConstructor(HyphenateEnumConverter.class, Class.class); - private static final MethodDescriptor CU_EXPLICIT_RUNTIME_CONVERTER = MethodDescriptor.ofMethod(ConfigUtils.class, - "populateExplicitRuntimeConverter", void.class, Class.class, Class.class, Converter.class); - - private static final String[] NO_STRINGS = new String[0]; - - public ConfigurationSetup() { - } - - /** - * Run before anything that consumes configuration; sets up the main configuration definition instance. - * - * @param runTimeConfigItem the run time config item - * @param buildTimeRunTimeConfigItem the build time/run time fixed config item - * @param resourceConsumer - * @param niResourceConsumer - * @param runTimeDefaultConsumer - * @param unmatchedConfigBuildItem the build item holding the unmatched config keys - * @param extensionClassLoaderBuildItem the extension class loader build item - * @param archiveRootBuildItem the application archive root - * @throws IOException - */ - @BuildStep - public void initializeConfiguration( - RunTimeConfigurationBuildItem runTimeConfigItem, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRunTimeConfigItem, - Consumer resourceConsumer, - Consumer niResourceConsumer, - Consumer runTimeDefaultConsumer, - UnmatchedConfigBuildItem unmatchedConfigBuildItem, - ExtensionClassLoaderBuildItem extensionClassLoaderBuildItem, - ArchiveRootBuildItem archiveRootBuildItem) throws IOException { - - SmallRyeConfig src = (SmallRyeConfig) ConfigProvider.getConfig(); - - final ConfigDefinition runTimeConfig = runTimeConfigItem.getConfigDefinition(); - final ConfigDefinition buildTimeRunTimeConfig = buildTimeRunTimeConfigItem.getConfigDefinition(); - - // store the expanded values from the build - final byte[] bytes; - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - try (OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { - final Properties properties = new Properties(); - properties.putAll(buildTimeRunTimeConfig.getLoadedProperties()); - properties.store(osw, "This file is generated from captured build-time values; do not edit this file manually"); - } - os.flush(); - bytes = os.toByteArray(); - } - resourceConsumer.accept( - new GeneratedResourceBuildItem(BuildTimeConfigFactory.BUILD_TIME_CONFIG_NAME, bytes)); - niResourceConsumer.accept( - new NativeImageResourceBuildItem(BuildTimeConfigFactory.BUILD_TIME_CONFIG_NAME)); - - // produce defaults for user-provided config - - final Set unmatched = new HashSet<>(); - unmatched.addAll(unmatchedConfigBuildItem.getSet()); - unmatched.addAll(runTimeConfig.getLoadedProperties().keySet()); - final boolean old = ExpandingConfigSource.setExpanding(false); - try { - for (String propName : unmatched) { - runTimeDefaultConsumer - .accept(new RunTimeConfigurationDefaultBuildItem(propName, - src.getOptionalValue(propName, String.class).orElse(""))); - } - } finally { - ExpandingConfigSource.setExpanding(old); - } - - } - - @BuildStep - public void addDiscoveredSources(ApplicationArchivesBuildItem archives, Consumer providerConsumer) - throws IOException { - final Collection sources = new LinkedHashSet<>(); - final Collection sourceProviders = new LinkedHashSet<>(); - for (ApplicationArchive archive : archives.getAllApplicationArchives()) { - Path childPath = archive.getChildPath("META-INF/services/" + ConfigSource.class.getName()); - if (childPath != null) { - sources.addAll(ServiceUtil.classNamesNamedIn(childPath)); - } - childPath = archive.getChildPath("META-INF/services/" + ConfigSourceProvider.class.getName()); - if (childPath != null) { - sourceProviders.addAll(ServiceUtil.classNamesNamedIn(childPath)); - } - } - if (sources.size() > 0) { - providerConsumer.accept(new ServiceProviderBuildItem(ConfigSource.class.getName(), sources.toArray(NO_STRINGS))); - } - if (sourceProviders.size() > 0) { - providerConsumer.accept( - new ServiceProviderBuildItem(ConfigSourceProvider.class.getName(), sourceProviders.toArray(NO_STRINGS))); - } - } - - /** - * Add a config sources for {@code application.properties}. - */ - @BuildStep - void setUpConfigFile(BuildProducer configSourceConsumer) { - configSourceConsumer.produce(new RunTimeConfigurationSourceBuildItem( - ApplicationPropertiesConfigSource.InJar.class.getName(), OptionalInt.empty())); - configSourceConsumer.produce(new RunTimeConfigurationSourceBuildItem( - ApplicationPropertiesConfigSource.InFileSystem.class.getName(), OptionalInt.empty())); - } - - /** - * Write the default run time configuration. - */ - @BuildStep - RunTimeConfigurationSourceBuildItem writeDefaults( - List defaults, - Consumer resourceConsumer, - Consumer niResourceConsumer) throws IOException { - final Properties properties = new Properties(); - for (RunTimeConfigurationDefaultBuildItem item : defaults) { - final String key = item.getKey(); - final String value = item.getValue(); - final String existing = properties.getProperty(key); - if (existing != null && !existing.equals(value)) { - log.warnf( - "Two conflicting default values were specified for configuration key \"%s\": \"%s\" and \"%s\" (using \"%2$s\")", - key, - existing, - value); - } else { - properties.setProperty(key, value); - } - } - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - try (OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { - properties.store(osw, "This is the generated set of default configuration values"); - osw.flush(); - resourceConsumer.accept( - new GeneratedResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME, os.toByteArray())); - niResourceConsumer.accept( - new NativeImageResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME)); - } - } - return new RunTimeConfigurationSourceBuildItem(DefaultConfigSource.class.getName(), OptionalInt.empty()); - } - - /** - * Generate the bytecode to load configuration objects at static init and run time. - * - * @param runTimeConfigItem the config build item - * @param classConsumer the consumer of generated classes - * @param runTimeInitConsumer the consumer of runtime init classes - */ - @BuildStep - void finalizeConfigLoader( - RunTimeConfigurationBuildItem runTimeConfigItem, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRunTimeConfigItem, - BuildProducer classConsumer, - Consumer runTimeInitConsumer, - Consumer objectLoaderConsumer, - List configTypeItems, - List runTimeSources) { - final ClassOutput classOutput = new GeneratedClassGizmoAdaptor(classConsumer, true); - - // General run time setup - - AccessorFinder accessorFinder = new AccessorFinder(); - - final ConfigDefinition runTimeConfigDef = runTimeConfigItem.getConfigDefinition(); - final ConfigPatternMap runTimePatterns = runTimeConfigDef.getLeafPatterns(); - - runTimeConfigDef.generateConfigRootClass(classOutput, accessorFinder); - - final ConfigDefinition buildTimeConfigDef = buildTimeRunTimeConfigItem.getConfigDefinition(); - final ConfigPatternMap buildTimePatterns = buildTimeConfigDef.getLeafPatterns(); - - buildTimeConfigDef.generateConfigRootClass(classOutput, accessorFinder); - - // Traverse all known run-time config types and ensure we have converters for them when image building runs - // This code is specific to native image and run time config, because the build time config is read during static init - - final HashSet> encountered = new HashSet<>(); - final ArrayList> configTypes = new ArrayList<>(); - for (ConfigurationTypeBuildItem item : configTypeItems) { - configTypes.add(item.getValueType()); - } - - for (LeafConfigType item : runTimePatterns) { - final Class typeClass = item.getItemClass(); - if (!typeClass.isPrimitive() && encountered.add(typeClass) - && Converters.getImplicitConverter(typeClass) != null) { - configTypes.add(typeClass); - } - } - - // stability - configTypes.sort(Comparator.comparing(Class::getName)); - int converterCnt = configTypes.size(); - - // Build time configuration class, also holds converters - try (final ClassCreator cc = new ClassCreator(classOutput, BUILD_TIME_CONFIG, null, Object.class.getName())) { - // field to stash converters into - cc.getFieldCreator(CONVERTERS_FIELD).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - // holder for the build-time configuration - cc.getFieldCreator(BUILD_TIME_CONFIG_FIELD) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); - - // static init block - try (MethodCreator clinit = cc.getMethodCreator("", void.class)) { - clinit.setModifiers(Opcodes.ACC_STATIC); - // set default profile to build profile - clinit.invokeStaticMethod(SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); - - // make implicit converters available to native image run time - final BranchResult inImageBuild = clinit.ifNonZero(clinit - .invokeStaticMethod(MethodDescriptor.ofMethod(ImageInfo.class, "inImageBuildtimeCode", boolean.class))); - try (BytecodeCreator yes = inImageBuild.trueBranch()) { - - final ResultHandle array = yes.newArray(Converter.class, yes.load(converterCnt)); - for (int i = 0; i < converterCnt; i++) { - yes.writeArrayValue(array, i, - yes.invokeStaticMethod(C_GET_IMPLICIT_CONVERTER, yes.loadClass(configTypes.get(i)))); - } - yes.writeStaticField(CONVERTERS_FIELD, array); - } - try (BytecodeCreator no = inImageBuild.falseBranch()) { - no.writeStaticField(CONVERTERS_FIELD, no.loadNull()); - } - - // create build time configuration object - - final ResultHandle builder = clinit.newInstance(SRCB_CONSTRUCT); - // todo: custom build time converters - final ResultHandle array = clinit.newArray(ConfigSource[].class, clinit.load(1)); - clinit.writeArrayValue(array, 0, clinit.invokeStaticMethod(BTCF_GET_CONFIG_SOURCE)); - clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, builder, array); - // add default sources, which are only visible during static init - clinit.invokeVirtualMethod(SRCB_ADD_DEFAULT_SOURCES, builder); - - // create the actual config object - final ResultHandle config = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, builder), - SmallRyeConfig.class); - - // create the config root - clinit.writeStaticField(BUILD_TIME_CONFIG_FIELD, clinit - .newInstance(MethodDescriptor.ofConstructor(BUILD_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - - // write out the parsing for the stored build time config - writeParsing(cc, clinit, config, null, buildTimePatterns); - - clinit.returnValue(null); - } - } - - // Run time configuration class - try (final ClassCreator cc = new ClassCreator(classOutput, RUN_TIME_CONFIG, null, Object.class.getName())) { - // holder for the run-time configuration - cc.getFieldCreator(RUN_TIME_CONFIG_FIELD) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); - - // config object initialization - try (MethodCreator carc = cc.getMethodCreator(ConfigurationSetup.CREATE_RUN_TIME_CONFIG)) { - carc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); - - // create run time configuration object - final ResultHandle builder = carc.newInstance(SRCB_CONSTRUCT); - carc.invokeVirtualMethod(SRCB_ADD_DEFAULT_SOURCES, builder); - - // discovered sources - carc.invokeVirtualMethod(SRCB_ADD_DISCOVERED_SOURCES, builder); - - // custom run time sources - final int size = runTimeSources.size(); - if (size > 0) { - final ResultHandle arrayHandle = carc.newArray(ConfigSource[].class, carc.load(size)); - for (int i = 0; i < size; i++) { - final RunTimeConfigurationSourceBuildItem source = runTimeSources.get(i); - final OptionalInt priority = source.getPriority(); - final ResultHandle val; - if (priority.isPresent()) { - val = carc.newInstance(MethodDescriptor.ofConstructor(source.getClassName(), int.class), - carc.load(priority.getAsInt())); - } else { - val = carc.newInstance(MethodDescriptor.ofConstructor(source.getClassName())); - } - carc.writeArrayValue(arrayHandle, i, val); - } - carc.invokeVirtualMethod( - SRCB_WITH_SOURCES, - builder, - arrayHandle); - } - // default value source - final ResultHandle defaultSourceArray = carc.newArray(ConfigSource[].class, carc.load(1)); - carc.writeArrayValue(defaultSourceArray, 0, carc.newInstance(RTD_CTOR)); - carc.invokeVirtualMethod(SRCB_WITH_SOURCES, builder, defaultSourceArray); - - // custom run time converters - carc.invokeStaticMethod(CS_POPULATE_CONVERTERS, builder); - - // cache explicit converts and make them available during runtime - for (LeafConfigType item : runTimePatterns) { - final Class typeClass = item.getItemClass(); - Class> itemConverterClass = item.getConverterClass(); - if (itemConverterClass == null) { - continue; - } - - ResultHandle typeClassHandle = carc.loadClass(typeClass); - final ResultHandle converter; - if (HyphenateEnumConverter.class.equals(itemConverterClass)) { - converter = carc.newInstance(HYPHENATED_ENUM_CONVERTER_CTOR, typeClassHandle); - } else { - converter = carc.newInstance(MethodDescriptor.ofConstructor(itemConverterClass)); - } - - carc.invokeStaticMethod(CU_EXPLICIT_RUNTIME_CONVERTER, typeClassHandle, carc.loadClass(itemConverterClass), - converter); - } - - // property expansion - final ResultHandle cache = carc.newInstance(ECS_CACHE_CONSTRUCT); - ResultHandle wrapper = carc.invokeStaticMethod(ECS_WRAPPER, cache); - carc.invokeVirtualMethod(SRCB_WITH_WRAPPER, builder, wrapper); - - //profiles - wrapper = carc.invokeStaticMethod(PROFILE_WRAPPER); - carc.invokeVirtualMethod(SRCB_WITH_WRAPPER, builder, wrapper); - - // write out loader for converter types - final BranchResult imgRun = carc.ifNonZero(carc.invokeStaticMethod(II_IN_IMAGE_RUN)); - try (BytecodeCreator inImageRun = imgRun.trueBranch()) { - final ResultHandle array = inImageRun.readStaticField(CONVERTERS_FIELD); - for (int i = 0; i < converterCnt; i++) { - // implicit converters will have a priority of 100. - inImageRun.invokeVirtualMethod( - SRCB_WITH_CONVERTER, - builder, - inImageRun.loadClass(configTypes.get(i)), - inImageRun.load(100), - inImageRun.readArrayValue(array, i)); - } - } - - // Build the config - - final ResultHandle config = carc.checkCast(carc.invokeVirtualMethod(SRCB_BUILD, builder), SmallRyeConfig.class); - - // IMPL NOTE: we do invoke ConfigProviderResolver.setInstance() in RUNTIME_INIT when an app starts, but ConfigProvider only obtains the - // resolver once when initializing ConfigProvider.INSTANCE. That is why we store the current Config as a static field on the - // SimpleConfigurationProviderResolver - carc.invokeStaticMethod(CPR_SET_INSTANCE, carc.newInstance(SCPR_CONSTRUCT)); - carc.invokeVirtualMethod(CPR_REGISTER_CONFIG, carc.invokeStaticMethod(CPR_INSTANCE), config, carc.loadNull()); - - // create the config root - carc.writeStaticField(RUN_TIME_CONFIG_FIELD, - carc.newInstance(MethodDescriptor.ofConstructor(RUN_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - - writeParsing(cc, carc, config, cache, runTimePatterns); - - carc.returnValue(null); - } - } - - // now construct the default values class - try (ClassCreator cc = ClassCreator - .builder() - .classOutput(classOutput) - .className(RUN_TIME_DEFAULTS) - .superClass(AbstractRawDefaultConfigSource.class) - .build()) { - - // constructor - try (MethodCreator ctor = cc.getMethodCreator(RTD_CTOR)) { - ctor.setModifiers(Opcodes.ACC_PUBLIC); - ctor.invokeSpecialMethod(ARDCS_CTOR, ctor.getThis()); - ctor.returnValue(null); - } - - try (MethodCreator gv = cc.getMethodCreator(RTD_GET_VALUE)) { - final ResultHandle nameIter = gv.getMethodParam(0); - // if (! nameIter.hasNext()) return null; - gv.ifNonZero(gv.invokeVirtualMethod(NI_HAS_NEXT, nameIter)).falseBranch().returnValue(gv.loadNull()); - // if (! nameIter.nextSegmentEquals("quarkus")) return null; - gv.ifNonZero(gv.invokeVirtualMethod(NI_NEXT_EQUALS, nameIter, gv.load("quarkus"))).falseBranch() - .returnValue(gv.loadNull()); - // nameIter.next(); // skip "quarkus" - gv.invokeVirtualMethod(NI_NEXT, nameIter); - // return getValue_xx(nameIter); - gv.returnValue(gv.invokeVirtualMethod( - generateGetValue(cc, runTimePatterns, new StringBuilder("getValue"), new HashMap<>()), gv.getThis(), - nameIter)); - } - } - - objectLoaderConsumer.accept(new BytecodeRecorderObjectLoaderBuildItem(new ObjectLoader() { - public ResultHandle load(final BytecodeCreator body, final Object obj, final boolean staticInit) { - if (!canHandleObject(obj, staticInit)) { - return null; - } - boolean buildTime = false; - ConfigDefinition.RootInfo rootInfo = runTimeConfigDef.getInstanceInfo(obj); - if (rootInfo == null) { - rootInfo = buildTimeConfigDef.getInstanceInfo(obj); - buildTime = true; - } - final FieldDescriptor fieldDescriptor = rootInfo.getFieldDescriptor(); - final ResultHandle configRoot = body - .readStaticField(buildTime ? BUILD_TIME_CONFIG_FIELD : RUN_TIME_CONFIG_FIELD); - return body.readInstanceField(fieldDescriptor, configRoot); - } - - @Override - public boolean canHandleObject(Object obj, boolean staticInit) { - boolean buildTime = false; - ConfigDefinition.RootInfo rootInfo = runTimeConfigDef.getInstanceInfo(obj); - if (rootInfo == null) { - rootInfo = buildTimeConfigDef.getInstanceInfo(obj); - buildTime = true; - } - if (rootInfo == null || staticInit && !buildTime) { - final Class objClass = obj.getClass(); - if (objClass.isAnnotationPresent(ConfigRoot.class)) { - String msg = String.format( - "You are trying to use a ConfigRoot[%s] at static initialization time", - objClass.getName()); - throw new IllegalStateException(msg); - } - return false; - } - return true; - } - })); - - runTimeInitConsumer.accept(new RuntimeInitializedClassBuildItem(RUN_TIME_CONFIG)); - } - - private MethodDescriptor generateGetValue(final ClassCreator cc, final ConfigPatternMap keyMap, - final StringBuilder methodName, final Map cache) { - final String methodNameStr = methodName.toString(); - final MethodDescriptor existing = cache.get(methodNameStr); - if (existing != null) { - return existing; - } - try (MethodCreator body = cc.getMethodCreator(methodNameStr, String.class, NameIterator.class)) { - body.setModifiers(Opcodes.ACC_PROTECTED); - final ResultHandle nameIter = body.getMethodParam(0); - final LeafConfigType matched = keyMap.getMatched(); - // if (! keyIter.hasNext()) { - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, nameIter)).falseBranch()) { - if (matched != null) { - // (exact match generated code) - matchedBody.returnValue( - matchedBody.load(matched.getDefaultValueString())); - } else { - // return; - matchedBody.returnValue(matchedBody.loadNull()); - } - } - // } - // branches for each next-string - boolean hasWildCard = false; - final Iterable names = keyMap.childNames(); - for (String name : names) { - if (name.equals(ConfigPatternMap.WILD_CARD)) { - hasWildCard = true; - } else { - // TODO: string switch - // if (keyIter.nextSegmentEquals(name)) { - try (BytecodeCreator nameMatched = body - .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, nameIter, body.load(name))).trueBranch()) { - // keyIter.next(); - nameMatched.invokeVirtualMethod(NI_NEXT, nameIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append(name); - // result = this.getValue_xxx(nameIter); - final ResultHandle result = nameMatched.invokeVirtualMethod( - generateGetValue(cc, keyMap.getChild(name), methodName, cache), nameMatched.getThis(), - nameIter); - methodName.setLength(length); - // return result; - nameMatched.returnValue(result); - } - // } - } - } - if (hasWildCard) { - // consume and parse - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, nameIter)) - .trueBranch()) { - // keyIter.next(); - matchedBody.invokeVirtualMethod(NI_NEXT, nameIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append("wildcard"); - // result = this.getValue_xxx(nameIter); - final ResultHandle result = matchedBody.invokeVirtualMethod( - generateGetValue(cc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName, cache), - matchedBody.getThis(), nameIter); - methodName.setLength(length); - // return result; - matchedBody.returnValue(result); - } - } - // it's not found - body.returnValue(body.loadNull()); - final MethodDescriptor md = body.getMethodDescriptor(); - cache.put(methodNameStr, md); - return md; - } - } - - private void writeParsing(final ClassCreator cc, final BytecodeCreator body, final ResultHandle config, - final ResultHandle cache, final ConfigPatternMap keyMap) { - // setup - // Iterable iterable = config.getPropertyNames(); - final ResultHandle iterable = body.invokeVirtualMethod( - MethodDescriptor.ofMethod(SmallRyeConfig.class, "getPropertyNames", Iterable.class), config); - // Iterator iterator = iterable.iterator(); - final ResultHandle iterator = body - .invokeInterfaceMethod(MethodDescriptor.ofMethod(Iterable.class, "iterator", Iterator.class), iterable); - - // loop: { - try (BytecodeCreator loop = body.createScope()) { - // if (iterator.hasNext()) - final BranchResult ifHasNext = loop.ifNonZero(loop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)); - // { - try (BytecodeCreator hasNext = ifHasNext.trueBranch()) { - // key = iterator.next(); - final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); - // NameIterator keyIter = new NameIterator(key); - final ResultHandle keyIter = hasNext - .newInstance(MethodDescriptor.ofConstructor(NameIterator.class, String.class), key); - // if (! keyIter.hasNext()) continue loop; - hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(loop); - // if (! keyIter.nextSegmentEquals("quarkus")) continue loop; - hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))).falseBranch() - .continueScope(loop); - // keyIter.next(); // skip "quarkus" - hasNext.invokeVirtualMethod(NI_NEXT, keyIter); - // parse(config, cache, keyIter); - or - parse(config, keyIter); - final ResultHandle[] args; - final boolean expand = cache != null; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - hasNext.invokeStaticMethod( - generateParserBody(cc, keyMap, new StringBuilder("parseKey"), new HashMap<>(), expand), - args); - // continue loop; - hasNext.continueScope(loop); - } - // } - } - // } - body.returnValue(body.loadNull()); - } - - private MethodDescriptor generateParserBody(final ClassCreator cc, final ConfigPatternMap keyMap, - final StringBuilder methodName, final Map parseMethodCache, final boolean expand) { - final String methodNameStr = methodName.toString(); - final MethodDescriptor existing = parseMethodCache.get(methodNameStr); - if (existing != null) { - return existing; - } - final Class[] argTypes; - if (expand) { - argTypes = new Class[] { SmallRyeConfig.class, ExpandingConfigSource.Cache.class, NameIterator.class }; - } else { - argTypes = new Class[] { SmallRyeConfig.class, NameIterator.class }; - } - try (MethodCreator body = cc.getMethodCreator(methodName.toString(), void.class, - argTypes)) { - body.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); - final ResultHandle config = body.getMethodParam(0); - final ResultHandle cache = expand ? body.getMethodParam(1) : null; - final ResultHandle keyIter = expand ? body.getMethodParam(2) : body.getMethodParam(1); - final LeafConfigType matched = keyMap.getMatched(); - // if (! keyIter.hasNext()) { - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch()) { - if (matched != null) { - // (exact match generated code) - matched.generateAcceptConfigurationValue(matchedBody, keyIter, cache, config); - } else { - // todo: unknown name warning goes here - } - // return; - matchedBody.returnValue(null); - } - // } - // branches for each next-string - boolean hasWildCard = false; - final Iterable names = keyMap.childNames(); - for (String name : names) { - if (name.equals(ConfigPatternMap.WILD_CARD)) { - hasWildCard = true; - } else { - // TODO: string switch - // if (keyIter.nextSegmentEquals(name)) { - try (BytecodeCreator nameMatched = body - .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))).trueBranch()) { - // keyIter.next(); - nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append(name); - final ResultHandle[] args; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - nameMatched.invokeStaticMethod( - generateParserBody(cc, keyMap.getChild(name), methodName, parseMethodCache, expand), - args); - methodName.setLength(length); - // return; - nameMatched.returnValue(null); - } - // } - } - } - if (hasWildCard) { - // consume and parse - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) - .trueBranch()) { - // keyIter.next(); - matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append("wildcard"); - final ResultHandle[] args; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - matchedBody.invokeStaticMethod( - generateParserBody(cc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName, parseMethodCache, - expand), - args); - methodName.setLength(length); - // return; - matchedBody.returnValue(null); - } - } - // todo: unknown name warning goes here - body.returnValue(null); - final MethodDescriptor md = body.getMethodDescriptor(); - parseMethodCache.put(methodNameStr, md); - return md; - } - } - - @BuildStep - void writeDefaultConfiguration( - - ) { - - } - - @BuildStep - RuntimeInitializedClassBuildItem runtimeInitializedClass() { - return new RuntimeInitializedClassBuildItem(InetRunTime.class.getName()); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index a7db493d10a0f..820113ce1bc2f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -5,7 +5,9 @@ import java.io.File; import java.lang.reflect.Modifier; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -18,6 +20,8 @@ import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem; @@ -25,13 +29,17 @@ import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.deployment.recording.BytecodeRecorderImpl; import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; @@ -65,7 +73,29 @@ MainClassBuildItem build(List staticInitTasks, List loaders, BuildProducer generatedClass, LaunchModeBuildItem launchMode, - ApplicationInfoBuildItem applicationInfo) { + ApplicationInfoBuildItem applicationInfo, + List runTimeDefaults, + List typeItems, + ConfigurationBuildItem configItem) { + + BuildTimeConfigurationReader.ReadResult readResult = configItem.getReadResult(); + Map defaults = new HashMap<>(); + for (RunTimeConfigurationDefaultBuildItem item : runTimeDefaults) { + if (defaults.putIfAbsent(item.getKey(), item.getValue()) != null) { + throw new IllegalStateException("More than one default value for " + item.getKey() + " was produced"); + } + } + List> additionalConfigTypes = typeItems.stream().map(ConfigurationTypeBuildItem::getValueType) + .collect(Collectors.toList()); + + ClassOutput classOutput = new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); + } + }; + + RunTimeConfigurationGenerator.generate(readResult, classOutput, defaults, additionalConfigTypes); appClassNameProducer.produce(new ApplicationClassNameBuildItem(APP_CLASS)); @@ -89,6 +119,10 @@ MainClassBuildItem build(List staticInitTasks, } mv.invokeStaticMethod(MethodDescriptor.ofMethod(Timing.class, "staticInitStarted", void.class)); + + // ensure that the config class is initialized + mv.invokeStaticMethod(RunTimeConfigurationGenerator.C_ENSURE_INITIALIZED); + ResultHandle startupContext = mv.newInstance(ofConstructor(StartupContext.class)); mv.writeStaticField(scField.getFieldDescriptor(), startupContext); TryBlock tryBlock = mv.tryBlock(); @@ -170,7 +204,7 @@ MainClassBuildItem build(List staticInitTasks, tryBlock = mv.tryBlock(); // Load the run time configuration - tryBlock.invokeStaticMethod(ConfigurationSetup.CREATE_RUN_TIME_CONFIG); + tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG); for (MainBytecodeRecorderBuildItem holder : mainMethod) { final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java index 0a65c6f706d99..3381cc95ebc7b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java @@ -1,10 +1,16 @@ package io.quarkus.deployment.util; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -74,6 +80,24 @@ public static Class rawTypeOf(final Type type) { } } + private static final Class[] NO_CLASSES = new Class[0]; + + public static Class[] rawTypesOfDestructive(final Type[] types) { + if (types.length == 0) { + return NO_CLASSES; + } + Type t; + Class r; + for (int i = 0; i < types.length; i++) { + t = types[i]; + r = rawTypeOf(t); + if (r != t) { + types[i] = r; + } + } + return Arrays.copyOf(types, types.length, Class[].class); + } + public static Type typeOfParameter(final Type type, final int paramIdx) { if (type instanceof ParameterizedType) { return ((ParameterizedType) type).getActualTypeArguments()[paramIdx]; @@ -94,6 +118,26 @@ public static void setFieldVal(Field field, Object obj, Object value) { } } + public static T newInstance(Class clazz) { + try { + return clazz.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } catch (NoSuchMethodException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + public static InstantiationError toError(final InstantiationException e) { final InstantiationError error = new InstantiationError(e.getMessage()); error.setStackTrace(e.getStackTrace()); @@ -117,4 +161,27 @@ public static NoSuchFieldError toError(final NoSuchFieldException e) { error.setStackTrace(e.getStackTrace()); return error; } + + public static UndeclaredThrowableException unwrapInvocationTargetException(InvocationTargetException original) { + try { + throw original.getCause(); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable t) { + return new UndeclaredThrowableException(t); + } + } + + public static IllegalArgumentException reportError(AnnotatedElement e, String fmt, Object... args) { + if (e instanceof Member) { + return new IllegalArgumentException( + String.format(fmt, args) + " at " + e + " of " + ((Member) e).getDeclaringClass()); + } else if (e instanceof Parameter) { + return new IllegalArgumentException( + String.format(fmt, args) + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " + + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); + } else { + return new IllegalArgumentException(String.format(fmt, args) + " at " + e); + } + } } diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index 6885d2e8d9be1..fdd2240918091 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -8,12 +8,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.CodeSource; @@ -249,6 +252,7 @@ public void writeClass(boolean applicationClass, String className, byte[] data) debugPath.mkdir(); } File classFile = new File(debugPath, dotName + ".class"); + classFile.getParentFile().mkdirs(); try (FileOutputStream classWriter = new FileOutputStream(classFile)) { classWriter.write(data); } @@ -276,6 +280,25 @@ public void writeClass(boolean applicationClass, String className, byte[] data) } } + @Override + public Writer writeSource(final String className) { + if (DEBUG_CLASSES_DIR != null) { + try { + File debugPath = new File(DEBUG_CLASSES_DIR); + if (!debugPath.exists()) { + debugPath.mkdir(); + } + File classFile = new File(debugPath, className + ".zig"); + classFile.getParentFile().mkdirs(); + log.infof("Wrote %s", classFile.getAbsolutePath()); + return new OutputStreamWriter(new FileOutputStream(classFile), StandardCharsets.UTF_8); + } catch (Throwable t) { + t.printStackTrace(); + } + } + return ClassOutput.super.writeSource(className); + } + @Override public void setTransformers(Map>> functions) { this.bytecodeTransformers = functions; diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java index bae45a1b5e308..5c1b299086664 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java @@ -32,6 +32,7 @@ import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; +import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.runtime.Application; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ProfileManager; @@ -133,12 +134,26 @@ public void run() { } final Application application; - Class appClass = loader - .loadClass(result.consume(ApplicationClassNameBuildItem.class).getClassName()) - .asSubclass(Application.class); + final String className = result.consume(ApplicationClassNameBuildItem.class).getClassName(); ClassLoader old = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(loader); + Class appClass; + try { + // force init here + appClass = Class.forName(className, true, loader).asSubclass(Application.class); + } catch (Throwable t) { + // todo: dev mode expects run time config to be available immediately even if static init didn't complete. + try { + final Class configClass = Class.forName(RunTimeConfigurationGenerator.CONFIG_CLASS_NAME, true, + loader); + configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG.getName()) + .invoke(null); + } catch (Throwable t2) { + t.addSuppressed(t2); + } + throw t; + } application = appClass.newInstance(); application.start(null); } finally { diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java index 47e6b773f4b00..04ea9a369c325 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java @@ -21,6 +21,7 @@ import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import io.quarkus.builder.BuildChainBuilder; @@ -32,7 +33,7 @@ import io.quarkus.runner.RuntimeRunner; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.Timing; -import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; /** * The main entry point for the dev mojo execution @@ -262,7 +263,13 @@ public void stop() { Thread.currentThread().setContextClassLoader(old); } } - SmallRyeConfigProviderResolver.instance().releaseConfig(SmallRyeConfigProviderResolver.instance().getConfig()); + QuarkusConfigFactory.setConfig(null); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } DevModeMain.runner = null; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index 0db6079bb6a1e..e1d5581b56f60 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -19,7 +19,9 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import io.quarkus.annotation.processor.Constants; @@ -261,6 +263,8 @@ private List recordConfigItemsFromConfigGroup(ConfigPhase configP private String simpleTypeToString(TypeMirror typeMirror) { if (typeMirror.getKind().isPrimitive()) { return typeMirror.toString(); + } else if (typeMirror.getKind() == TypeKind.ARRAY) { + return "list of " + simpleTypeToString(((ArrayType) typeMirror).getComponentType()); } final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Application.java b/core/runtime/src/main/java/io/quarkus/runtime/Application.java index 8cc773f761324..b51146fc4dd0f 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/Application.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/Application.java @@ -4,7 +4,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.LockSupport; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.graalvm.nativeimage.ImageInfo; import org.wildfly.common.Assert; import org.wildfly.common.lock.Locks; @@ -41,12 +40,6 @@ public abstract class Application { private volatile boolean shutdownRequested; private static volatile Application currentApplication; - /** - * The generated config code will install a new resolver, we save the original one here and make sure - * to restore it on shutdown. - */ - private final static ConfigProviderResolver originalResolver = ConfigProviderResolver.instance(); - /** * Construct a new instance. */ @@ -161,7 +154,6 @@ public final void stop() { doStop(); } finally { currentApplication = null; - ConfigProviderResolver.setInstance(originalResolver); stateLock.lock(); try { state = ST_STOPPED; diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java deleted file mode 100644 index 6f862d1c18fd9..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.io.IOError; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Properties; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -import io.smallrye.config.PropertiesConfigSource; - -/** - * - */ -public final class BuildTimeConfigFactory { - - public static final String BUILD_TIME_CONFIG_NAME = "META-INF/build-config.properties"; - - private BuildTimeConfigFactory() { - } - - public static ConfigSource getBuildTimeConfigSource() { - Properties properties = new Properties(); - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - final Enumeration resources = classLoader.getResources(BUILD_TIME_CONFIG_NAME); - if (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (InputStream is = url.openStream()) { - if (is != null) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - properties.load(isr); - } - } - } - } - return new PropertiesConfigSource(properties, "Build time configuration"); - } catch (IOException e) { - throw new IOError(e); - } - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java new file mode 100644 index 0000000000000..f92291dae3156 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -0,0 +1,79 @@ +package io.quarkus.runtime.configuration; + +import java.util.NoSuchElementException; + +import org.graalvm.nativeimage.ImageInfo; +import org.jboss.logging.Logger; + +import com.oracle.svm.core.annotate.RecomputeFieldValue; + +/** + * Utility methods to log configuration problems. + */ +public final class ConfigDiagnostic { + private static final Logger log = Logger.getLogger("io.quarkus.config"); + + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) + private static volatile boolean error = false; + + private ConfigDiagnostic() { + } + + public static void invalidValue(String name, IllegalArgumentException ex) { + final String message = ex.getMessage(); + log.errorf("An invalid value was given for configuration key \"%s\": %s", name, + message == null ? ex.toString() : message); + error = true; + } + + public static void missingValue(String name, NoSuchElementException ex) { + final String message = ex.getMessage(); + log.errorf("Configuration key \"%s\" is required, but its value is empty/missing: %s", name, + message == null ? ex.toString() : message); + error = true; + } + + public static void duplicate(String name) { + log.errorf("Configuration key \"%s\" was specified more than once", name); + error = true; + } + + public static void deprecated(String name) { + log.warnf("Configuration key \"%s\" is deprecated", name); + } + + public static void unknown(String name) { + log.warnf("Unrecognized configuration key \"%s\" was provided; it will be ignored", name); + } + + public static void unknown(NameIterator name) { + unknown(name.getName()); + } + + public static void unknownRunTime(String name) { + if (ImageInfo.inImageRuntimeCode()) { + // only warn at run time for native images, otherwise the user will get warned twice for every property + log.warnf("Unrecognized configuration key \"%s\" was provided; it will be ignored", name); + } + } + + public static void unknownRunTime(NameIterator name) { + unknownRunTime(name.getName()); + } + + /** + * Determine if a fatal configuration error has occurred. + * + * @return {@code true} if a fatal configuration error has occurred + */ + public static boolean isError() { + return error; + } + + /** + * Reset the config error status (for e.g. testing). + */ + public static void resetError() { + error = false; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java index ad3d262b6ffbb..33daecaacfb36 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java @@ -1,23 +1,24 @@ package io.quarkus.runtime.configuration; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.OptionalLong; import java.util.Set; -import java.util.regex.Pattern; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.Converter; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; +import io.smallrye.config.Converters; import io.smallrye.config.SmallRyeConfig; /** @@ -30,7 +31,6 @@ */ public class ConfigInstantiator { - private static final Pattern COMMA_PATTERN = Pattern.compile(","); // certain well-known classname suffixes that we support private static Set supportedClassNameSuffix; @@ -71,54 +71,15 @@ private static void handleObject(String prefix, Object o, SmallRyeConfig config) String name = configItem.name(); if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { name = dashify(field.getName()); + } else if (name.equals(ConfigItem.ELEMENT_NAME)) { + name = field.getName(); } String fullName = prefix + "." + name; - String defaultValue = configItem.defaultValue(); - if (defaultValue.equals(ConfigItem.NO_DEFAULT)) { - defaultValue = null; - } final Type genericType = field.getGenericType(); - Optional val; - final boolean fieldIsOptional = fieldClass.equals(Optional.class); - final boolean fieldIsList = fieldClass.equals(List.class); - if (fieldIsOptional) { - Class actualType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - val = config.getOptionalValue(fullName, actualType); - } else if (fieldIsList) { - Class actualType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - val = config.getOptionalValues(fullName, actualType, ArrayList::new); - } else { - val = config.getOptionalValue(fullName, fieldClass); - } - if (val.isPresent()) { - field.set(o, fieldIsOptional ? val : val.get()); - } else if (defaultValue != null) { - if (fieldIsList) { - Class listType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - String[] parts = COMMA_PATTERN.split(defaultValue); - List list = new ArrayList<>(); - for (String i : parts) { - list.add(config.convert(i, listType)); - } - field.set(o, list); - } else if (fieldIsOptional) { - Class optionalType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - field.set(o, Optional.of(config.convert(defaultValue, optionalType))); - } else { - field.set(o, config.convert(defaultValue, fieldClass)); - } - } else if (fieldIsOptional) { - field.set(o, Optional.empty()); - } else if (fieldClass.equals(OptionalInt.class)) { - field.set(o, OptionalInt.empty()); - } else if (fieldClass.equals(OptionalDouble.class)) { - field.set(o, OptionalDouble.empty()); - } else if (fieldClass.equals(OptionalLong.class)) { - field.set(o, OptionalLong.empty()); + final Converter conv = getConverterFor(genericType); + try { + field.set(o, config.getValue(fullName, conv)); + } catch (NoSuchElementException ignored) { } } } @@ -127,6 +88,40 @@ private static void handleObject(String prefix, Object o, SmallRyeConfig config) } } + private static Converter getConverterFor(Type type) { + // hopefully this is enough + final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + Class rawType = rawTypeOf(type); + if (rawType == Optional.class) { + return Converters.newOptionalConverter(getConverterFor(typeOfParameter(type, 0))); + } else if (rawType == List.class) { + return Converters.newCollectionConverter(getConverterFor(typeOfParameter(type, 0)), ArrayList::new); + } else { + return config.getConverter(rawTypeOf(type)); + } + } + + // cribbed from io.quarkus.deployment.util.ReflectUtil + private static Class rawTypeOf(final Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return rawTypeOf(((ParameterizedType) type).getRawType()); + } else if (type instanceof GenericArrayType) { + return Array.newInstance(rawTypeOf(((GenericArrayType) type).getGenericComponentType()), 0).getClass(); + } else { + throw new IllegalArgumentException("Type has no raw type class: " + type); + } + } + + static Type typeOfParameter(final Type type, final int paramIdx) { + if (type instanceof ParameterizedType) { + return ((ParameterizedType) type).getActualTypeArguments()[paramIdx]; + } else { + throw new IllegalArgumentException("Type is not parameterized: " + type); + } + } + // Configuration keys are normally derived from the field names that they are tied to. // This is done by de-camel-casing the name and then joining the segments with hyphens (-). // Some examples: diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index 50295d1a0b01c..b6feba0e01017 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -1,196 +1,110 @@ package io.quarkus.runtime.configuration; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.function.IntFunction; -import java.util.stream.Collectors; +import java.util.regex.Pattern; -import org.eclipse.microprofile.config.spi.Converter; +import org.eclipse.microprofile.config.spi.ConfigSource; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.StringUtil; +import io.smallrye.config.PropertiesConfigSourceProvider; +import io.smallrye.config.SmallRyeConfigBuilder; /** * */ public final class ConfigUtils { - private static final Map> EXPLICIT_RUNTIME_CONVERTERS_CACHE = new HashMap<>(); - private ConfigUtils() { } - /** - * This method replicates the logic of {@link SmallRyeConfig#getValues(String, Class, IntFunction)} for the given - * default value string. - * - * @param config the config instance (must not be {@code null}) - * @param defaultValue the default value string (must not be {@code null}) - * @param itemType the item type class (must not be {@code null}) - * @param converterClass - The converter class to use - * @param collectionFactory the collection factory (must not be {@code null}) - * @param the item type - * @param the collection type - * @return the collection (not {@code null}) - */ - public static > C getDefaults(SmallRyeConfig config, String defaultValue, Class itemType, - Class> converterClass, - IntFunction collectionFactory) { - final String[] items = Arrays.stream(StringUtil.split(defaultValue)).filter(s -> !s.isEmpty()).toArray(String[]::new); - final C collection = collectionFactory.apply(items.length); - for (String item : items) { - if (converterClass == null) { - collection.add(config.convert(item, itemType)); - } else { - final Converter converter = getConverterOfType(itemType, converterClass); - final String rawValue = config.convert(item, String.class); - collection.add(converter.convert(rawValue)); - } - } - - return collection; + public static IntFunction> listFactory() { + return ArrayList::new; } - /** - * Retrieve the value of a given config name from Configuration object. Converter the value to an appropriate type using the - * given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the value in appropriate type - */ - public static T getValue(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getValue(configName, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.getValue(configName, String.class); - return converter.convert(rawValue); + public static IntFunction> setFactory() { + return LinkedHashSet::new; } - /** - * Retrieve the Optional value of a property represented by the given config name. Converter the value to an appropriate - * type using the given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return Optional value of appropriate type - */ - public static Optional getOptionalValue(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getOptionalValue(configName, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.getValue(configName, String.class); - return Optional.ofNullable(converter.convert(rawValue)); + public static IntFunction> sortedSetFactory() { + return size -> new TreeSet<>(); } /** - * Retrieve the value of a given config name from Configuration object. Converter the value to an appropriate type using the - * given converter. + * Get the basic configuration builder. * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the values in appropriate type + * @param runTime {@code true} if the configuration is run time, {@code false} if build time + * @return the configuration builder */ - public static ArrayList getValues(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getValues(configName, objectType, ArrayListFactory.getInstance()); + public static SmallRyeConfigBuilder configBuilder(final boolean runTime) { + final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); + final ApplicationPropertiesConfigSource.InFileSystem inFileSystem = new ApplicationPropertiesConfigSource.InFileSystem(); + final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); + builder.withSources(inFileSystem, inJar); + final ExpandingConfigSource.Cache cache = new ExpandingConfigSource.Cache(); + builder.withWrapper(ExpandingConfigSource.wrapper(cache)); + builder.withWrapper(DeploymentProfileConfigSource.wrapper()); + final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (runTime) { + builder.addDefaultSources(); + } else { + final List sources = new ArrayList<>(); + sources.addAll(new PropertiesConfigSourceProvider("META-INF/microprofile-config.properties", true, classLoader) + .getConfigSources(classLoader)); + // required by spec... + sources.addAll( + new PropertiesConfigSourceProvider("WEB-INF/classes/META-INF/microprofile-config.properties", true, + classLoader).getConfigSources(classLoader)); + sources.add(new EnvConfigSource()); + sources.add(new SysPropConfigSource()); + builder.withSources(sources.toArray(new ConfigSource[0])); } - - final Converter converter = getConverterOfType(objectType, converterClass); - final ArrayList rawValues = config.getValues(configName, String.class, ArrayListFactory.getInstance()); - return rawValues.parallelStream().map(converter::convert).collect(Collectors.toCollection(ArrayList::new)); + builder.addDiscoveredSources(); + builder.addDiscoveredConverters(); + return builder; } - /** - * Converter the value to an appropriate type using the given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param value - the value (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the value - */ - public static T convert(SmallRyeConfig config, String value, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.convert(value, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.convert(value, String.class); - return converter.convert(rawValue); - } + static final class EnvConfigSource implements ConfigSource { + static final Pattern REP_PATTERN = Pattern.compile("[^a-zA-Z0-9_]"); - private static Converter getConverterOfType(Class type, Class> converterType) { - @SuppressWarnings("unchecked") - final Converter converter = (Converter) EXPLICIT_RUNTIME_CONVERTERS_CACHE - .get(new ConverterClassHolder(type, converterType)); - if (converter != null) { - return converter; + public Map getProperties() { + return Collections.emptyMap(); } - // build time converter no need to be cached - return newConverterInstance(type, converterType); - } - - public static Converter newConverterInstance(Class type, Class> converterClass) { - // todo: this gets cleaned up with the SmallRye Config update - if (HyphenateEnumConverter.class.equals(converterClass)) { - @SuppressWarnings("unchecked") - final Converter converter = new HyphenateEnumConverter(type); - return converter; + public String getValue(final String propertyName) { + return System.getenv(REP_PATTERN.matcher(propertyName.toUpperCase(Locale.ROOT)).replaceAll("_")); } - try { - return converterClass.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new IllegalArgumentException(e); + public String getName() { + return "System environment"; } } - public static void populateExplicitRuntimeConverter(Class typeClass, Class> converterType, - Converter converter) { - final Class type = getWrapperClass(typeClass); - EXPLICIT_RUNTIME_CONVERTERS_CACHE.put(new ConverterClassHolder(type, converterType), converter); - } - - private static Class getWrapperClass(Class type) { - if (type == Integer.TYPE) { - return Integer.class; + static final class SysPropConfigSource implements ConfigSource { + public Map getProperties() { + Map output = new TreeMap<>(); + for (Map.Entry entry : System.getProperties().entrySet()) { + String key = (String) entry.getKey(); + if (key.startsWith("quarkus.")) { + output.put(key, entry.getValue().toString()); + } + } + return output; } - if (type == Long.TYPE) { - return Long.class; - } - if (type == Boolean.TYPE) { - return Boolean.class; - } - if (type == Float.TYPE) { - return Float.class; + public String getValue(final String propertyName) { + return System.getProperty(propertyName); } - if (type == Double.TYPE) { - return Double.class; + public String getName() { + return "System properties"; } - - return type; } - } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java new file mode 100644 index 0000000000000..cb5706fb9e397 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java @@ -0,0 +1,46 @@ +package io.quarkus.runtime.configuration; + +/** + * An exception indicating that a configuration failure has occurred. + */ +public class ConfigurationException extends RuntimeException { + private static final long serialVersionUID = 4445679764085720090L; + + /** + * Constructs a new {@code ConfigurationException} instance. The message is left blank ({@code null}), and no + * cause is specified. + */ + public ConfigurationException() { + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial message. No + * cause is specified. + * + * @param msg the message + */ + public ConfigurationException(final String msg) { + super(msg); + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial cause. If + * a non-{@code null} cause is specified, its message is used to initialize the message of this + * {@code ConfigurationException}; otherwise the message is left blank ({@code null}). + * + * @param cause the cause + */ + public ConfigurationException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial message and cause. + * + * @param msg the message + * @param cause the cause + */ + public ConfigurationException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java index 5e66365e41506..3025974522450 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java @@ -19,6 +19,7 @@ * This small utility class is a tool which helps populating SmallRye {@link ConfigBuilder} with * {@link Converter} implementations loaded from {@link ServiceLoader}. */ +// todo: delete public class ConverterSupport { private static final Logger LOG = Logger.getLogger(ConverterSupport.class); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java deleted file mode 100644 index 40cbe8f7d02da..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Map; -import java.util.Properties; - -import io.smallrye.config.PropertiesConfigSource; - -/** - * The default values run time configuration source. - */ -public final class DefaultConfigSource extends PropertiesConfigSource { - private static final long serialVersionUID = -6482737535291300045L; - - public static final String DEFAULT_CONFIG_PROPERTIES_NAME = "META-INF/quarkus-default-config.properties"; - - /** - * Construct a new instance. - */ - public DefaultConfigSource() { - super(getMap(), "Default configuration values", 0); - } - - @SuppressWarnings("unchecked") - private static Map getMap() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl == null) { - cl = DefaultConfigSource.class.getClassLoader(); - } - try { - final Properties p = new Properties(); - // work around #1477 - final Enumeration resources = cl == null ? ClassLoader.getSystemResources(DEFAULT_CONFIG_PROPERTIES_NAME) - : cl.getResources(DEFAULT_CONFIG_PROPERTIES_NAME); - if (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (InputStream is = url.openStream()) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - p.load(isr); - } - } - } - return (Map) p; - } catch (IOException e) { - throw new IllegalStateException("Cannot read default configuration", e); - } - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java index 1eb4059d75393..236286f6a4533 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java @@ -61,4 +61,9 @@ public String getValue(final String name) { public String getName() { return delegate.getName(); } + + public String toString() { + return "DeploymentProfileConfigSource[profile=" + profilePrefix + ",delegate=" + getDelegate() + ",ord=" + getOrdinal() + + "]"; + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java index f356eb22343e0..3746e210cf0d1 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java @@ -32,6 +32,9 @@ public DurationConverter() { */ @Override public Duration convert(String value) { + if (value.isEmpty()) { + return null; + } if (DIGITS.asPredicate().test(value)) { return Duration.ofSeconds(Long.valueOf(value)); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java index aefcc7dfb77e3..86c28d94a8a22 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java @@ -52,6 +52,10 @@ public void flush() { cache.flush(); } + public String toString() { + return "ExpandingConfigSource[delegate=" + getDelegate() + ",ord=" + getOrdinal() + "]"; + } + private static boolean isExpanding() { return NO_EXPAND.get() != Boolean.TRUE; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java index a1108704612fc..45d88f8db3747 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java @@ -7,10 +7,12 @@ import org.eclipse.microprofile.config.spi.Converter; +import io.quarkus.runtime.util.StringUtil; + /** - * A converter for hyphenated enums + * A converter for hyphenated enums. */ -final public class HyphenateEnumConverter> implements Converter { +public final class HyphenateEnumConverter> implements Converter { private static final String HYPHEN = "-"; private static final Pattern PATTERN = Pattern.compile("([-_]+)"); @@ -27,6 +29,10 @@ public HyphenateEnumConverter(Class enumType) { } } + public static > HyphenateEnumConverter of(Class enumType) { + return new HyphenateEnumConverter(enumType); + } + @Override public E convert(String value) { if (value == null || value.trim().isEmpty()) { @@ -46,7 +52,7 @@ public E convert(String value) { private String hyphenate(String value) { StringBuffer target = new StringBuffer(); - String hyphenate = io.quarkus.runtime.util.StringUtil.hyphenate(value); + String hyphenate = StringUtil.hyphenate(value); Matcher matcher = PATTERN.matcher(hyphenate); while (matcher.find()) { matcher.appendReplacement(target, HYPHEN); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java index eaa1858e69193..fbe8e5f95e331 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java @@ -42,6 +42,9 @@ public class MemorySizeConverter implements Converter { * @return {@link MemorySize} - a memory size represented by the given value */ public MemorySize convert(String value) { + if (value.isEmpty()) { + return null; + } Matcher matcher = MEMORY_SIZE_PATTERN.matcher(value); if (matcher.find()) { BigInteger number = new BigInteger(matcher.group(1)); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java index b641f262bd090..0c68a3c2a2348 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java @@ -295,6 +295,22 @@ public String getPreviousSegment() { } } + public String getAllPreviousSegments() { + final int pos = getPosition(); + if (pos == -1) { + return ""; + } + return name.substring(0, pos); + } + + public String getAllPreviousSegmentsWith(String suffix) { + final int pos = getPosition(); + if (pos == -1) { + return suffix; + } + return name.substring(0, pos) + "." + suffix; + } + public boolean hasNext() { return pos < name.length(); } @@ -316,7 +332,12 @@ public String getName() { } public String toString() { - // generated code relies on this behavior - return getName(); + if (pos == -1) { + return "*" + name; + } else if (pos == name.length()) { + return name + "*"; + } else { + return name.substring(0, pos) + '*' + name.substring(pos + 1); + } } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java index 0b1aaf9872c60..a783840069ad8 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java @@ -17,6 +17,6 @@ public class PathConverter implements Converter { @Override public Path convert(String value) { - return Paths.get(value); + return value.isEmpty() ? null : Paths.get(value); } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java new file mode 100644 index 0000000000000..b359cd2aac0a4 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java @@ -0,0 +1,29 @@ +package io.quarkus.runtime.configuration; + +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigFactory; +import io.smallrye.config.SmallRyeConfigProviderResolver; + +/** + * The simple Quarkus implementation of {@link SmallRyeConfigFactory}. + */ +public final class QuarkusConfigFactory extends SmallRyeConfigFactory { + + private static volatile SmallRyeConfig config; + + /** + * Construct a new instance. Called by service loader. + */ + public QuarkusConfigFactory() { + // todo: replace with {@code provider()} post-Java 11 + } + + public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configProviderResolver, + final ClassLoader classLoader) { + return config; + } + + public static void setConfig(SmallRyeConfig config) { + QuarkusConfigFactory.config = config; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java index eb395a0b945b8..ad6e72093c48b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java @@ -21,6 +21,6 @@ public RegexConverter() { } public Pattern convert(final String value) { - return Pattern.compile(value); + return value.isEmpty() ? null : Pattern.compile(value); } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java deleted file mode 100644 index c41510ad7bb88..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.quarkus.runtime.configuration; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; - -import io.smallrye.config.SmallRyeConfigBuilder; - -/** - * A simple configuration provider. - */ -public class SimpleConfigurationProviderResolver extends ConfigProviderResolver { - - // We use a shared config - private static volatile Config config; - - public Config getConfig() { - return config; - } - - public Config getConfig(final ClassLoader loader) { - return getConfig(); - } - - public ConfigBuilder getBuilder() { - return new SmallRyeConfigBuilder(); - } - - public void registerConfig(final Config config, final ClassLoader classLoader) { - SimpleConfigurationProviderResolver.config = config; - } - - public void releaseConfig(final Config config) { - SimpleConfigurationProviderResolver.config = null; - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java index fbf76fd31b8e2..c35da81757777 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java @@ -1,7 +1,5 @@ package io.quarkus.runtime.configuration; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.wildfly.common.Assert; @@ -68,20 +66,4 @@ public static boolean setExpanding(boolean newValue) { return true; } } - - @TargetClass(ConfigProvider.class) - static final class Target_ConfigProvider { - @Delete - private static ConfigProviderResolver INSTANCE; - - @Substitute - public static Config getConfig() { - return ConfigProviderResolver.instance().getConfig(); - } - - @Substitute - public static Config getConfig(ClassLoader cl) { - return getConfig(); - } - } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java deleted file mode 100644 index b40200f6b81bf..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.util.Arrays; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.ConfigSourceProvider; - -/** - * This is a temporary hack until the class loader mess is worked out. - */ -public class TemporaryConfigSourceProvider implements ConfigSourceProvider { - public Iterable getConfigSources(final ClassLoader forClassLoader) { - return Arrays.asList( - new ApplicationPropertiesConfigSource.InJar(), - new ApplicationPropertiesConfigSource.InFileSystem()); - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java new file mode 100644 index 0000000000000..b7d18c0bd394e --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/ConfigurationSubstitutions.java @@ -0,0 +1,49 @@ +package io.quarkus.runtime.graal; + +import org.eclipse.microprofile.config.Config; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.AlwaysInline; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigProviderResolver; + +@TargetClass(SmallRyeConfigProviderResolver.class) +final class Target_io_smallrye_config_SmallRyeConfigProviderResolver { + @Substitute + public Config getConfig() { + final SmallRyeConfig config = Target_io_quarkus_runtime_configuration_QuarkusConfigFactory.config; + if (config == null) { + throw new IllegalStateException("No configuration is available"); + } + return config; + } + + @Substitute + @AlwaysInline("trivial") + public Config getConfig(ClassLoader classLoader) { + return getConfig(); + } + + @Substitute + public void registerConfig(Config config, ClassLoader classLoader) { + // no op + } + + @Substitute + public void releaseConfig(Config config) { + // no op + } +} + +@TargetClass(QuarkusConfigFactory.class) +final class Target_io_quarkus_runtime_configuration_QuarkusConfigFactory { + @Alias + static SmallRyeConfig config; +} + +final class ConfigurationSubstitutions { +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java index 31b994c477cfa..25358cb1af1b4 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java @@ -1,6 +1,8 @@ package io.quarkus.runtime.util; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Objects; @@ -97,6 +99,13 @@ public String next() { }; } + /** + * @deprecated Use {@link String#join} instead. + * @param delim delimiter + * @param it iterator + * @return the joined string + */ + @Deprecated public static String join(String delim, Iterator it) { final StringBuilder b = new StringBuilder(); if (it.hasNext()) { @@ -167,6 +176,34 @@ public String next() { }; } + @SafeVarargs + public static List withoutSuffix(List list, T... segments) { + if (list.size() < segments.length) { + return list; + } + for (int i = 0; i < segments.length; i++) { + if (!list.get(list.size() - i - 1).equals(segments[segments.length - i - 1])) { + return list; + } + } + return list.subList(0, list.size() - segments.length); + } + + public static List toList(Iterator orig) { + return toList(orig, 0); + } + + private static List toList(Iterator orig, int idx) { + if (orig.hasNext()) { + final String item = orig.next(); + final List list = toList(orig, idx + 1); + list.set(idx, item); + return list; + } else { + return Arrays.asList(new String[idx]); + } + } + @SafeVarargs private static boolean arrayContains(final T item, final T... array) { for (T arrayItem : array) { diff --git a/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory b/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory new file mode 100644 index 0000000000000..0900f32a78ef9 --- /dev/null +++ b/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory @@ -0,0 +1 @@ +io.quarkus.runtime.configuration.QuarkusConfigFactory diff --git a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider b/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider deleted file mode 100644 index 778dff1e4a68c..0000000000000 --- a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.runtime.configuration.TemporaryConfigSourceProvider diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java index 0b3605e20e529..4c404b30c50f6 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java @@ -34,7 +34,11 @@ public static void initConfig() { @AfterEach public void doAfter() { - cpr.releaseConfig(config); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } } private SmallRyeConfig buildConfig(Map configMap) { diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java index e09d72bb2fb0f..48b668ef8c06a 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java @@ -31,7 +31,11 @@ public static void initConfig() { @AfterEach public void doAfter() { - cpr.releaseConfig(config); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } } private SmallRyeConfig buildConfig(Map configMap) { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java index c4c57bf92dd9c..688d6250a4390 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java @@ -20,12 +20,15 @@ import org.apache.maven.toolchain.ToolchainManager; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import io.quarkus.maven.components.MavenVersionEnforcer; import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.remotedev.AgentRunner; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; -import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.smallrye.config.SmallRyeConfig; /** * The dev mojo, that connects to a remote host. @@ -101,13 +104,15 @@ public void execute() throws MojoFailureException, MojoExecutionException { Path config = Paths.get(resources).resolve("application.properties"); if (Files.exists(config)) { try { - Config built = SmallRyeConfigProviderResolver.instance().getBuilder() - .addDefaultSources() - .addDiscoveredConverters() - .addDiscoveredSources() + SmallRyeConfig built = ConfigUtils.configBuilder(false) .withSources(new PropertiesConfigSource(config.toUri().toURL())).build(); - SmallRyeConfigProviderResolver.instance().registerConfig(built, - Thread.currentThread().getContextClassLoader()); + QuarkusConfigFactory.setConfig(built); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + final Config existing = cpr.getConfig(); + if (existing != built) { + cpr.releaseConfig(existing); + // subsequent calls will get the new config + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java index ea738d5ba3507..1f213df0344a0 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageLauncher.java @@ -52,7 +52,7 @@ public NativeImageLauncher(Class testClass) { } private static Config installAndGetSomeConfig() { - final SmallRyeConfig config = ConfigUtils.configBuilder().build(); + final SmallRyeConfig config = ConfigUtils.configBuilder(false).build(); QuarkusConfigFactory.setConfig(config); final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); try { From 6fd7e99b65a54beddd1c5ca48ccd9567bee43309 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 21 Nov 2019 08:56:21 -0600 Subject: [PATCH 044/602] TCK fixes to deal with property expansion --- .../tck/config/CustomConfigProviderTest.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java index eea764d61d919..d08a201848e53 100644 --- a/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java +++ b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java @@ -3,12 +3,32 @@ import org.eclipse.microprofile.config.tck.ConfigProviderTest; import org.testng.annotations.Test; +import io.quarkus.runtime.configuration.ExpandingConfigSource; + public class CustomConfigProviderTest extends ConfigProviderTest { - @Test(enabled = false) + @Test public void testEnvironmentConfigSource() { + // this test fails when there is a expression-like thing in an env prop + boolean old = ExpandingConfigSource.setExpanding(false); + try { + super.testPropertyConfigSource(); + } finally { + ExpandingConfigSource.setExpanding(old); + } } @Test(enabled = false) public void testInjectedConfigSerializable() { } + + @Test + public void testPropertyConfigSource() { + // this test fails when there is a expression-like thing in a sys prop + boolean old = ExpandingConfigSource.setExpanding(false); + try { + super.testPropertyConfigSource(); + } finally { + ExpandingConfigSource.setExpanding(old); + } + } } From 83332759a36e75b244e511ca31bba3e2ec1fd7e9 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 16:07:15 -0600 Subject: [PATCH 045/602] Update guide to clarify that roots are only available from their corresponding phases --- docs/src/main/asciidoc/writing-extensions.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 0778a25b40296..1814eb7ece64e 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -220,7 +220,7 @@ Static Init:: This means that if a framework can boot in this phase then it will have its booted state directly written to the image, and so the boot code does not need to be executed when the image is started. + -There are some restrictions on what can be done in this stage as the Substrate VM disallows some objects in the native executable. For example you should not attempt to listen on a port or start threads in this phase. +There are some restrictions on what can be done in this stage as the Substrate VM disallows some objects in the native executable. For example you should not attempt to listen on a port or start threads in this phase. In addition, it is disallowed to read run time configuration during static initialization. + In non-native pure JVM mode, there is no real difference between Static and Runtime Init, except that Static Init is always executed first. This mode benefits from the same build phase augmentation as native mode as the descriptor parsing and annotation scanning are done at build time and any associated class/framework dependencies can be removed from the build output jar. In servers like @@ -886,6 +886,7 @@ this change is complete. ===== Configuration Root Phases +Configuration roots are strictly bound by configuration phase, and attempting to access a configuration root from outside of its corresponding phase will result in an error. A configuration root dictates when its contained keys are read from configuration, and when they are available to applications. The phases defined by `io.quarkus.runtime.annotations.ConfigPhase` are as follows: [cols="<3m,^1,^1,^1,^1,<8",options="header"] From 5095a94605e72dbca7306e2dac2b5d62dad736f5 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 Nov 2019 16:21:33 -0600 Subject: [PATCH 046/602] Add docs relating to optional and empty values and config groups --- .../src/main/asciidoc/writing-extensions.adoc | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 1814eb7ece64e..21696b7061218 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -852,11 +852,37 @@ In addition, custom converters may be registered by adding their fully qualified Though these implicit converters use reflection, Quarkus will automatically ensure that they are loaded at the appropriate time. +===== Optional Values + +If the configuration type is one of the optional types, then empty values are allowed for the configuration key; otherwise, +specification of an empty value will result in a configuration error which prevents the application from starting. This +is especially relevant to configuration properties of inherently emptiable values such as `List`, `Set`, and `String`. Such +value types will never be empty; in the event of an empty value, an empty `Optional` is always used. + +==== Configuration Default Values + +A configuration item can be marked to have a default value. The default value is used when no matching configuration key +is specified in the configuration. + +Configuration items with a primitive type (such as `int` or `boolean`) implicitly use a default value of `0` or `false`. The +sole exception to this rule is the `char` type which does not have an implicit default value. + +A property with a default value is not implicitly optional. If a non-optional configuration item with a default value +is explicitly specified to have an empty value, the application will report a configuration error and will not start. If +it is desired for a property to have a default value and also be optional, it must have an `Optional` type as described above. + ==== Configuration Groups Configuration values are always collected into grouping classes which are marked with the `@io.quarkus.runtime.annotations.ConfigGroup` annotation. These classes contain a field for each key within its group. In addition, configuration groups can be nested. +===== Optional Configuration Groups + +A nested configuration group may be wrapped with an `Optional` type. In this case, the group is not populated unless one +or more properties within that group are specified in the configuration. If the group is populated, then any required +properties in the group must also be specified otherwise a configuration error will be reported and the application will +not start. + ==== Configuration Maps A `Map` can be used for configuration at any position where a configuration group would be allowed. The key type of such a From b0bc1ab0a8209707f37720e6dff95be7745ea0a0 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 12 Nov 2019 21:00:04 +0100 Subject: [PATCH 047/602] Tests for Map> where Xxx is a leaf or config group --- .../src/main/resources/application.properties | 5 +++++ .../java/io/quarkus/extest/ConfiguredBeanTest.java | 14 ++++++++++++++ .../extest/runtime/config/TestRunTimeConfig.java | 7 +++++++ 3 files changed, 26 insertions(+) diff --git a/core/test-extension/deployment/src/main/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties index 9c904afc68904..dba86f4b3a5a1 100644 --- a/core/test-extension/deployment/src/main/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -99,6 +99,11 @@ quarkus.rt.one-to-nine=one,two,three,four,five,six,seven,eight,nine quarkus.rt.map-of-numbers.key1=one quarkus.rt.map-of-numbers.key2=two +### map configurations +quarkus.rt.leaf-map.key.first=first-key-value +quarkus.rt.leaf-map.key.second=second-key-value +quarkus.rt.config-group-map.key.group.nested-value=value +quarkus.rt.config-group-map.key.group.oov=value2.1+value2.2 ### build time and run time configuration using enhanced converters quarkus.btrt.map-of-numbers.key1=one diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java index 64cbf269e3e60..bcf9d1092a31e 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java @@ -204,6 +204,20 @@ public void validateRuntimeConfigMap() { Assertions.assertEquals(Arrays.asList("value1", "value2", "value3"), stringListMap.get("key1")); Assertions.assertEquals(Arrays.asList("value4", "value5"), stringListMap.get("key2")); Assertions.assertEquals(Collections.singletonList("value6"), stringListMap.get("key3")); + + //quarkus.rt.leaf-map.key.first=first-key-value + //quarkus.rt.leaf-map.key.second=second-key-value + + final Map> leafMap = runTimeConfig.leafMap; + Assertions.assertEquals("first-key-value", leafMap.get("key").get("first")); + Assertions.assertEquals("second-key-value", leafMap.get("key").get("second")); + + //quarkus.rt.config-group-map.key.group.nested-value=value + //quarkus.rt.config-group-map.key.group.oov=value2.1+value2.2 + final Map> configGroupMap = runTimeConfig.configGroupMap; + NestedConfig nestedConfigFromMap = configGroupMap.get("key").get("group"); + Assertions.assertEquals("value", nestedConfigFromMap.nestedValue); + Assertions.assertEquals(new ObjectOfValue("value2.1", "value2.2"), nestedConfigFromMap.oov); } /** diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java index fe9ab9c45384a..66d9c335790ce 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java @@ -28,6 +28,13 @@ public class TestRunTimeConfig { @ConfigItem public AllValuesConfig allValues; + /** A map of properties */ + @ConfigItem + public Map> leafMap; + /** A map of property lists */ + @ConfigItem + public Map> configGroupMap; + /** * Enum object */ From ee02c5746f2822bafcd8d6169428d90b20c9c282 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 14 Nov 2019 16:27:39 -0600 Subject: [PATCH 048/602] Blacklist RestEASY config source from native image for now --- .../io/quarkus/deployment/steps/ConfigBuildSteps.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java index fd5089b4eba02..3c49eae0b5546 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java @@ -2,11 +2,11 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.OptionalInt; import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigSource; @@ -94,8 +94,11 @@ void nativeServiceProviders( Converter.class)) { final String serviceName = serviceClass.getName(); final Set names = ServiceUtil.classNamesNamedIn(classLoader, SERVICES_PREFIX + serviceName); - if (!names.isEmpty()) { - providerProducer.produce(new ServiceProviderBuildItem(serviceName, new ArrayList<>(names))); + final List list = names.stream() + // todo: see https://github.com/quarkusio/quarkus/issues/5492 + .filter(s -> !s.startsWith("org.jboss.resteasy.microprofile.config.")).collect(Collectors.toList()); + if (!list.isEmpty()) { + providerProducer.produce(new ServiceProviderBuildItem(serviceName, list)); } } } From e936072fbcfa1a15a1ffc1bb36a35f6846be70b9 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Thu, 14 Nov 2019 22:33:42 +0100 Subject: [PATCH 049/602] fix: doc generator so that config documentation website generator works --- .../annotation/processor/Constants.java | 1 - .../generate_doc/ConfigDoItemFinder.java | 35 ++++++++++++---- .../generate_doc/ConfigDocItemScanner.java | 9 ++-- .../processor/generate_doc/ConfigPhase.java | 16 +++++-- .../generate_doc/DocGeneratorUtil.java | 24 ++++++++++- .../generate_doc/DocGeneratorUtilTest.java | 42 +++++++++++++++++++ 6 files changed, 108 insertions(+), 19 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index c43b06a85e93d..6aa0ae55fcffd 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -30,7 +30,6 @@ final public class Constants { public static final String DEPLOYMENT = "deployment"; public static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^.+[\\.$](\\w+)$"); - public static final Pattern CONFIG_ROOT_PATTERN = Pattern.compile("^(\\w+)Config(uration)?"); public static final Pattern PKG_PATTERN = Pattern.compile("^io\\.quarkus\\.(\\w+)\\.?(\\w+)?\\.?(\\w+)?"); public static final String INSTANCE_SYM = "__instance"; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index e1d5581b56f60..4b10bd2c3bbd3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -172,8 +173,8 @@ private List recursivelyFindConfigItems(Element element, String p DeclaredType declaredType = (DeclaredType) typeMirror; TypeElement typeElement = (TypeElement) declaredType.asElement(); Name qualifiedName = typeElement.getQualifiedName(); - optional = qualifiedName.toString().startsWith("java.util.Optional"); - list = qualifiedName.contentEquals("java.util.List"); + optional = qualifiedName.toString().startsWith(Optional.class.getName()); + list = qualifiedName.contentEquals(List.class.getName()); List typeArguments = declaredType.getTypeArguments(); if (!typeArguments.isEmpty()) { @@ -197,8 +198,17 @@ private List recursivelyFindConfigItems(Element element, String p } else { // FIXME: this is for Optional and List TypeMirror realTypeMirror = typeArguments.get(0); - type = simpleTypeToString(realTypeMirror); + if (optional && (realTypeMirror.toString().startsWith(List.class.getName()) + || realTypeMirror.getKind() == TypeKind.ARRAY)) { + list = true; + DeclaredType declaredRealType = (DeclaredType) typeMirror; + typeArguments = declaredRealType.getTypeArguments(); + if (!typeArguments.isEmpty()) { + realTypeMirror = typeArguments.get(0); + } + } + type = simpleTypeToString(realTypeMirror); if (isEnumType(realTypeMirror)) { acceptedValues = extractEnumValues(realTypeMirror); } @@ -215,10 +225,10 @@ private List recursivelyFindConfigItems(Element element, String p configDocKey.setKey(name); configDocKey.setType(type); + configDocKey.setList(list); + configDocKey.setOptional(optional); configDocKey.setConfigPhase(configPhase); configDocKey.setDefaultValue(defaultValue); - configDocKey.setOptional(optional); - configDocKey.setList(list); configDocKey.setDocMapKey(configDocMapKey); configDocKey.setConfigDoc(configDescription); configDocKey.setAcceptedValues(acceptedValues); @@ -261,14 +271,25 @@ private List recordConfigItemsFromConfigGroup(ConfigPhase configP } private String simpleTypeToString(TypeMirror typeMirror) { + if (typeMirror.getKind().isPrimitive()) { return typeMirror.toString(); } else if (typeMirror.getKind() == TypeKind.ARRAY) { - return "list of " + simpleTypeToString(((ArrayType) typeMirror).getComponentType()); + return simpleTypeToString(((ArrayType) typeMirror).getComponentType()); } final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); - return knownGenericType != null ? knownGenericType : typeMirror.toString(); + + if (knownGenericType != null) { + return knownGenericType; + } + + List typeArguments = ((DeclaredType) typeMirror).getTypeArguments(); + if (!typeArguments.isEmpty()) { + return simpleTypeToString(typeArguments.get(0)); + } + + return typeMirror.toString(); } private List extractEnumValues(TypeMirror realTypeMirror) { diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java index 965880e9d740c..6f49b204b9b8d 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java @@ -2,7 +2,7 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; -import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.deriveConfigRootName; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -69,6 +69,7 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { } ConfigPhase configPhase = ConfigPhase.BUILD_TIME; + final String extensionName = pkgMatcher.group(1); for (AnnotationMirror annotationMirror : clazz.getAnnotationMirrors()) { String annotationName = annotationMirror.getAnnotationType().toString(); @@ -87,13 +88,9 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { } if (name.isEmpty()) { - final Matcher nameMatcher = Constants.CONFIG_ROOT_PATTERN.matcher(clazz.getSimpleName()); - if (nameMatcher.find()) { - name = Constants.QUARKUS + Constants.DOT + hyphenate(nameMatcher.group(1)); - } + name = deriveConfigRootName(clazz.getSimpleName().toString(), configPhase); } - final String extensionName = pkgMatcher.group(1); ConfigRootInfo configRootInfo = new ConfigRootInfo(name, clazz, extensionName, configPhase); configRoots.add(configRootInfo); break; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java index cd9708af3e6dd..55e72faab481f 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigPhase.java @@ -5,9 +5,10 @@ import io.quarkus.annotation.processor.Constants; public enum ConfigPhase implements Comparable { - RUN_TIME("The configuration is overridable at runtime", Constants.CONFIG_PHASE_RUNTIME_ILLUSTRATION), - BUILD_TIME("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION), - BUILD_AND_RUN_TIME_FIXED("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION); + RUN_TIME("The configuration is overridable at runtime", Constants.CONFIG_PHASE_RUNTIME_ILLUSTRATION, "RunTime"), + BUILD_TIME("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION, "BuildTime"), + BUILD_AND_RUN_TIME_FIXED("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION, + "BuildTime"); static final Comparator COMPARATOR = new Comparator() { /** @@ -52,10 +53,12 @@ public int compare(ConfigPhase firstPhase, ConfigPhase secondPhase) { private String description; private String illustration; + private String configSuffix; - ConfigPhase(String description, String illustration) { + ConfigPhase(String description, String illustration, String configSuffix) { this.description = description; this.illustration = illustration; + this.configSuffix = configSuffix; } @Override @@ -63,10 +66,15 @@ public String toString() { return "ConfigPhase{" + "description='" + description + '\'' + ", illustration='" + illustration + '\'' + + ", configSuffix='" + configSuffix + '\'' + '}'; } public String getIllustration() { return illustration; } + + public String getConfigSuffix() { + return configSuffix; + } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java index 8cf060413e738..fe88cd3ef7fcf 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java @@ -17,6 +17,8 @@ import io.quarkus.annotation.processor.Constants; public class DocGeneratorUtil { + private static final String CONFIG = "Config"; + private static final String CONFIGURATION = "Configuration"; private static String CONFIG_GROUP_PREFIX = "config-group-"; static final String VERTX_JAVA_DOC_SITE = "https://vertx.io/docs/apidocs/"; static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/javase/8/docs/api/"; @@ -98,8 +100,12 @@ static String getJavaDocSiteLink(String type) { } private static String getJavaDocLinkForType(String type) { - int indexOfFirstUpperCase = 0; + int beginOfWrappedTypeIndex = type.indexOf("<"); + if (beginOfWrappedTypeIndex != -1) { + type = type.substring(0, beginOfWrappedTypeIndex); + } + int indexOfFirstUpperCase = 0; for (int index = 0; index < type.length(); index++) { char charAt = type.charAt(index); if (charAt >= 'A' && charAt <= 'Z') { @@ -380,4 +386,20 @@ private static String typeSimpleName(TypeMirror typeMirror) { String type = ((DeclaredType) typeMirror).asElement().toString(); return type.substring(1 + type.lastIndexOf(Constants.DOT)); } + + static String deriveConfigRootName(String simpleClassName, ConfigPhase configPhase) { + int length = simpleClassName.length(); + + if (simpleClassName.endsWith(CONFIG)) { + String sanitized = simpleClassName.substring(0, length - CONFIG.length()); + return deriveConfigRootName(sanitized, configPhase); + } else if (simpleClassName.endsWith(CONFIGURATION)) { + String sanitized = simpleClassName.substring(0, length - CONFIGURATION.length()); + return deriveConfigRootName(sanitized, configPhase); + } else if (simpleClassName.endsWith(configPhase.getConfigSuffix())) { + String sanitized = simpleClassName.substring(0, length - configPhase.getConfigSuffix().length()); + return deriveConfigRootName(sanitized, configPhase); + } + return Constants.QUARKUS + Constants.DOT + hyphenate(simpleClassName); + } } diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java index 3ff77a75b2fb4..b5504cc41148a 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java @@ -6,6 +6,7 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.appendConfigItemsIntoExistingOnes; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.deriveConfigRootName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -93,6 +94,12 @@ public void shouldReturnALinkToOfficialJavaDocIfIsJavaOfficialType() { value = getJavaDocSiteLink(Map.Entry.class.getName()); assertEquals(OFFICIAL_JAVA_DOC_BASE_LINK + "java/util/Map.Entry.html", value); + + value = getJavaDocSiteLink(List.class.getName()); + assertEquals(OFFICIAL_JAVA_DOC_BASE_LINK + "java/util/List.html", value); + + value = getJavaDocSiteLink("java.util.List"); + assertEquals(OFFICIAL_JAVA_DOC_BASE_LINK + "java/util/List.html", value); } @Test @@ -279,4 +286,39 @@ public void shouldDeepAppendConfigSectionConfigItemsIntoExistingConfigItemsOfCon assertEquals(deepConfigKey, deepSection.getConfigDocItems().get(0)); assertEquals(configItem, deepSection.getConfigDocItems().get(1)); } + + @Test + public void derivingConfigRootNameTestCase() { + // should hyphenate class name + String simpleClassName = "RootName"; + String actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); + assertEquals("quarkus.root-name", actual); + + // should hyphenate class name after removing Config(uration) suffix + simpleClassName = "RootNameConfig"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.BUILD_TIME); + assertEquals("quarkus.root-name", actual); + + simpleClassName = "RootNameConfiguration"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.BUILD_AND_RUN_TIME_FIXED); + assertEquals("quarkus.root-name", actual); + + // should hyphenate class name after removing RunTimeConfig(uration) suffix + simpleClassName = "RootNameRunTimeConfig"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); + assertEquals("quarkus.root-name", actual); + + simpleClassName = "RootNameRunTimeConfiguration"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); + assertEquals("quarkus.root-name", actual); + + // should hyphenate class name after removing BuildTimeConfig(uration) suffix + simpleClassName = "RootNameBuildTimeConfig"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.BUILD_AND_RUN_TIME_FIXED); + assertEquals("quarkus.root-name", actual); + + simpleClassName = "RootNameBuildTimeConfiguration"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.BUILD_TIME); + assertEquals("quarkus.root-name", actual); + } } From 93b8d084dd87c8e7419bdbb793689886d145b95d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 18 Nov 2019 16:18:05 -0600 Subject: [PATCH 050/602] Support enabling/disabling expansion at run time --- .../runtime/configuration/Substitutions.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java index c35da81757777..297734c44f2a8 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java @@ -1,7 +1,6 @@ package io.quarkus.runtime.configuration; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.wildfly.common.Assert; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Delete; @@ -17,6 +16,8 @@ final class Substitutions { static final FastThreadLocalInt depth = FastThreadLocalFactory.createInt(); + // 0 = expand so that the default value is to expand + static final FastThreadLocalInt notExpanding = FastThreadLocalFactory.createInt(); @TargetClass(ConfigExpander.class) static final class Target_ConfigExpander { @@ -56,14 +57,16 @@ static final class Target_ExpandingConfigSource { @Substitute private static boolean isExpanding() { - return true; + return notExpanding.get() == 0; } @Substitute public static boolean setExpanding(boolean newValue) { - if (!newValue) - throw Assert.unsupported(); - return true; + try { + return notExpanding.get() == 0; + } finally { + notExpanding.set(newValue ? 0 : 1); + } } } } From 691a4983362d4cc30e3d2b07fb6dec5d9baa3cd4 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 19 Nov 2019 13:07:00 -0600 Subject: [PATCH 051/602] Add a doc note about clearing properties --- docs/src/main/asciidoc/config.adoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 11269265af0b3..020d3df4611b4 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -434,6 +434,15 @@ quarkus.http.port=9090 And then set the `QUARKUS_PROFILE` environment variable to `staging` to activate my profile. +=== Clearing properties + +Run time properties which are optional, and which have had values set at build time or which have a default value, +may be explicitly cleared by assigning an empty string to the property. Note that this will _only_ affect +run time properties, and will _only_ work with properties whose values are not required. + +The property may be cleared by setting the corresponding `application.properties` property, setting the +corresponding system property, or setting the corresponding environment variable. + ==== Miscellaneous The default Quarkus application runtime profile is set to the profile used to build the application. For example: From 71ee1bf828eee7d5821b8307b4655ea3152ade09 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 20 Nov 2019 16:09:48 -0600 Subject: [PATCH 052/602] Only add run time only config sources at run time, not during static init --- .../RunTimeConfigurationGenerator.java | 7 +++++++ .../quarkus/deployment/steps/ConfigBuildSteps.java | 10 +--------- .../quarkus/runtime/configuration/ConfigUtils.java | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index d34f5ade45216..7ee50c6ef9901 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -20,6 +20,7 @@ import org.eclipse.microprofile.config.spi.ConfigBuilder; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.Converter; import org.objectweb.asm.Opcodes; import org.wildfly.common.Assert; @@ -137,6 +138,8 @@ public final class RunTimeConfigurationGenerator { IntFunction.class); static final MethodDescriptor CU_CONFIG_BUILDER = MethodDescriptor.ofMethod(ConfigUtils.class, "configBuilder", SmallRyeConfigBuilder.class, boolean.class); + static final MethodDescriptor CU_ADD_SOURCE_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceProvider", + void.class, SmallRyeConfigBuilder.class, ConfigSourceProvider.class); static final MethodDescriptor HM_NEW = MethodDescriptor.ofConstructor(HashMap.class); static final MethodDescriptor HM_PUT = MethodDescriptor.ofMethod(HashMap.class, "put", Object.class, Object.class, @@ -373,6 +376,10 @@ public void run() { // create the run time config final ResultHandle runTimeBuilder = readConfig.invokeStaticMethod(CU_CONFIG_BUILDER, readConfig.load(true)); + // add in our run time only config source provider + readConfig.invokeStaticMethod(CU_ADD_SOURCE_PROVIDER, runTimeBuilder, readConfig.newInstance( + MethodDescriptor.ofConstructor("io.quarkus.runtime.generated.ConfigSourceProviderImpl"))); + // create the map for run time specified values config source final ResultHandle specifiedRunTimeValues = clinit.newInstance(HM_NEW); for (Map.Entry entry : specifiedRunTimeDefaultValues.entrySet()) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java index 3c49eae0b5546..6067cc13b4bf7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.steps; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.OptionalInt; @@ -17,7 +16,6 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; -import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.RunTimeConfigurationSourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; @@ -38,8 +36,7 @@ class ConfigBuildSteps { @BuildStep void generateConfigSources(List runTimeSources, - final BuildProducer generatedClass, - final BuildProducer generatedResource) { + final BuildProducer generatedClass) { ClassOutput classOutput = new ClassOutput() { @Override public void write(String name, byte[] data) { @@ -72,10 +69,6 @@ public void write(String name, byte[] data) { mc.returnValue(list); } } - - generatedResource.produce(new GeneratedResourceBuildItem( - SERVICES_PREFIX + ConfigSourceProvider.class.getName(), - PROVIDER_CLASS_NAME.getBytes(StandardCharsets.UTF_8))); } // XXX replace this with constant-folded service loader impl @@ -83,7 +76,6 @@ public void write(String name, byte[] data) { void nativeServiceProviders( final DeploymentClassLoaderBuildItem classLoaderItem, final BuildProducer providerProducer) throws IOException { - providerProducer.produce(new ServiceProviderBuildItem(ConfigSourceProvider.class.getName(), PROVIDER_CLASS_NAME)); providerProducer.produce(new ServiceProviderBuildItem(ConfigProviderResolver.class.getName(), SmallRyeConfigProviderResolver.class.getName())); final ClassLoader classLoader = classLoaderItem.getClassLoader(); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index b6feba0e01017..6d260f36ff452 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -14,6 +14,7 @@ import java.util.regex.Pattern; import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import io.smallrye.config.PropertiesConfigSourceProvider; import io.smallrye.config.SmallRyeConfigBuilder; @@ -71,6 +72,19 @@ public static SmallRyeConfigBuilder configBuilder(final boolean runTime) { return builder; } + /** + * Add a configuration source provider to the builder. + * + * @param builder the builder + * @param provider the provider to add + */ + public static void addSourceProvider(SmallRyeConfigBuilder builder, ConfigSourceProvider provider) { + final Iterable sources = provider.getConfigSources(Thread.currentThread().getContextClassLoader()); + for (ConfigSource source : sources) { + builder.withSources(source); + } + } + static final class EnvConfigSource implements ConfigSource { static final Pattern REP_PATTERN = Pattern.compile("[^a-zA-Z0-9_]"); From b9ac117489c31ea23f72b90bc1cceaaaf3388146 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 21 Nov 2019 08:00:09 -0600 Subject: [PATCH 053/602] Use GeneratedClassGizmoAdaptor in config classes --- .../io/quarkus/deployment/steps/ConfigBuildSteps.java | 8 ++------ .../io/quarkus/deployment/steps/MainClassBuildStep.java | 7 +------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java index 6067cc13b4bf7..221f99b6eb645 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java @@ -12,6 +12,7 @@ import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.Converter; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; @@ -37,12 +38,7 @@ class ConfigBuildSteps { @BuildStep void generateConfigSources(List runTimeSources, final BuildProducer generatedClass) { - ClassOutput classOutput = new ClassOutput() { - @Override - public void write(String name, byte[] data) { - generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); - } - }; + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); try (ClassCreator cc = ClassCreator.builder().interfaces(ConfigSourceProvider.class).setFinal(true) .className(PROVIDER_CLASS_NAME) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 820113ce1bc2f..04927872b1312 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -88,12 +88,7 @@ MainClassBuildItem build(List staticInitTasks, List> additionalConfigTypes = typeItems.stream().map(ConfigurationTypeBuildItem::getValueType) .collect(Collectors.toList()); - ClassOutput classOutput = new ClassOutput() { - @Override - public void write(String name, byte[] data) { - generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); - } - }; + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); RunTimeConfigurationGenerator.generate(readResult, classOutput, defaults, additionalConfigTypes); From e945218e85488c86cb9dd4a5a1b3de508e17075e Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 21 Nov 2019 14:11:24 -0600 Subject: [PATCH 054/602] Fix REST TCK issues by setting up a config as needed --- tcks/microprofile-rest-client/pom.xml | 4 ++ .../CustomInvokeWithJsonBProviderTest.java | 41 +++++++++++++++++++ .../CustomInvokeWithJsonPProviderTest.java | 41 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java create mode 100644 tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java diff --git a/tcks/microprofile-rest-client/pom.xml b/tcks/microprofile-rest-client/pom.xml index 4f2405e7a2945..4947d131ad085 100644 --- a/tcks/microprofile-rest-client/pom.xml +++ b/tcks/microprofile-rest-client/pom.xml @@ -40,6 +40,10 @@ org.eclipse.microprofile.rest.client.tck.cditests.HasConversationScopeTest + + + org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest + org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest diff --git a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java new file mode 100644 index 0000000000000..ccc826d759932 --- /dev/null +++ b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java @@ -0,0 +1,41 @@ +package io.quarkus.tck.restclient; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.SmallRyeConfig; + +/** + * + */ +public class CustomInvokeWithJsonBProviderTest extends InvokeWithJsonBProviderTest { + @BeforeTest + public void setupClient() throws Exception { + SmallRyeConfig config = ConfigUtils.configBuilder(true).build(); + QuarkusConfigFactory.setConfig(config); + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + Config old = cpr.getConfig(); + if (old != config) { + cpr.releaseConfig(old); + } + } catch (IllegalStateException ignored) { + } + super.setupClient(); + } + + @AfterTest + public void tearDownClient() { + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + Config old = cpr.getConfig(); + cpr.releaseConfig(old); + } catch (IllegalStateException ignored) { + } + } +} diff --git a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java new file mode 100644 index 0000000000000..622866c526377 --- /dev/null +++ b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java @@ -0,0 +1,41 @@ +package io.quarkus.tck.restclient; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.SmallRyeConfig; + +/** + * + */ +public class CustomInvokeWithJsonPProviderTest extends InvokeWithJsonPProviderTest { + @BeforeTest + public void setupClient() throws Exception { + SmallRyeConfig config = ConfigUtils.configBuilder(true).build(); + QuarkusConfigFactory.setConfig(config); + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + Config old = cpr.getConfig(); + if (old != config) { + cpr.releaseConfig(old); + } + } catch (IllegalStateException ignored) { + } + super.setupClient(); + } + + @AfterTest + public void tearDownClient() { + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + Config old = cpr.getConfig(); + cpr.releaseConfig(old); + } catch (IllegalStateException ignored) { + } + } +} From 4760bd6a6704c2ae364325660baad26d59124690 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 22 Nov 2019 10:27:00 -0600 Subject: [PATCH 055/602] Add @Override --- .../java/io/quarkus/deployment/configuration/type/ArrayOf.java | 3 +++ .../io/quarkus/deployment/configuration/type/CollectionOf.java | 3 +++ .../java/io/quarkus/deployment/configuration/type/Leaf.java | 3 +++ .../deployment/configuration/type/LowerBoundCheckOf.java | 3 +++ .../quarkus/deployment/configuration/type/MinMaxValidated.java | 3 +++ .../io/quarkus/deployment/configuration/type/OptionalOf.java | 2 ++ .../deployment/configuration/type/PatternValidated.java | 3 +++ .../deployment/configuration/type/UpperBoundCheckOf.java | 3 +++ 8 files changed, 23 insertions(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java index 0e050069f6455..8116feeb95576 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java @@ -19,6 +19,7 @@ public ConverterType getElementType() { return type; } + @Override public Class getLeafType() { return type.getLeafType(); } @@ -31,6 +32,7 @@ public Class getArrayType() { return arrayType; } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -43,6 +45,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof ArrayOf && equals((ArrayOf) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java index 842ba505b1546..3be6c14499eba 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java @@ -19,6 +19,7 @@ public ConverterType getElementType() { return type; } + @Override public Class getLeafType() { return type.getLeafType(); } @@ -27,6 +28,7 @@ public Class getCollectionClass() { return collectionClass; } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -39,6 +41,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof CollectionOf && equals((CollectionOf) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java index 4a0073e33660f..827d6cdf52372 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java @@ -17,6 +17,7 @@ public Leaf(final Class type, final Class convertWith) { this.convertWith = (Class>) convertWith; } + @Override public Class getLeafType() { return type; } @@ -25,6 +26,7 @@ public Class> getConvertWith() { return convertWith; } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -37,6 +39,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof Leaf && equals((Leaf) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java index ea37614dfc7ac..29bb7430f2b6b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java @@ -23,10 +23,12 @@ public ConverterType getClassConverterType() { return classConverterType; } + @Override public Class getLeafType() { return classConverterType.getLeafType(); } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -39,6 +41,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof LowerBoundCheckOf && equals((LowerBoundCheckOf) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java index 320c29923ad7c..b96153bf7fe71 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java @@ -42,10 +42,12 @@ public boolean isMaxInclusive() { return maxInclusive; } + @Override public Class getLeafType() { return type.getLeafType(); } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -58,6 +60,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof MinMaxValidated && equals((MinMaxValidated) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java index dcbc01ddfa902..8ec697235bc0f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java @@ -17,6 +17,7 @@ public ConverterType getNestedType() { return type; } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -29,6 +30,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof OptionalOf && equals((OptionalOf) obj); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java index cba5df78daf1f..b41312458779d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java @@ -23,6 +23,7 @@ public String getPatternString() { return patternString; } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -35,6 +36,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof PatternValidated && equals((PatternValidated) obj); } @@ -43,6 +45,7 @@ public boolean equals(final PatternValidated obj) { return obj == this || obj != null && type.equals(obj.type) && patternString.equals(obj.patternString); } + @Override public Class getLeafType() { return type.getLeafType(); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java index 7a328de862dd7..f9aaca6a4d73e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java @@ -23,10 +23,12 @@ public ConverterType getClassConverterType() { return classConverterType; } + @Override public Class getLeafType() { return classConverterType.getLeafType(); } + @Override public int hashCode() { int hashCode = this.hashCode; if (hashCode == 0) { @@ -39,6 +41,7 @@ public int hashCode() { return hashCode; } + @Override public boolean equals(final Object obj) { return obj instanceof UpperBoundCheckOf && equals((UpperBoundCheckOf) obj); } From d8fbf43860b590e5ceeafc6d444acd310d327ed1 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 22 Nov 2019 10:36:44 -0600 Subject: [PATCH 056/602] Reduce usage of ConverterSupport to constant values --- .../quarkus/deployment/ExtensionLoader.java | 3 - .../configuration/ConverterSupport.java | 142 +---------------- .../configuration/ConverterSupportTest.java | 146 ------------------ ....eclipse.microprofile.config.spi.Converter | 2 - 4 files changed, 1 insertion(+), 292 deletions(-) delete mode 100644 core/runtime/src/test/java/io/quarkus/runtime/configuration/ConverterSupportTest.java delete mode 100644 core/runtime/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index b6c92f5d9a0f2..009c873d332cf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -190,9 +190,6 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, builder.withSources(ds1, ds2, pcs); - // populate builder with all converters loaded from ServiceLoader - ConverterSupport.populateConverters(builder); - if (configCustomizer != null) { configCustomizer.accept(builder); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java index 3025974522450..748e976d4fa3d 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java @@ -1,37 +1,14 @@ package io.quarkus.runtime.configuration; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.function.Consumer; - import javax.annotation.Priority; -import org.eclipse.microprofile.config.spi.ConfigBuilder; import org.eclipse.microprofile.config.spi.Converter; -import org.jboss.logging.Logger; - -import io.smallrye.config.Converters; /** - * This small utility class is a tool which helps populating SmallRye {@link ConfigBuilder} with - * {@link Converter} implementations loaded from {@link ServiceLoader}. + * This small utility class holds constants relevant to configuration converters. */ -// todo: delete public class ConverterSupport { - private static final Logger LOG = Logger.getLogger(ConverterSupport.class); - - /** - * A list of {@link ConverterItem} which will be loaded in static initialization. This needs - * to be static so we can load {@link Converter} implementations without producing reflective - * class build items in the deployment time. The {@link ConverterItem} instances uses generic - * {@link Object} type to avoid typecast errors from compiler. - */ - private static final List> CONVERTERS = getConverters(); - /** * Default {@link Converter} priority with value {@value #DEFAULT_SMALLRYE_CONVERTER_PRIORITY} * to be used for all discovered converters in case when no {@link Priority} annotation is @@ -49,124 +26,7 @@ public class ConverterSupport { */ public static final int DEFAULT_QUARKUS_CONVERTER_PRIORITY = 200; - /** - * Populates given {@link ConfigBuilder} with all {@link Converter} implementations loaded from - * the {@link ServiceLoader}. - * - * @param builder the {@link ConfigBuilder} - */ - public static void populateConverters(final ConfigBuilder builder) { - CONVERTERS.forEach(addConverterTo(builder)); - } - - /** - * Get {@link Converter} priority by looking for a {@link Priority} annotation which can be put - * on the converter type. If no {@link Priority} annotation is found a default priority of - * {@value #DEFAULT_SMALLRYE_CONVERTER_PRIORITY} is returned. - * - * @param converterClass - * @return - */ - static int getConverterPriority(final Class> converterClass) { - return Optional - .ofNullable(converterClass.getAnnotation(Priority.class)) - .map(Priority::value) - .orElse(DEFAULT_SMALLRYE_CONVERTER_PRIORITY); - } - - /** - * Converts {@link Converter} instance into {@link ConverterItem}. - * - * @param the converter conversion type - * @param converter the converter instance - * @return New {@link ConverterItem} which wraps given {@link Converter} and related metadata - */ - @SuppressWarnings("unchecked") - private static ConverterItem converterToItem(final Converter converter) { - final Class> converterClass = (Class>) converter.getClass(); - final Type genericType = Converters.getConverterType(converterClass); - if (!(genericType instanceof Class)) { - throw new IllegalArgumentException("General converters may not convert generic types"); - } - final Class convertedType = (Class) genericType; - final int priority = getConverterPriority(converterClass); - return new ConverterItem(convertedType, converter, priority); - } - - /** - * @return A {@link List} of {@link ConverterItem} loaded from {@link ServiceLoader} - */ - @SuppressWarnings("unchecked") - private static List> getConverters() { - final List> items = new ArrayList<>(); - for (Converter converter : ServiceLoader.load(Converter.class)) { - items.add((ConverterItem) converterToItem(converter)); - } - return items; - } - - /** - * Create a {@link Consumer} which consumes {@link ConverterItem} wrapping {@link Converter} - * (and related metadata) and adds it to the given {@link ConfigBuilder}. - * - * @param the {@link Converter} conversion type - * @param builder - * @return A {@link ConverterItem} {@link Consumer} which populates {@link ConfigBuilder} - */ - private static Consumer> addConverterTo(final ConfigBuilder builder) { - return item -> { - - final Class type = item.getConvertedType(); - final Converter converter = item.getConverter(); - final int priority = item.getPriority(); - - LOG.debugf("Populate SmallRye config builder with converter for %s of priority %s", type, priority); - - builder.withConverter(type, priority, converter); - }; - } - private ConverterSupport() { // this is utility class } - - /** - * This class wraps {@link Converter} and related metadata, i.e. a {@link Converter} conversion - * type and its priority. - * - * @param the {@link Converter} conversion type - */ - private static final class ConverterItem { - - final Class convertedType; - final Converter converter; - final int priority; - - public ConverterItem(final Class convertedType, final Converter converter, final int priority) { - this.convertedType = convertedType; - this.converter = converter; - this.priority = priority; - } - - /** - * @return {@link Converter} conversion type - */ - public Class getConvertedType() { - return convertedType; - } - - /** - * @return A {@link Converter} this item wraps - */ - public Converter getConverter() { - return converter; - } - - /** - * @return A {@link Converter} priority - */ - public int getPriority() { - return priority; - } - } } diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConverterSupportTest.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConverterSupportTest.java deleted file mode 100644 index f4f7c29ea8a18..0000000000000 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConverterSupportTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.quarkus.runtime.configuration; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.HashMap; -import java.util.Map; - -import javax.annotation.Priority; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.Converter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * A test cases against {@link ConverterSupport} class. - */ -public class ConverterSupportTest { - - public static class MyPojoUno { - } - - public static class MyPojoDuo { - } - - public static class MyPojoUnoConverter implements Converter { - - @Override - public MyPojoUno convert(final String value) { - return new MyPojoUno(); - } - } - - @Priority(333) - public static class MyPojoDuoConverter implements Converter { - - @Override - public MyPojoDuo convert(final String value) { - return new MyPojoDuo(); - } - } - - static class TestConverterWrapper { - - final Class type; - final int priority; - final Converter converter; - - public TestConverterWrapper(Class type, int priority, Converter converter) { - this.type = type; - this.priority = priority; - this.converter = converter; - } - } - - static class TestConfigBuilder implements ConfigBuilder { - - private final Map, TestConverterWrapper> wrappers = new HashMap<>(); - - @Override - public ConfigBuilder addDefaultSources() { - // do nothing - return null; - } - - @Override - public ConfigBuilder addDiscoveredSources() { - // do nothing - return null; - } - - @Override - public ConfigBuilder addDiscoveredConverters() { - // do nothing - return null; - } - - @Override - public ConfigBuilder forClassLoader(ClassLoader loader) { - // do nothing - return null; - } - - @Override - public ConfigBuilder withSources(ConfigSource... sources) { - // do nothing - return null; - } - - @Override - public ConfigBuilder withConverters(Converter... converters) { - // do nothing - return null; - } - - @Override - public ConfigBuilder withConverter(Class type, int priority, Converter converter) { - wrappers.put(type, new TestConverterWrapper(type, priority, converter)); - return null; - } - - @Override - public Config build() { - // do nothing - return null; - } - - public TestConverterWrapper get(Class type) { - return wrappers.get(type); - } - } - - TestConfigBuilder builder; - - @BeforeEach - public void setup() { - builder = new TestConfigBuilder(); - ConverterSupport.populateConverters(builder); - } - - @Test - public void testAllConvertersLoaded() { - assertNotNull(builder.get(MyPojoUno.class)); - assertNotNull(builder.get(MyPojoDuo.class)); - } - - @Test - public void testCorrectTypesMapping() { - assertSame(MyPojoUnoConverter.class, builder.get(MyPojoUno.class).converter.getClass()); - assertSame(MyPojoDuoConverter.class, builder.get(MyPojoDuo.class).converter.getClass()); - } - - @Test - public void testPriorityDefaults() { - assertTrue(builder.get(MyPojoUno.class).priority == 100); - } - - @Test - public void testPriorityFromAnnotation() { - assertTrue(builder.get(MyPojoDuo.class).priority == 333); - } -} diff --git a/core/runtime/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter b/core/runtime/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter deleted file mode 100644 index 89f86617564aa..0000000000000 --- a/core/runtime/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter +++ /dev/null @@ -1,2 +0,0 @@ -io.quarkus.runtime.configuration.ConverterSupportTest$MyPojoUnoConverter -io.quarkus.runtime.configuration.ConverterSupportTest$MyPojoDuoConverter From 55c51d4558fff993673791b49c4bab496ebb934d Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 22 Nov 2019 10:39:07 -0600 Subject: [PATCH 057/602] Add consistent trimming behavior to converters --- .../quarkus/runtime/configuration/CidrAddressConverter.java | 3 ++- .../io/quarkus/runtime/configuration/DurationConverter.java | 1 + .../runtime/configuration/HyphenateEnumConverter.java | 5 ++--- .../quarkus/runtime/configuration/InetAddressConverter.java | 3 ++- .../runtime/configuration/InetSocketAddressConverter.java | 3 ++- .../quarkus/runtime/configuration/MemorySizeConverter.java | 1 + 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java index b99781b8f35fc..708693b26cccb 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java @@ -15,7 +15,8 @@ public class CidrAddressConverter implements Converter { @Override - public CidrAddress convert(final String value) { + public CidrAddress convert(String value) { + value = value.trim(); if (value.isEmpty()) { return null; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java index 3746e210cf0d1..6e1406aedc601 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java @@ -32,6 +32,7 @@ public DurationConverter() { */ @Override public Duration convert(String value) { + value = value.trim(); if (value.isEmpty()) { return null; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java index 45d88f8db3747..20032f9b714f6 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java @@ -35,11 +35,10 @@ public static > HyphenateEnumConverter of(Class enumType @Override public E convert(String value) { - if (value == null || value.trim().isEmpty()) { + value = value.trim(); + if (value.isEmpty()) { return null; } - - value = value.trim(); final String hyphenatedValue = hyphenate(value); final Enum enumValue = values.get(hyphenatedValue); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java index 94c5c498df6ab..b7adf02c8ad6e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java @@ -17,7 +17,8 @@ public class InetAddressConverter implements Converter { @Override - public InetAddress convert(final String value) { + public InetAddress convert(String value) { + value = value.trim(); if (value.isEmpty()) { return null; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java index 42fa13083715d..ed3a3785e3512 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java @@ -19,7 +19,8 @@ public class InetSocketAddressConverter implements Converter { @Override - public InetSocketAddress convert(final String value) { + public InetSocketAddress convert(String value) { + value = value.trim(); if (value.isEmpty()) { return null; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java index fbe8e5f95e331..e4361653cfc75 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java @@ -42,6 +42,7 @@ public class MemorySizeConverter implements Converter { * @return {@link MemorySize} - a memory size represented by the given value */ public MemorySize convert(String value) { + value = value.trim(); if (value.isEmpty()) { return null; } From dedf77966f75df9ca749a4c2e20f3dbc99d89c79 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 22 Nov 2019 15:30:30 -0600 Subject: [PATCH 058/602] Disable failing test (see #5175) --- tcks/microprofile-rest-client/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tcks/microprofile-rest-client/pom.xml b/tcks/microprofile-rest-client/pom.xml index 4947d131ad085..6476bff8e3843 100644 --- a/tcks/microprofile-rest-client/pom.xml +++ b/tcks/microprofile-rest-client/pom.xml @@ -44,6 +44,9 @@ org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest + + + io.quarkus.tck.restclient.CustomInvokeWithJsonPProviderTest From 1491669a772f6a7893835fece85d32d258c7115f Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Fri, 22 Nov 2019 15:45:28 +0100 Subject: [PATCH 059/602] Fixes 5689, adds FormAuth smoke test to Elytron package and Vert.x http package The class FormAuthCookiesTestCase will receive additional work, mostly cookie value rotation, time out etc. Fixes #5689 --- .../security/test/FormAuthTestCase.java | 54 +++++++++++ .../application-form-auth.properties | 23 +++++ .../security/FormAuthCookiesTestCase.java | 91 +++++++++++++++++++ .../security/FormAuthenticationMechanism.java | 2 +- 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 extensions/elytron-security-properties-file/deployment/src/test/java/io/quarkus/security/test/FormAuthTestCase.java create mode 100644 extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthCookiesTestCase.java diff --git a/extensions/elytron-security-properties-file/deployment/src/test/java/io/quarkus/security/test/FormAuthTestCase.java b/extensions/elytron-security-properties-file/deployment/src/test/java/io/quarkus/security/test/FormAuthTestCase.java new file mode 100644 index 0000000000000..219effe9656a1 --- /dev/null +++ b/extensions/elytron-security-properties-file/deployment/src/test/java/io/quarkus/security/test/FormAuthTestCase.java @@ -0,0 +1,54 @@ +package io.quarkus.security.test; + +import static io.restassured.RestAssured.given; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.authentication.FormAuthConfig; + +/** + * Tests of FORM authentication mechanism + *

+ * See @io.quarkus.vertx.http.security.FormAuthTestCase for functional coverage. + */ +public class FormAuthTestCase { + static Class[] testClasses = { + TestSecureServlet.class, TestApplication.class, RolesEndpointClassLevel.class + }; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(testClasses) + .addAsResource("application-form-auth.properties", "application.properties") + .addAsResource("test-users.properties") + .addAsResource("test-roles.properties")); + + @Test() + public void testSecureAccessSuccess() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + given().auth() + .form("stuart", "test", + new FormAuthConfig("j_security_check", "j_username", "j_password") + .withLoggingEnabled()) + .when().get("/secure-test").then().statusCode(200); + + given().auth() + .form("jdoe", "p4ssw0rd", + new FormAuthConfig("j_security_check", "j_username", "j_password") + .withLoggingEnabled()) + .when().get("/secure-test").then().statusCode(403); + + given().auth() + .form("scott", "jb0ss", + new FormAuthConfig("j_security_check", "j_username", "j_password") + .withLoggingEnabled()) + .when().get("/jaxrs-secured/rolesClass").then().statusCode(200); + } + +} diff --git a/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties b/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties new file mode 100644 index 0000000000000..bb91fd666d238 --- /dev/null +++ b/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties @@ -0,0 +1,23 @@ +# Logging +quarkus.log.level=DEBUG +quarkus.log.console.enable=true +quarkus.log.console.level=DEBUG +quarkus.log.file.enable=true +quarkus.log.file.path=/tmp/trace.log +quarkus.log.file.level=TRACE +quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.category."io.quarkus.arc".level=TRACE +quarkus.log.category."io.undertow.request.security".level=TRACE + +# Identities +quarkus.security.users.file.enabled=true +quarkus.security.users.file.users=test-users.properties +quarkus.security.users.file.roles=test-roles.properties +quarkus.security.users.file.plain-text=true + +# Auth method +quarkus.http.auth.form.enabled=true +quarkus.http.auth.basic=false +quarkus.http.auth.form.login-page=/ +quarkus.http.auth.form.error-page=/ +quarkus.http.auth.form.landing-page=/ diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthCookiesTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthCookiesTestCase.java new file mode 100644 index 0000000000000..7a5ae632bfe1e --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthCookiesTestCase.java @@ -0,0 +1,91 @@ +package io.quarkus.vertx.http.security; + +import static org.hamcrest.Matchers.*; + +import java.util.function.Supplier; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.filter.cookie.CookieFilter; + +public class FormAuthCookiesTestCase { + + private static final String APP_PROPS = "" + + "quarkus.http.auth.form.enabled=true\n" + + "quarkus.http.auth.form.login-page=login\n" + + "quarkus.http.auth.form.error-page=error\n" + + "quarkus.http.auth.form.landing-page=landing\n" + + "quarkus.http.auth.policy.r1.roles-allowed=admin\n" + + "quarkus.http.auth.permission.roles1.paths=/admin%E2%9D%A4\n" + + "quarkus.http.auth.permission.roles1.policy=r1\n" + + "quarkus.http.auth.form.timeout=PT6S\n" + + "quarkus.http.auth.form.new-cookie-interval=PT2S\n" + + "quarkus.http.auth.form.cookie-name=laitnederc-sukrauq\n" + + "quarkus.http.auth.session.encryption-key=CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT\n"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestIdentityProvider.class, TestTrustedIdentityProvider.class, PathHandler.class) + .addAsResource(new StringAsset(APP_PROPS), "application.properties"); + } + }); + + @BeforeAll + public static void setup() { + TestIdentityController.resetRoles() + .add("admin", "admin", "admin"); + } + + @Test + public void testFormBasedAuthSuccess() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + CookieFilter cookies = new CookieFilter(); + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .get("/admin❤") + .then() + .assertThat() + .statusCode(302) + .header("location", containsString("/login")) + .cookie("quarkus-redirect-location", containsString("/admin%E2%9D%A4")); + + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .formParam("j_username", "admin") + .formParam("j_password", "admin") + .post("/j_security_check") + .then() + .assertThat() + .statusCode(302) + .header("location", containsString("/admin%E2%9D%A4")) + .cookie("laitnederc-sukrauq", notNullValue()); + + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .get("/admin❤") + .then() + .assertThat() + .statusCode(200) + .body(equalTo("admin:/admin%E2%9D%A4")); + + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java index 3e909ce55c67f..8bfc37b827712 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java @@ -61,7 +61,7 @@ public void init(HttpConfiguration httpConfiguration, HttpBuildTimeConfig buildT key = httpConfiguration.encryptionKey.get(); } FormAuthConfig form = buildTimeConfig.auth.form; - loginManager = new PersistentLoginManager(key, "quarkus-credential", form.timeout.toMillis(), + loginManager = new PersistentLoginManager(key, form.cookieName, form.timeout.toMillis(), form.newCookieInterval.toMillis()); loginPage = form.loginPage.startsWith("/") ? form.loginPage : "/" + form.loginPage; errorPage = form.errorPage.startsWith("/") ? form.errorPage : "/" + form.errorPage; From a1c72a0546debc48969a2949830a15423aa45b04 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 21 Nov 2019 10:48:52 +0100 Subject: [PATCH 060/602] Reactive routes - usability improvements - introduce RouteBase annotation - use route method name if neither path nor regex is set - resolves #5606 --- .../recording/AnnotationProxyProvider.java | 17 ++++ .../recording/BytecodeRecorderImpl.java | 56 +++++++----- .../AnnotatedRouteHandlerBuildItem.java | 9 +- .../web/deployment/VertxWebProcessor.java | 87 ++++++++++++++++++- .../io/quarkus/vertx/web/SimpleRouteTest.java | 2 +- .../quarkus/vertx/web/base/RouteBaseTest.java | 81 +++++++++++++++++ .../main/java/io/quarkus/vertx/web/Route.java | 7 +- .../java/io/quarkus/vertx/web/RouteBase.java | 36 ++++++++ 8 files changed, 267 insertions(+), 28 deletions(-) create mode 100644 extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/base/RouteBaseTest.java create mode 100644 extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RouteBase.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/AnnotationProxyProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/AnnotationProxyProvider.java index d8909704a6c0b..7d7a61d02d042 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/AnnotationProxyProvider.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/AnnotationProxyProvider.java @@ -88,6 +88,8 @@ public interface AnnotationProxy { Map getDefaultValues(); + Map getValues(); + } public class AnnotationProxyBuilder { @@ -97,6 +99,7 @@ public class AnnotationProxyBuilder { private final AnnotationInstance annotationInstance; private final Class annotationType; private final Map defaultValues = new HashMap<>(); + private final Map values = new HashMap<>(); AnnotationProxyBuilder(AnnotationInstance annotationInstance, Class annotationType, String annotationLiteral, ClassInfo annotationClass) { @@ -106,6 +109,18 @@ public class AnnotationProxyBuilder { this.annotationClass = annotationClass; } + /** + * Explicit values override the default values from the annotation class. + * + * @param name + * @param value + * @return self + */ + public AnnotationProxyBuilder withValue(String name, Object value) { + values.put(name, value); + return this; + } + /** * Explicit default values override the default values from the annotation class. * @@ -180,6 +195,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return annotationInstance; case "getDefaultValues": return defaultValues; + case "getValues": + return values; default: break; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index 29e47c8e8f713..321358c01e610 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -14,7 +14,6 @@ import java.net.URL; import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -805,41 +804,54 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand // new com.foo.MyAnnotation_Proxy_AnnotationLiteral("foo") AnnotationProxy annotationProxy = (AnnotationProxy) param; List constructorParams = annotationProxy.getAnnotationClass().methods().stream() - .filter(m -> !m.name().equals("") && !m.name().equals("")) - .collect(Collectors.toList()); + .filter(m -> !m.name().equals("") && !m.name().equals("")).collect(Collectors.toList()); Map annotationValues = annotationProxy.getAnnotationInstance().values().stream() .collect(Collectors.toMap(AnnotationValue::name, Function.identity())); DeferredParameter[] constructorParamsHandles = new DeferredParameter[constructorParams.size()]; for (ListIterator iterator = constructorParams.listIterator(); iterator.hasNext();) { MethodInfo valueMethod = iterator.next(); - AnnotationValue value = annotationValues.get(valueMethod.name()); - if (value == null) { - // method.invokeInterfaceMethod(MAP_PUT, valuesHandle, method.load(entry.getKey()), loadObjectInstance(method, entry.getValue(), returnValueResults, entry.getValue().getClass())); - Object defaultValue = annotationProxy.getDefaultValues().get(valueMethod.name()); - if (defaultValue != null) { - constructorParamsHandles[iterator.previousIndex()] = loadObjectInstance(defaultValue, - existing, defaultValue.getClass()); - continue; + Object explicitValue = annotationProxy.getValues().get(valueMethod.name()); + if (explicitValue != null) { + constructorParamsHandles[iterator.previousIndex()] = loadObjectInstance(explicitValue, existing, + explicitValue.getClass()); + } else { + AnnotationValue value = annotationValues.get(valueMethod.name()); + if (value == null) { + // method.invokeInterfaceMethod(MAP_PUT, valuesHandle, method.load(entry.getKey()), loadObjectInstance(method, entry.getValue(), + // returnValueResults, entry.getValue().getClass())); + Object defaultValue = annotationProxy.getDefaultValues().get(valueMethod.name()); + if (defaultValue != null) { + constructorParamsHandles[iterator.previousIndex()] = loadObjectInstance(defaultValue, existing, + defaultValue.getClass()); + continue; + } + if (value == null) { + value = valueMethod.defaultValue(); + } } if (value == null) { - value = valueMethod.defaultValue(); + throw new NullPointerException("Value not set for " + param); } + DeferredParameter retValue = loadValue(value, annotationProxy.getAnnotationClass(), valueMethod); + constructorParamsHandles[iterator.previousIndex()] = retValue; } - if (value == null) { - throw new NullPointerException("Value not set for " + param); - } - DeferredParameter retValue = loadValue(value, annotationProxy.getAnnotationClass(), valueMethod); - constructorParamsHandles[iterator.previousIndex()] = retValue; } return new DeferredArrayStoreParameter() { @Override ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) { - return method - .newInstance(MethodDescriptor.ofConstructor(annotationProxy.getAnnotationLiteralType(), - constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()), - Arrays.stream(constructorParamsHandles).map(m -> context.loadDeferred(m)) - .toArray(ResultHandle[]::new)); + MethodDescriptor constructor = MethodDescriptor.ofConstructor(annotationProxy.getAnnotationLiteralType(), + constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()); + ResultHandle[] args = new ResultHandle[constructorParamsHandles.length]; + for (int i = 0; i < constructorParamsHandles.length; i++) { + DeferredParameter deferredParameter = constructorParamsHandles[i]; + if (deferredParameter instanceof DeferredArrayStoreParameter) { + DeferredArrayStoreParameter arrayParam = (DeferredArrayStoreParameter) deferredParameter; + arrayParam.doPrepare(context); + } + args[i] = context.loadDeferred(deferredParameter); + } + return method.newInstance(constructor, args); } }; diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/AnnotatedRouteHandlerBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/AnnotatedRouteHandlerBuildItem.java index 118b3ee62e2ab..e9ddd2d5943a9 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/AnnotatedRouteHandlerBuildItem.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/AnnotatedRouteHandlerBuildItem.java @@ -12,12 +12,15 @@ public final class AnnotatedRouteHandlerBuildItem extends MultiBuildItem { private final BeanInfo bean; private final List routes; + private final AnnotationInstance routeBase; private final MethodInfo method; - public AnnotatedRouteHandlerBuildItem(BeanInfo bean, MethodInfo method, List routes) { + public AnnotatedRouteHandlerBuildItem(BeanInfo bean, MethodInfo method, List routes, + AnnotationInstance routeBase) { this.bean = bean; this.method = method; this.routes = routes; + this.routeBase = routeBase; } public BeanInfo getBean() { @@ -32,4 +35,8 @@ public List getRoutes() { return routes; } + public AnnotationInstance getRouteBase() { + return routeBase; + } + } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index e1d7458b9001e..d102597adb9f2 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -40,6 +40,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.recording.AnnotationProxyProvider.AnnotationProxyBuilder; import io.quarkus.deployment.util.HashUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; @@ -51,6 +52,7 @@ import io.quarkus.vertx.http.runtime.HandlerType; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; import io.quarkus.vertx.web.RoutingExchange; import io.quarkus.vertx.web.runtime.RoutingExchangeImpl; @@ -66,6 +68,7 @@ class VertxWebProcessor { private static final DotName ROUTE = DotName.createSimple(Route.class.getName()); private static final DotName ROUTES = DotName.createSimple(Route.Routes.class.getName()); private static final DotName ROUTE_FILTER = DotName.createSimple(RouteFilter.class.getName()); + private static final DotName ROUTE_BASE = DotName.createSimple(RouteBase.class.getName()); private static final DotName ROUTING_CONTEXT = DotName.createSimple(RoutingContext.class.getName()); private static final DotName RX_ROUTING_CONTEXT = DotName .createSimple(io.vertx.reactivex.ext.web.RoutingContext.class.getName()); @@ -74,6 +77,12 @@ class VertxWebProcessor { private static final DotName[] ROUTE_PARAM_TYPES = { ROUTING_CONTEXT, RX_ROUTING_CONTEXT, ROUTING_EXCHANGE }; private static final DotName[] ROUTE_FILTER_TYPES = { ROUTING_CONTEXT }; + private static final String VALUE_PATH = "path"; + private static final String VALUE_REGEX = "regex"; + private static final String VALUE_PRODUCES = "produces"; + private static final String VALUE_CONSUMES = "consumes"; + private static final String DASH = "/"; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.VERTX_WEB); @@ -98,7 +107,9 @@ void validateBeanDeployment( for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { if (bean.isClassBean()) { // NOTE: inherited business methods are not taken into account - for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + ClassInfo beanClass = bean.getTarget().get().asClass(); + AnnotationInstance routeBaseAnnotation = beanClass.classAnnotation(ROUTE_BASE); + for (MethodInfo method : beanClass.methods()) { List routes = new LinkedList<>(); AnnotationInstance routeAnnotation = annotationStore.getAnnotation(method, ROUTE); if (routeAnnotation != null) { @@ -114,7 +125,8 @@ void validateBeanDeployment( } if (!routes.isEmpty()) { LOGGER.debugf("Found route handler business method %s declared on %s", method, bean); - routeHandlerBusinessMethods.produce(new AnnotatedRouteHandlerBuildItem(bean, method, routes)); + routeHandlerBusinessMethods + .produce(new AnnotatedRouteHandlerBuildItem(bean, method, routes, routeBaseAnnotation)); } // AnnotationInstance filterAnnotation = annotationStore.getAnnotation(method, ROUTE_FILTER); @@ -156,12 +168,68 @@ void addAdditionalRoutes( BuildProducer filterProducer) throws IOException { ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); + for (AnnotatedRouteHandlerBuildItem businessMethod : routeHandlerBusinessMethods) { + String handlerClass = generateHandler(businessMethod.getBean(), businessMethod.getMethod(), classOutput); reflectiveClasses.produce(new ReflectiveClassBuildItem(false, false, handlerClass)); Handler routingHandler = recorder.createHandler(handlerClass); + + AnnotationInstance routeBaseAnnotation = businessMethod.getRouteBase(); + String pathPrefix = null; + String[] produces = null; + String[] consumes = null; + + if (routeBaseAnnotation != null) { + AnnotationValue pathPrefixValue = routeBaseAnnotation.value(VALUE_PATH); + if (pathPrefixValue != null) { + pathPrefix = pathPrefixValue.asString(); + } + AnnotationValue producesValue = routeBaseAnnotation.value(VALUE_PRODUCES); + if (producesValue != null) { + produces = producesValue.asStringArray(); + } + AnnotationValue consumesValue = routeBaseAnnotation.value(VALUE_CONSUMES); + if (consumesValue != null) { + consumes = consumesValue.asStringArray(); + } + } + for (AnnotationInstance routeAnnotation : businessMethod.getRoutes()) { - Route route = annotationProxy.builder(routeAnnotation, Route.class).build(classOutput); + AnnotationProxyBuilder builder = annotationProxy.builder(routeAnnotation, Route.class); + AnnotationValue regexValue = routeAnnotation.value(VALUE_REGEX); + AnnotationValue pathValue = routeAnnotation.value(VALUE_PATH); + + if (regexValue == null) { + if (pathPrefix != null) { + StringBuilder path = new StringBuilder(); + path.append(pathPrefix); + if (pathValue == null) { + path.append(DASH); + path.append(dashify(businessMethod.getMethod().name())); + } else { + String pathValueStr = pathValue.asString(); + if (!pathValueStr.startsWith(DASH)) { + path.append(DASH); + } + path.append(pathValue.asString()); + } + builder.withValue(VALUE_PATH, path.toString()); + } else { + if (pathValue == null) { + builder.withDefaultValue(VALUE_PATH, dashify(businessMethod.getMethod().name())); + } + } + } + + if (routeAnnotation.value(VALUE_PRODUCES) == null && produces != null) { + builder.withValue(VALUE_PRODUCES, produces); + } + if (routeAnnotation.value(VALUE_CONSUMES) == null && consumes != null) { + builder.withValue(VALUE_CONSUMES, consumes); + } + + Route route = builder.build(classOutput); Function routeFunction = recorder.createRouteFunction(route, bodyHandler.getHandler()); AnnotationValue typeValue = routeAnnotation.value("type"); @@ -314,4 +382,17 @@ private String generateHandler(BeanInfo bean, MethodInfo method, ClassOutput cla invokerCreator.close(); return generatedName.replace('/', '.'); } + + private static String dashify(String value) { + StringBuilder ret = new StringBuilder(); + char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (i != 0 && i != (chars.length - 1) && Character.isUpperCase(c)) { + ret.append('-'); + } + ret.append(Character.toLowerCase(c)); + } + return ret.toString(); + } } diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java index 1b021317a6dfb..bf0a2a7273522 100644 --- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java @@ -64,7 +64,7 @@ void rxHello(io.vertx.reactivex.ext.web.RoutingContext context) { context.response().setStatusCode(200).end("Hello " + (name != null ? name : "world") + "!"); } - @Route(path = "/bzuk") + @Route // path is derived from the method name void bzuk(RoutingExchange exchange) { exchange.ok("Hello " + exchange.getParam("name").orElse("world") + "!"); } diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/base/RouteBaseTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/base/RouteBaseTest.java new file mode 100644 index 0000000000000..2d6547ca7e7b4 --- /dev/null +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/base/RouteBaseTest.java @@ -0,0 +1,81 @@ +package io.quarkus.vertx.web.base; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.vertx.ext.web.RoutingContext; + +public class RouteBaseTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class)); + + @Test + public void testPath() { + when().get("/hello").then().statusCode(200).body(is("Hello world!")); + when().get("/simple/hello").then().statusCode(200).body(is("Hello another world!")); + when().get("/some/foo").then().statusCode(200).body(is("Hello foo!")); + } + + @Test + public void testProduces() { + given().header("Accept", "application/json").when().get("/ping").then().statusCode(200).body(is("{\"ping\":\"pong\"}")); + given().header("Accept", "text/html").when().get("/ping").then().statusCode(200).body(is("pong")); + } + + @RouteBase + static class SimpleBean { + + @Route(path = "hello") // -> "/simple-bean/hello" + void hello(RoutingContext context) { + context.response().end("Hello world!"); + } + + } + + @RouteBase(path = "simple") + static class AnotherBean { + + @Route // path is derived from the method name -> "/simple/hello" + void hello(RoutingContext context) { + context.response().end("Hello another world!"); + } + + @Route(regex = ".*foo") // base path is ignored + void helloRegex(RoutingContext context) { + context.response().end("Hello foo!"); + } + + } + + @RouteBase(produces = "text/html") + static class HtmlEndpoint { + + @Route(path = "ping") + void ping(RoutingContext context) { + context.response().end("pong"); + } + + } + + @RouteBase(produces = "application/json") + static class JsonEndpoint { + + @Route(path = "ping") + void ping(RoutingContext context) { + context.response().end("{\"ping\":\"pong\"}"); + } + + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java index 1e0004e8c3dbf..c081acd6ce0b2 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java @@ -17,6 +17,9 @@ * The target business method must return {@code void} and accept exactly one argument. must return {@code void} and accept * exactly one argument. The type of the argument can be {@link io.vertx.ext.web.RoutingContext}, * {@link io.vertx.reactivex.ext.web.RoutingContext} or {@link io.quarkus.vertx.web.RoutingExchange}. + *

+ * If neither {@link #path()()} nor {@link #regex()} is set the route will match a path derived from the name of the + * method. This is done by de-camel-casing the name and then joining the segments with hyphens. */ @Repeatable(Routes.class) @Retention(RetentionPolicy.RUNTIME) @@ -58,13 +61,15 @@ int order() default 0; /** - * + * Used for content-based routing. + * * @see io.vertx.ext.web.Route#produces(String) * @return the produced content types */ String[] produces() default {}; /** + * Used for content-based routing. * * @see io.vertx.ext.web.Route#consumes(String) * @return the consumed content types diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RouteBase.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RouteBase.java new file mode 100644 index 0000000000000..13dae429743c1 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RouteBase.java @@ -0,0 +1,36 @@ +package io.quarkus.vertx.web; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to configure some defaults for reactive routes declared on a class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RouteBase { + + /** + * The value is used as a prefix for any route method declared on the class where {@link Route#path()} is used. + * + * @return the path prefix + */ + String path() default ""; + + /** + * The values are used for any route method declared on the class where {@link Route#produces()} is empty. + * + * @return the produced content types + */ + String[] produces() default {}; + + /** + * The values are used for any route method declared on the class where {@link Route#consumes()} is empty. + * + * @return the consumed content types + */ + String[] consumes() default {}; + +} From cfd9806ef028f6a3c70369f825797a10cb108c69 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 22 Nov 2019 17:35:52 +0100 Subject: [PATCH 061/602] Update docs --- docs/src/main/asciidoc/reactive-routes.adoc | 33 ++++++++++++++----- .../main/java/io/quarkus/vertx/web/Route.java | 4 ++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/src/main/asciidoc/reactive-routes.adoc b/docs/src/main/asciidoc/reactive-routes.adoc index 4bd881e7063c5..98a5052cd263e 100644 --- a/docs/src/main/asciidoc/reactive-routes.adoc +++ b/docs/src/main/asciidoc/reactive-routes.adoc @@ -55,14 +55,14 @@ import javax.enterprise.context.ApplicationScoped; @ApplicationScoped <1> public class MyDeclarativeRoutes { - - @Route(path = "/", methods = HttpMethod.GET) <2> - public void handle(RoutingContext rc) { <3> + // neither path nor regex is set - match a path derived from the method name + @Route(methods = HttpMethod.GET) <2> + void hello(RoutingContext rc) { <3> rc.response().end("hello"); } - @Route(path = "/hello", methods = HttpMethod.GET) - public void greetings(RoutingExchange ex) { <4> + @Route(path = "/greetings", methods = HttpMethod.GET) + void greetings(RoutingExchange ex) { <4> ex.ok("hello " + ex.getParam("name").orElse("world")); } } @@ -77,9 +77,8 @@ More details about using the `RoutingContext` is available in the https://vertx. The `@Route` annotation allows to configure: -* The `path` - using the https://vertx.io/docs/vertx-web/java/#_capturing_path_parameters[Vert.x Web format] -* The `regex` - when `path` is not used. -See https://vertx.io/docs/vertx-web/java/#_routing_with_regular_expressions[for more details] +* The `path` - for routing by path, using the https://vertx.io/docs/vertx-web/java/#_capturing_path_parameters[Vert.x Web format] +* The `regex` - for routing with regular expressions, see https://vertx.io/docs/vertx-web/java/#_routing_with_regular_expressions[for more details] * The `methods` - the HTTP verb triggering the route such as `GET`, `POST`... * The `type` - it can be _normal_ (non-blocking), _blocking_ (method dispatched on a worker thread), or _failure_ to indicate that this route is called on failures * The `order` - the order of the route when several routes are involved in handling the incoming request. @@ -109,6 +108,24 @@ public void route(RoutingContext rc) { Each route can use different paths, methods... +=== `@RouteBase` + +This annotation can be used to configure some defaults for reactive routes declared on a class. + +[source,java] +---- +@RouteBase(path = "simple", produces = "text/plain") <1> +public class SimpleRoutes { + + @Route(path = "ping") // the final path is /simple/ping + void ping(RoutingContext rc) { + rc.response().end("pong"); + } +} +---- +<1> The `path` value is used as a prefix for any route method declared on the class where `Route#path()` is used. The `produces` value is used for content-based routing for all routes where `Route#produces()` is empty. + + == Using the Vert.x Web Router You can also register your route directly on the _HTTP routing layer_ by registering routes directly on the `Router` object. diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java index c081acd6ce0b2..858629924829b 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java @@ -18,7 +18,9 @@ * exactly one argument. The type of the argument can be {@link io.vertx.ext.web.RoutingContext}, * {@link io.vertx.reactivex.ext.web.RoutingContext} or {@link io.quarkus.vertx.web.RoutingExchange}. *

- * If neither {@link #path()()} nor {@link #regex()} is set the route will match a path derived from the name of the + * If both {@link #path()} and {@link #regex()} are set the regular expression is used for matching. + *

+ * If neither {@link #path()} nor {@link #regex()} is set the route will match a path derived from the name of the * method. This is done by de-camel-casing the name and then joining the segments with hyphens. */ @Repeatable(Routes.class) From 92d4024fae0d6873a53ff3e3b280dfbe4106551d Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sat, 23 Nov 2019 10:08:52 +0100 Subject: [PATCH 062/602] chore: Uprade httpclient to 4.5.10 and httpcore to 4.4.12 (both are patch upgrades) --- bom/runtime/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 012ce836bdf4f..690a3cf84ab7c 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -103,8 +103,8 @@ 3.8.3 3.8.3-01 - 4.5.9 - 4.4.11 + 4.5.10 + 4.4.12 4.1.4 9.0.1 2.3.2 From 4e371fc2cf558332e91b72552205f076fe25cbe1 Mon Sep 17 00:00:00 2001 From: soberich <25544967+soberich@users.noreply.github.com> Date: Sun, 17 Nov 2019 11:51:19 +0100 Subject: [PATCH 063/602] Allow multiple source directories. Kotlin, annotation processors and/or multi-module projects required multiple directories as sourceDir. --- .../quarkus/gradle/QuarkusPluginExtension.java | 14 ++++++++------ .../io/quarkus/gradle/tasks/QuarkusDev.java | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 2232888290986..521651982e08f 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -2,9 +2,11 @@ import java.io.File; import java.util.Set; +import java.util.regex.Pattern; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.resolver.AppModelResolver; @@ -33,7 +35,7 @@ public QuarkusPluginExtension(Project project) { public File outputDirectory() { if (outputDirectory == null) outputDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getOutput().getClassesDirs().getAsPath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs().getAsPath(); return new File(outputDirectory); } @@ -41,17 +43,17 @@ public File outputDirectory() { public File outputConfigDirectory() { if (outputConfigDirectory == null) { outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getOutput().getResourcesDir().getAbsolutePath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir().getAbsolutePath(); } return new File(outputConfigDirectory); } - public File sourceDir() { + public Set sourceDir() { if (sourceDir == null) { sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getAllJava().getSourceDirectories().getAsPath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava().getSourceDirectories().getAsPath(); } - return new File(sourceDir); + return project.getLayout().files(sourceDir.split(Pattern.quote(File.pathSeparator))).getFiles(); } public File workingDir() { @@ -71,7 +73,7 @@ public String finalName() { public Set resourcesDir() { return project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getResources().getSrcDirs(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getResources().getSrcDirs(); } public AppArtifact getAppArtifact() { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 4c88072bf067d..744e388fca23a 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -1,5 +1,8 @@ package io.quarkus.gradle.tasks; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toSet; + import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -26,6 +29,7 @@ import java.util.concurrent.Executors; import java.util.jar.Attributes; import java.util.jar.Manifest; +import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -42,6 +46,7 @@ import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -94,12 +99,12 @@ public void setBuildDir(File buildDir) { } @Optional - @InputDirectory - public File getSourceDir() { + @InputFiles + public Set getSourceDir() { if (sourceDir == null) - return extension().sourceDir(); + return Collections.unmodifiableSet(new HashSet<>(extension().sourceDir())); else - return new File(sourceDir); + return getProject().getLayout().files(sourceDir.split(Pattern.quote(File.pathSeparator))).getFiles(); } @Option(description = "Set source directory", option = "source-dir") @@ -151,7 +156,7 @@ public void startDev() { Project project = getProject(); QuarkusPluginExtension extension = (QuarkusPluginExtension) project.getExtensions().findByName("quarkus"); - if (!getSourceDir().isDirectory()) { + if (getSourceDir().stream().anyMatch(file -> !file.isDirectory())) { throw new GradleException("The `src/main/java` directory is required, please create it."); } @@ -309,7 +314,8 @@ public void startDev() { DevModeContext.ModuleInfo moduleInfo = new DevModeContext.ModuleInfo( project.getName(), project.getProjectDir().getAbsolutePath(), - Collections.singleton(getSourceDir().getAbsolutePath()), + getSourceDir().stream().map(File::getAbsolutePath) + .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)), extension.outputDirectory().getAbsolutePath(), res); context.getModules().add(moduleInfo); From 1dd21f72b5704347439863efa7e69e6ee7ba9e00 Mon Sep 17 00:00:00 2001 From: soberich <25544967+soberich@users.noreply.github.com> Date: Sun, 17 Nov 2019 12:35:07 +0100 Subject: [PATCH 064/602] Quote on splitting for Windows. --- .../src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java index 82e6e7d6590ee..d2dbaf7514034 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java +++ b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java @@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -214,7 +215,7 @@ private boolean checkForClassFilesChangesInModule(DevModeContext.ModuleInfo modu } try { - for (String folder : module.getClassesPath().split(File.pathSeparator)) { + for (String folder : module.getClassesPath().split(Pattern.quote(File.pathSeparator))) { final Path moduleClassesPath = Paths.get(folder); try (final Stream classesStream = Files.walk(moduleClassesPath)) { final Set classFilePaths = classesStream From 81e870010c4c4f7a79b42718eaf2928e09fbbdca Mon Sep 17 00:00:00 2001 From: soberich <25544967+soberich@users.noreply.github.com> Date: Sat, 23 Nov 2019 18:19:52 +0100 Subject: [PATCH 065/602] Gradle 6.x compatibility. --- .../io/quarkus/gradle/tasks/QuarkusBuild.java | 6 +- .../io/quarkus/gradle/tasks/QuarkusDev.java | 6 +- .../gradle/tasks/QuarkusListExtensions.java | 6 +- .../quarkus/gradle/tasks/QuarkusNative.java | 110 +++++++++--------- 4 files changed, 63 insertions(+), 65 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java index 7097db1843a49..7e2d6e8a2e801 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java @@ -23,7 +23,7 @@ */ public class QuarkusBuild extends QuarkusTask { - private boolean uberJar; + private Boolean uberJar; private List ignoredEntries = new ArrayList<>(); @@ -33,12 +33,12 @@ public QuarkusBuild() { @Optional @Input - public boolean isUberJar() { + public Boolean isUberJar() { return uberJar; } @Option(description = "Set to true if the build task should build an uberjar", option = "uber-jar") - public void setUberJar(boolean uberJar) { + public void setUberJar(Boolean uberJar) { this.uberJar = uberJar; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 744e388fca23a..6427346f34977 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -80,7 +80,7 @@ public class QuarkusDev extends QuarkusTask { private String jvmArgs; - private boolean preventnoverify = false; + private Boolean preventnoverify = false; public QuarkusDev() { super("Development mode: enables hot deployment with background compilation"); @@ -139,14 +139,14 @@ public void setJvmArgs(String jvmArgs) { @Optional @Input - public boolean isPreventnoverify() { + public Boolean isPreventnoverify() { return preventnoverify; } @Option(description = "value is intended to be set to true when some generated bytecode is" + " erroneous causing the JVM to crash when the verify:none option is set " + "(which is on by default)", option = "prevent-noverify") - public void setPreventnoverify(boolean preventnoverify) { + public void setPreventnoverify(Boolean preventnoverify) { this.preventnoverify = preventnoverify; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index 3850f4797dda4..02f78e8d563a6 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -17,7 +17,7 @@ */ public class QuarkusListExtensions extends QuarkusPlatformTask { - private boolean all = true; + private Boolean all = true; private String format = "concise"; @@ -25,12 +25,12 @@ public class QuarkusListExtensions extends QuarkusPlatformTask { @Optional @Input - public boolean isAll() { + public Boolean isAll() { return all; } @Option(description = "List all extensions or just the installable.", option = "all") - public void setAll(boolean all) { + public void setAll(Boolean all) { this.all = all; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index b1b95b4727f17..690e43a584dfe 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -29,31 +29,31 @@ */ public class QuarkusNative extends QuarkusTask { - private boolean reportErrorsAtRuntime = false; + private Boolean reportErrorsAtRuntime = false; - private boolean debugSymbols = false; + private Boolean debugSymbols = false; - private boolean debugBuildProcess; + private Boolean debugBuildProcess; - private boolean cleanupServer; + private Boolean cleanupServer; - private boolean enableHttpUrlHandler; + private Boolean enableHttpUrlHandler; - private boolean enableHttpsUrlHandler; + private Boolean enableHttpsUrlHandler; - private boolean enableAllSecurityServices; + private Boolean enableAllSecurityServices; - private boolean enableIsolates; + private Boolean enableIsolates; private String graalvmHome = System.getenv("GRAALVM_HOME"); - private boolean enableServer = false; + private Boolean enableServer = false; - private boolean enableJni = false; + private Boolean enableJni = false; - private boolean autoServiceLoaderRegistration = false; + private Boolean autoServiceLoaderRegistration = false; - private boolean dumpProxies = false; + private Boolean dumpProxies = false; private String nativeImageXmx; @@ -63,19 +63,19 @@ public class QuarkusNative extends QuarkusTask { private String dockerBuild; - private boolean enableVMInspection = false; + private Boolean enableVMInspection = false; - private boolean enableFallbackImages = false; + private Boolean enableFallbackImages = false; - private boolean fullStackTraces = true; + private Boolean fullStackTraces = true; - private boolean enableReports; + private Boolean enableReports; private List additionalBuildArgs; - private boolean addAllCharsets = false; + private Boolean addAllCharsets = false; - private boolean reportExceptionStackTraces = true; + private Boolean reportExceptionStackTraces = true; public QuarkusNative() { super("Building a native image"); @@ -83,113 +83,113 @@ public QuarkusNative() { @Optional @Input - public boolean isAddAllCharsets() { + public Boolean isAddAllCharsets() { return addAllCharsets; } @Option(description = "Should all Charsets supported by the host environment be included in the native image", option = "add-all-charsets") - public void setAddAllCharsets(final boolean addAllCharsets) { + public void setAddAllCharsets(final Boolean addAllCharsets) { this.addAllCharsets = addAllCharsets; } @Optional @Input - public boolean isReportErrorsAtRuntime() { + public Boolean isReportErrorsAtRuntime() { return reportErrorsAtRuntime; } @Option(description = "Report errors at runtime", option = "report-errors-runtime") - public void setReportErrorsAtRuntime(boolean reportErrorsAtRuntime) { + public void setReportErrorsAtRuntime(Boolean reportErrorsAtRuntime) { this.reportErrorsAtRuntime = reportErrorsAtRuntime; } @Optional @Input - public boolean isDebugSymbols() { + public Boolean isDebugSymbols() { return debugSymbols; } @Option(description = "Specify if debug symbols should be set", option = "debug-symbols") - public void setDebugSymbols(boolean debugSymbols) { + public void setDebugSymbols(Boolean debugSymbols) { this.debugSymbols = debugSymbols; } @Optional @Input - public boolean isDebugBuildProcess() { + public Boolean isDebugBuildProcess() { return debugBuildProcess; } @Option(description = "Specify if debug is set during build process", option = "debug-build-process") - public void setDebugBuildProcess(boolean debugBuildProcess) { + public void setDebugBuildProcess(Boolean debugBuildProcess) { this.debugBuildProcess = debugBuildProcess; } @Optional @Input - public boolean isCleanupServer() { + public Boolean isCleanupServer() { return cleanupServer; } @Option(description = "Cleanup server", option = "cleanup-server") - public void setCleanupServer(boolean cleanupServer) { + public void setCleanupServer(Boolean cleanupServer) { this.cleanupServer = cleanupServer; } @Optional @Input - public boolean isEnableHttpUrlHandler() { + public Boolean isEnableHttpUrlHandler() { return enableHttpUrlHandler; } @Optional @Input - private boolean isEnableFallbackImages() { + public Boolean isEnableFallbackImages() { return enableFallbackImages; } @Option(description = "Enable the GraalVM native image compiler to generate Fallback Images in case of compilation error. " + "Careful: these are not as efficient as normal native images.", option = "enable-fallback-images") - public void setEnableFallbackImages(boolean enableFallbackImages) { + public void setEnableFallbackImages(Boolean enableFallbackImages) { this.enableFallbackImages = enableFallbackImages; } @Option(description = "Specify if http url handler is enabled", option = "enable-http-url-handler") - public void setEnableHttpUrlHandler(boolean enableHttpUrlHandler) { + public void setEnableHttpUrlHandler(Boolean enableHttpUrlHandler) { this.enableHttpUrlHandler = enableHttpUrlHandler; } @Optional @Input - public boolean isEnableHttpsUrlHandler() { + public Boolean isEnableHttpsUrlHandler() { return enableHttpsUrlHandler; } @Option(description = "Specify if https url handler is enabled", option = "enable-https-url-handler") - public void setEnableHttpsUrlHandler(boolean enableHttpsUrlHandler) { + public void setEnableHttpsUrlHandler(Boolean enableHttpsUrlHandler) { this.enableHttpsUrlHandler = enableHttpsUrlHandler; } @Optional @Input - public boolean isEnableAllSecurityServices() { + public Boolean isEnableAllSecurityServices() { return enableAllSecurityServices; } @Option(description = "Enable all security services", option = "enable-all-security-services") - public void setEnableAllSecurityServices(boolean enableAllSecurityServices) { + public void setEnableAllSecurityServices(Boolean enableAllSecurityServices) { this.enableAllSecurityServices = enableAllSecurityServices; } @Optional @Input - public boolean isEnableIsolates() { + public Boolean isEnableIsolates() { return enableIsolates; } @Option(description = "Report errors at runtime", option = "enable-isolates") - public void setEnableIsolates(boolean enableIsolates) { + public void setEnableIsolates(Boolean enableIsolates) { this.enableIsolates = enableIsolates; } @@ -206,45 +206,45 @@ public void setGraalvmHome(String graalvmHome) { @Optional @Input - public boolean isEnableServer() { + public Boolean isEnableServer() { return enableServer; } @Option(description = "Enable server", option = "enable-server") - public void setEnableServer(boolean enableServer) { + public void setEnableServer(Boolean enableServer) { this.enableServer = enableServer; } @Optional @Input - public boolean isEnableJni() { + public Boolean isEnableJni() { return enableJni; } @Option(description = "Enable jni", option = "enable-jni") - public void setEnableJni(boolean enableJni) { + public void setEnableJni(Boolean enableJni) { this.enableJni = enableJni; } @Optional @Input - public boolean isAutoServiceLoaderRegistration() { + public Boolean getAutoServiceLoaderRegistration() { return autoServiceLoaderRegistration; } @Option(description = "Auto ServiceLoader registration", option = "auto-service-loader-registration") - public void setAutoServiceLoaderRegistration(boolean autoServiceLoaderRegistration) { + public void setAutoServiceLoaderRegistration(Boolean autoServiceLoaderRegistration) { this.autoServiceLoaderRegistration = autoServiceLoaderRegistration; } @Optional @Input - public boolean isDumpProxies() { + public Boolean isDumpProxies() { return dumpProxies; } @Option(description = "Dump proxies", option = "dump-proxies") - public void setDumpProxies(boolean dumpProxies) { + public void setDumpProxies(Boolean dumpProxies) { this.dumpProxies = dumpProxies; } @@ -278,13 +278,11 @@ public String getDockerBuild() { } @Option(description = "Container runtime", option = "container-runtime") - @Optional public void setContainerRuntime(String containerRuntime) { this.containerRuntime = containerRuntime; } @Option(description = "Container runtime options", option = "container-runtime-options") - @Optional public void setContainerRuntimeOptions(String containerRuntimeOptions) { this.containerRuntimeOptions = containerRuntimeOptions; } @@ -296,29 +294,29 @@ public void setDockerBuild(String dockerBuild) { @Optional @Input - public boolean isEnableVMInspection() { + public Boolean isEnableVMInspection() { return enableVMInspection; } @Option(description = "Enable VM inspection", option = "enable-vm-inspection") - public void setEnableVMInspection(boolean enableVMInspection) { + public void setEnableVMInspection(Boolean enableVMInspection) { this.enableVMInspection = enableVMInspection; } @Optional @Input - public boolean isFullStackTraces() { + public Boolean isFullStackTraces() { return fullStackTraces; } @Option(description = "Specify full stacktraces", option = "full-stacktraces") - public void setFullStackTraces(boolean fullStackTraces) { + public void setFullStackTraces(Boolean fullStackTraces) { this.fullStackTraces = fullStackTraces; } @Optional @Input - public boolean isEnableReports() { + public Boolean isEnableReports() { return enableReports; } @@ -329,7 +327,7 @@ public void setDisableReports(boolean disableReports) { } @Option(description = "Enable reports", option = "enable-reports") - public void setEnableReports(boolean enableReports) { + public void setEnableReports(Boolean enableReports) { this.enableReports = enableReports; } @@ -346,12 +344,12 @@ public void setAdditionalBuildArgs(List additionalBuildArgs) { @Optional @Input - public boolean isReportExceptionStackTraces() { + public Boolean isReportExceptionStackTraces() { return reportExceptionStackTraces; } @Option(description = "Show exception stack traces for exceptions during image building", option = "report-exception-stack-traces") - public void setReportExceptionStackTraces(boolean reportExceptionStackTraces) { + public void setReportExceptionStackTraces(Boolean reportExceptionStackTraces) { this.reportExceptionStackTraces = reportExceptionStackTraces; } From 43ea59fae618aac214f848b65393fb1f32e072b5 Mon Sep 17 00:00:00 2001 From: Maciej Swiderski Date: Sun, 24 Nov 2019 09:21:17 +0100 Subject: [PATCH 066/602] added decision table section into kogito guide --- docs/src/main/asciidoc/kogito.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/src/main/asciidoc/kogito.adoc b/docs/src/main/asciidoc/kogito.adoc index b40de1ac26424..eb349c37f27f6 100644 --- a/docs/src/main/asciidoc/kogito.adoc +++ b/docs/src/main/asciidoc/kogito.adoc @@ -397,6 +397,20 @@ curl -X GET http://localhost:8080/persons \ To learn more about persistence in Kogito visit https://github.com/kiegroup/kogito-runtimes/wiki/Persistence[this page] +== Using decision tables + +Kogito allows to define business rules as decision tables using the Microsoft Excel file formats. +To be able to use such assets in your application, an additional dependency is required: + +[source,xml] +---- + + org.kie.kogito + drools-decisiontables + +---- + +Once the dependency is added to the project, decision tables in `xls` or `xlsx` format can be properly handled. == References From 39c9d8c8acc00e976e2acf562d0826fa5d6012b2 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sun, 24 Nov 2019 11:10:44 +0530 Subject: [PATCH 067/602] issue-5683 remove unused "libDir" param --- .../maven/src/main/java/io/quarkus/maven/BuildMojo.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java index d218a1196d8da..b73e0445db542 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java @@ -94,12 +94,6 @@ public class BuildMojo extends AbstractMojo { @Parameter(defaultValue = "${project.build.directory}") private File buildDir; - /** - * The directory for library jars - */ - @Parameter(defaultValue = "${project.build.directory}/lib") - private File libDir; - @Parameter(defaultValue = "${project.build.finalName}") private String finalName; From a03446ba69588be2856e0197c455f7dd4e18f474 Mon Sep 17 00:00:00 2001 From: Vincent Sevel Date: Sat, 23 Nov 2019 08:27:43 +0100 Subject: [PATCH 068/602] Vault Extension Serialization Error in Native Mode at Runtime #5636 --- .../vault/runtime/client/dto/VaultAppRoleAuthAuthMetadata.java | 2 +- .../quarkus/vault/runtime/client/dto/VaultAppRoleAuthBody.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthAuthMetadata.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthAuthMetadata.java index 0912d1392fa1f..0be2406213094 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthAuthMetadata.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthAuthMetadata.java @@ -1,4 +1,4 @@ package io.quarkus.vault.runtime.client.dto; -public class VaultAppRoleAuthAuthMetadata { +public class VaultAppRoleAuthAuthMetadata implements VaultModel { } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthBody.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthBody.java index 8e9b9ccc56c0f..3a7be226b7537 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthBody.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/dto/VaultAppRoleAuthBody.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public class VaultAppRoleAuthBody { +public class VaultAppRoleAuthBody implements VaultModel { @JsonProperty("role_id") public String roleId; From 0f963a4399c19659514ce113cb4f951f1e55ff92 Mon Sep 17 00:00:00 2001 From: Vincent Sevel Date: Sun, 24 Nov 2019 09:28:35 +0100 Subject: [PATCH 069/602] indirection --- docs/src/main/asciidoc/mailer.adoc | 18 ++++++++++++++++++ docs/src/main/asciidoc/vault.adoc | 15 +++++++++++++++ .../java/io/quarkus/vault/VaultITCase.java | 14 ++++++++++++++ .../resources/application-vault.properties | 2 ++ 4 files changed, 49 insertions(+) diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index b3faf47c9eb31..148c0ced3d535 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -62,6 +62,24 @@ quarkus.mailer.username=.... quarkus.mailer.password=.... ---- +[NOTE] +==== +It is recommended to encrypt any sensitive data, such as the `quarkus.mailer.password`. +One approach is to save the value into a secure store like HashiCorp Vault, and refer to it from the configuration. +Assuming for instance that Vault contains key `mail-password` at path `myapps/myapp/myconfig`, then the mailer +extension can be simply configured as: +``` +... +# path within the kv secret engine where is located the application sensitive configuration +quarkus.vault.secret-config-kv-path=myapps/myapp/myconfig + +... +quarkus.mailer.password=${mail-password} +``` +Please note that the password value is evaluated only once, at startup time. If `mail-password` was changed in Vault, +the only way to get the new value would be to restart the application. +==== + [TIP] For more information about the Mailer extension configuration please refer to the <>. diff --git a/docs/src/main/asciidoc/vault.adoc b/docs/src/main/asciidoc/vault.adoc index febab24c1063e..0b59095b44254 100644 --- a/docs/src/main/asciidoc/vault.adoc +++ b/docs/src/main/asciidoc/vault.adoc @@ -426,6 +426,21 @@ quarkus.datasource.credentials-provider = mydatabase quarkus.hibernate-orm.database.generation=drop-and-create ---- +[NOTE] +==== +Another way to specify the datasource password is with property indirection. Assuming that vault path +`myapps/vault-quickstart/config` contains key `my-db-password`, all is required on the datasource configuration is: +``` +quarkus.datasource.username = sarah +quarkus.datasource.password = ${my-db-password} +``` +The only drawback is that the password will never be fetched again from vault after the initial property loading. +This means that if the db password was changed while running, the application would have to be restarted after +vault has been updated with the new password. +This contrasts with the credentials provider approach, which fetches the password from vault everytime a connection +creation is attempted. +==== + Restart the application after rebuilding it, and test it with the new endpoint: [source,shell] diff --git a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultITCase.java index 5a0cb0ebfcbb4..3de415655b5fc 100644 --- a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultITCase.java +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultITCase.java @@ -56,6 +56,8 @@ public class VaultITCase { private static final Logger log = Logger.getLogger(VaultITCase.class); + public static final String MY_PASSWORD = "my-password"; + @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) @@ -70,6 +72,9 @@ public class VaultITCase { @ConfigProperty(name = PASSWORD_PROPERTY_NAME) String someSecret; + @ConfigProperty(name = MY_PASSWORD) + String someSecretThroughIndirection; + @Test public void credentialsProvider() { Properties staticCredentials = credentialsProvider.getCredentials("static"); @@ -89,6 +94,15 @@ public void config() { assertEquals(DB_PASSWORD, value); } + @Test + public void configPropertyIndirection() { + assertEquals(DB_PASSWORD, someSecretThroughIndirection); + + Config config = ConfigProviderResolver.instance().getConfig(); + String value = config.getValue(MY_PASSWORD, String.class); + assertEquals(DB_PASSWORD, value); + } + @Test public void secretV1() { Map secrets = kvSecretEngine.readSecret(APP_SECRET_PATH); diff --git a/integration-tests/vault/src/test/resources/application-vault.properties b/integration-tests/vault/src/test/resources/application-vault.properties index bab16e714efb8..91bfb467a5c7e 100644 --- a/integration-tests/vault/src/test/resources/application-vault.properties +++ b/integration-tests/vault/src/test/resources/application-vault.properties @@ -3,6 +3,8 @@ quarkus.vault.authentication.userpass.username=bob quarkus.vault.authentication.userpass.password=sinclair quarkus.vault.secret-config-kv-path=config +my-password=${password} + quarkus.vault.credentials-provider.static.kv-path=config quarkus.vault.credentials-provider.dynamic.database-credentials-role=mydbrole From 7eb50b8dba4dcf40afc7486da3fa2b87dc23b807 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 24 Nov 2019 13:12:01 +0200 Subject: [PATCH 070/602] Revert "Allow multiple source directories for Gradle." --- .../quarkus/dev/RuntimeUpdatesProcessor.java | 3 +- .../gradle/QuarkusPluginExtension.java | 14 +-- .../io/quarkus/gradle/tasks/QuarkusBuild.java | 6 +- .../io/quarkus/gradle/tasks/QuarkusDev.java | 24 ++-- .../gradle/tasks/QuarkusListExtensions.java | 6 +- .../quarkus/gradle/tasks/QuarkusNative.java | 110 +++++++++--------- 6 files changed, 78 insertions(+), 85 deletions(-) diff --git a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java index d2dbaf7514034..82e6e7d6590ee 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java +++ b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java @@ -22,7 +22,6 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -215,7 +214,7 @@ private boolean checkForClassFilesChangesInModule(DevModeContext.ModuleInfo modu } try { - for (String folder : module.getClassesPath().split(Pattern.quote(File.pathSeparator))) { + for (String folder : module.getClassesPath().split(File.pathSeparator)) { final Path moduleClassesPath = Paths.get(folder); try (final Stream classesStream = Files.walk(moduleClassesPath)) { final Set classFilePaths = classesStream diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 521651982e08f..2232888290986 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -2,11 +2,9 @@ import java.io.File; import java.util.Set; -import java.util.regex.Pattern; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginConvention; -import org.gradle.api.tasks.SourceSet; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.resolver.AppModelResolver; @@ -35,7 +33,7 @@ public QuarkusPluginExtension(Project project) { public File outputDirectory() { if (outputDirectory == null) outputDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs().getAsPath(); + .getSourceSets().getByName("main").getOutput().getClassesDirs().getAsPath(); return new File(outputDirectory); } @@ -43,17 +41,17 @@ public File outputDirectory() { public File outputConfigDirectory() { if (outputConfigDirectory == null) { outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir().getAbsolutePath(); + .getSourceSets().getByName("main").getOutput().getResourcesDir().getAbsolutePath(); } return new File(outputConfigDirectory); } - public Set sourceDir() { + public File sourceDir() { if (sourceDir == null) { sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava().getSourceDirectories().getAsPath(); + .getSourceSets().getByName("main").getAllJava().getSourceDirectories().getAsPath(); } - return project.getLayout().files(sourceDir.split(Pattern.quote(File.pathSeparator))).getFiles(); + return new File(sourceDir); } public File workingDir() { @@ -73,7 +71,7 @@ public String finalName() { public Set resourcesDir() { return project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getResources().getSrcDirs(); + .getSourceSets().getByName("main").getResources().getSrcDirs(); } public AppArtifact getAppArtifact() { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java index 7e2d6e8a2e801..7097db1843a49 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java @@ -23,7 +23,7 @@ */ public class QuarkusBuild extends QuarkusTask { - private Boolean uberJar; + private boolean uberJar; private List ignoredEntries = new ArrayList<>(); @@ -33,12 +33,12 @@ public QuarkusBuild() { @Optional @Input - public Boolean isUberJar() { + public boolean isUberJar() { return uberJar; } @Option(description = "Set to true if the build task should build an uberjar", option = "uber-jar") - public void setUberJar(Boolean uberJar) { + public void setUberJar(boolean uberJar) { this.uberJar = uberJar; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 6427346f34977..4c88072bf067d 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -1,8 +1,5 @@ package io.quarkus.gradle.tasks; -import static java.util.stream.Collectors.collectingAndThen; -import static java.util.stream.Collectors.toSet; - import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -29,7 +26,6 @@ import java.util.concurrent.Executors; import java.util.jar.Attributes; import java.util.jar.Manifest; -import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -46,7 +42,6 @@ import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; -import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -80,7 +75,7 @@ public class QuarkusDev extends QuarkusTask { private String jvmArgs; - private Boolean preventnoverify = false; + private boolean preventnoverify = false; public QuarkusDev() { super("Development mode: enables hot deployment with background compilation"); @@ -99,12 +94,12 @@ public void setBuildDir(File buildDir) { } @Optional - @InputFiles - public Set getSourceDir() { + @InputDirectory + public File getSourceDir() { if (sourceDir == null) - return Collections.unmodifiableSet(new HashSet<>(extension().sourceDir())); + return extension().sourceDir(); else - return getProject().getLayout().files(sourceDir.split(Pattern.quote(File.pathSeparator))).getFiles(); + return new File(sourceDir); } @Option(description = "Set source directory", option = "source-dir") @@ -139,14 +134,14 @@ public void setJvmArgs(String jvmArgs) { @Optional @Input - public Boolean isPreventnoverify() { + public boolean isPreventnoverify() { return preventnoverify; } @Option(description = "value is intended to be set to true when some generated bytecode is" + " erroneous causing the JVM to crash when the verify:none option is set " + "(which is on by default)", option = "prevent-noverify") - public void setPreventnoverify(Boolean preventnoverify) { + public void setPreventnoverify(boolean preventnoverify) { this.preventnoverify = preventnoverify; } @@ -156,7 +151,7 @@ public void startDev() { Project project = getProject(); QuarkusPluginExtension extension = (QuarkusPluginExtension) project.getExtensions().findByName("quarkus"); - if (getSourceDir().stream().anyMatch(file -> !file.isDirectory())) { + if (!getSourceDir().isDirectory()) { throw new GradleException("The `src/main/java` directory is required, please create it."); } @@ -314,8 +309,7 @@ public void startDev() { DevModeContext.ModuleInfo moduleInfo = new DevModeContext.ModuleInfo( project.getName(), project.getProjectDir().getAbsolutePath(), - getSourceDir().stream().map(File::getAbsolutePath) - .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)), + Collections.singleton(getSourceDir().getAbsolutePath()), extension.outputDirectory().getAbsolutePath(), res); context.getModules().add(moduleInfo); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index 02f78e8d563a6..3850f4797dda4 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -17,7 +17,7 @@ */ public class QuarkusListExtensions extends QuarkusPlatformTask { - private Boolean all = true; + private boolean all = true; private String format = "concise"; @@ -25,12 +25,12 @@ public class QuarkusListExtensions extends QuarkusPlatformTask { @Optional @Input - public Boolean isAll() { + public boolean isAll() { return all; } @Option(description = "List all extensions or just the installable.", option = "all") - public void setAll(Boolean all) { + public void setAll(boolean all) { this.all = all; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index 690e43a584dfe..b1b95b4727f17 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -29,31 +29,31 @@ */ public class QuarkusNative extends QuarkusTask { - private Boolean reportErrorsAtRuntime = false; + private boolean reportErrorsAtRuntime = false; - private Boolean debugSymbols = false; + private boolean debugSymbols = false; - private Boolean debugBuildProcess; + private boolean debugBuildProcess; - private Boolean cleanupServer; + private boolean cleanupServer; - private Boolean enableHttpUrlHandler; + private boolean enableHttpUrlHandler; - private Boolean enableHttpsUrlHandler; + private boolean enableHttpsUrlHandler; - private Boolean enableAllSecurityServices; + private boolean enableAllSecurityServices; - private Boolean enableIsolates; + private boolean enableIsolates; private String graalvmHome = System.getenv("GRAALVM_HOME"); - private Boolean enableServer = false; + private boolean enableServer = false; - private Boolean enableJni = false; + private boolean enableJni = false; - private Boolean autoServiceLoaderRegistration = false; + private boolean autoServiceLoaderRegistration = false; - private Boolean dumpProxies = false; + private boolean dumpProxies = false; private String nativeImageXmx; @@ -63,19 +63,19 @@ public class QuarkusNative extends QuarkusTask { private String dockerBuild; - private Boolean enableVMInspection = false; + private boolean enableVMInspection = false; - private Boolean enableFallbackImages = false; + private boolean enableFallbackImages = false; - private Boolean fullStackTraces = true; + private boolean fullStackTraces = true; - private Boolean enableReports; + private boolean enableReports; private List additionalBuildArgs; - private Boolean addAllCharsets = false; + private boolean addAllCharsets = false; - private Boolean reportExceptionStackTraces = true; + private boolean reportExceptionStackTraces = true; public QuarkusNative() { super("Building a native image"); @@ -83,113 +83,113 @@ public QuarkusNative() { @Optional @Input - public Boolean isAddAllCharsets() { + public boolean isAddAllCharsets() { return addAllCharsets; } @Option(description = "Should all Charsets supported by the host environment be included in the native image", option = "add-all-charsets") - public void setAddAllCharsets(final Boolean addAllCharsets) { + public void setAddAllCharsets(final boolean addAllCharsets) { this.addAllCharsets = addAllCharsets; } @Optional @Input - public Boolean isReportErrorsAtRuntime() { + public boolean isReportErrorsAtRuntime() { return reportErrorsAtRuntime; } @Option(description = "Report errors at runtime", option = "report-errors-runtime") - public void setReportErrorsAtRuntime(Boolean reportErrorsAtRuntime) { + public void setReportErrorsAtRuntime(boolean reportErrorsAtRuntime) { this.reportErrorsAtRuntime = reportErrorsAtRuntime; } @Optional @Input - public Boolean isDebugSymbols() { + public boolean isDebugSymbols() { return debugSymbols; } @Option(description = "Specify if debug symbols should be set", option = "debug-symbols") - public void setDebugSymbols(Boolean debugSymbols) { + public void setDebugSymbols(boolean debugSymbols) { this.debugSymbols = debugSymbols; } @Optional @Input - public Boolean isDebugBuildProcess() { + public boolean isDebugBuildProcess() { return debugBuildProcess; } @Option(description = "Specify if debug is set during build process", option = "debug-build-process") - public void setDebugBuildProcess(Boolean debugBuildProcess) { + public void setDebugBuildProcess(boolean debugBuildProcess) { this.debugBuildProcess = debugBuildProcess; } @Optional @Input - public Boolean isCleanupServer() { + public boolean isCleanupServer() { return cleanupServer; } @Option(description = "Cleanup server", option = "cleanup-server") - public void setCleanupServer(Boolean cleanupServer) { + public void setCleanupServer(boolean cleanupServer) { this.cleanupServer = cleanupServer; } @Optional @Input - public Boolean isEnableHttpUrlHandler() { + public boolean isEnableHttpUrlHandler() { return enableHttpUrlHandler; } @Optional @Input - public Boolean isEnableFallbackImages() { + private boolean isEnableFallbackImages() { return enableFallbackImages; } @Option(description = "Enable the GraalVM native image compiler to generate Fallback Images in case of compilation error. " + "Careful: these are not as efficient as normal native images.", option = "enable-fallback-images") - public void setEnableFallbackImages(Boolean enableFallbackImages) { + public void setEnableFallbackImages(boolean enableFallbackImages) { this.enableFallbackImages = enableFallbackImages; } @Option(description = "Specify if http url handler is enabled", option = "enable-http-url-handler") - public void setEnableHttpUrlHandler(Boolean enableHttpUrlHandler) { + public void setEnableHttpUrlHandler(boolean enableHttpUrlHandler) { this.enableHttpUrlHandler = enableHttpUrlHandler; } @Optional @Input - public Boolean isEnableHttpsUrlHandler() { + public boolean isEnableHttpsUrlHandler() { return enableHttpsUrlHandler; } @Option(description = "Specify if https url handler is enabled", option = "enable-https-url-handler") - public void setEnableHttpsUrlHandler(Boolean enableHttpsUrlHandler) { + public void setEnableHttpsUrlHandler(boolean enableHttpsUrlHandler) { this.enableHttpsUrlHandler = enableHttpsUrlHandler; } @Optional @Input - public Boolean isEnableAllSecurityServices() { + public boolean isEnableAllSecurityServices() { return enableAllSecurityServices; } @Option(description = "Enable all security services", option = "enable-all-security-services") - public void setEnableAllSecurityServices(Boolean enableAllSecurityServices) { + public void setEnableAllSecurityServices(boolean enableAllSecurityServices) { this.enableAllSecurityServices = enableAllSecurityServices; } @Optional @Input - public Boolean isEnableIsolates() { + public boolean isEnableIsolates() { return enableIsolates; } @Option(description = "Report errors at runtime", option = "enable-isolates") - public void setEnableIsolates(Boolean enableIsolates) { + public void setEnableIsolates(boolean enableIsolates) { this.enableIsolates = enableIsolates; } @@ -206,45 +206,45 @@ public void setGraalvmHome(String graalvmHome) { @Optional @Input - public Boolean isEnableServer() { + public boolean isEnableServer() { return enableServer; } @Option(description = "Enable server", option = "enable-server") - public void setEnableServer(Boolean enableServer) { + public void setEnableServer(boolean enableServer) { this.enableServer = enableServer; } @Optional @Input - public Boolean isEnableJni() { + public boolean isEnableJni() { return enableJni; } @Option(description = "Enable jni", option = "enable-jni") - public void setEnableJni(Boolean enableJni) { + public void setEnableJni(boolean enableJni) { this.enableJni = enableJni; } @Optional @Input - public Boolean getAutoServiceLoaderRegistration() { + public boolean isAutoServiceLoaderRegistration() { return autoServiceLoaderRegistration; } @Option(description = "Auto ServiceLoader registration", option = "auto-service-loader-registration") - public void setAutoServiceLoaderRegistration(Boolean autoServiceLoaderRegistration) { + public void setAutoServiceLoaderRegistration(boolean autoServiceLoaderRegistration) { this.autoServiceLoaderRegistration = autoServiceLoaderRegistration; } @Optional @Input - public Boolean isDumpProxies() { + public boolean isDumpProxies() { return dumpProxies; } @Option(description = "Dump proxies", option = "dump-proxies") - public void setDumpProxies(Boolean dumpProxies) { + public void setDumpProxies(boolean dumpProxies) { this.dumpProxies = dumpProxies; } @@ -278,11 +278,13 @@ public String getDockerBuild() { } @Option(description = "Container runtime", option = "container-runtime") + @Optional public void setContainerRuntime(String containerRuntime) { this.containerRuntime = containerRuntime; } @Option(description = "Container runtime options", option = "container-runtime-options") + @Optional public void setContainerRuntimeOptions(String containerRuntimeOptions) { this.containerRuntimeOptions = containerRuntimeOptions; } @@ -294,29 +296,29 @@ public void setDockerBuild(String dockerBuild) { @Optional @Input - public Boolean isEnableVMInspection() { + public boolean isEnableVMInspection() { return enableVMInspection; } @Option(description = "Enable VM inspection", option = "enable-vm-inspection") - public void setEnableVMInspection(Boolean enableVMInspection) { + public void setEnableVMInspection(boolean enableVMInspection) { this.enableVMInspection = enableVMInspection; } @Optional @Input - public Boolean isFullStackTraces() { + public boolean isFullStackTraces() { return fullStackTraces; } @Option(description = "Specify full stacktraces", option = "full-stacktraces") - public void setFullStackTraces(Boolean fullStackTraces) { + public void setFullStackTraces(boolean fullStackTraces) { this.fullStackTraces = fullStackTraces; } @Optional @Input - public Boolean isEnableReports() { + public boolean isEnableReports() { return enableReports; } @@ -327,7 +329,7 @@ public void setDisableReports(boolean disableReports) { } @Option(description = "Enable reports", option = "enable-reports") - public void setEnableReports(Boolean enableReports) { + public void setEnableReports(boolean enableReports) { this.enableReports = enableReports; } @@ -344,12 +346,12 @@ public void setAdditionalBuildArgs(List additionalBuildArgs) { @Optional @Input - public Boolean isReportExceptionStackTraces() { + public boolean isReportExceptionStackTraces() { return reportExceptionStackTraces; } @Option(description = "Show exception stack traces for exceptions during image building", option = "report-exception-stack-traces") - public void setReportExceptionStackTraces(Boolean reportExceptionStackTraces) { + public void setReportExceptionStackTraces(boolean reportExceptionStackTraces) { this.reportExceptionStackTraces = reportExceptionStackTraces; } From 2968f9f3145daf137da624e205339011679353c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 29 Oct 2019 11:09:34 +0100 Subject: [PATCH 071/602] feat: make fault tolerance not dependents of metrics Fixes #4857 --- .../io/quarkus/deployment/Capabilities.java | 1 + .../deployment/pom.xml | 4 - .../deployment/NoopMetricRegistry.java | 223 ++++++++++++++++++ .../SmallRyeFaultToleranceProcessor.java | 12 +- .../smallrye-fault-tolerance/runtime/pom.xml | 5 - .../deployment/SmallRyeMetricsProcessor.java | 18 +- 6 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java b/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java index 1a3cd698e7bed..5941f8fe2be39 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java @@ -25,6 +25,7 @@ public final class Capabilities extends SimpleBuildItem { public static final String SECURITY_ELYTRON_OAUTH2 = "io.quarkus.elytron.security.oauth2"; public static final String SECURITY_ELYTRON_JDBC = "io.quarkus.elytron.security.jdbc"; public static final String QUARTZ = "io.quarkus.quartz"; + public static final String METRICS = "io.quarkus.metrics"; private final Set capabilities; diff --git a/extensions/smallrye-fault-tolerance/deployment/pom.xml b/extensions/smallrye-fault-tolerance/deployment/pom.xml index e79ca9096eee8..c96bd88afac5c 100644 --- a/extensions/smallrye-fault-tolerance/deployment/pom.xml +++ b/extensions/smallrye-fault-tolerance/deployment/pom.xml @@ -22,10 +22,6 @@ io.quarkus quarkus-arc-deployment - - io.quarkus - quarkus-smallrye-metrics-deployment - io.quarkus quarkus-smallrye-context-propagation-deployment diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java new file mode 100644 index 0000000000000..3f79aca78c3e8 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java @@ -0,0 +1,223 @@ +package io.quarkus.smallrye.faulttolerance.deployment; + +import java.util.Map; +import java.util.SortedMap; +import java.util.SortedSet; + +import javax.inject.Singleton; + +import org.eclipse.microprofile.metrics.*; + +@Singleton +public class NoopMetricRegistry extends MetricRegistry { + + @Override + public T register(String s, T t) throws IllegalArgumentException { + return null; + } + + @Override + public T register(Metadata metadata, T t) throws IllegalArgumentException { + return null; + } + + @Override + public T register(Metadata metadata, T t, Tag... tags) throws IllegalArgumentException { + return null; + } + + @Override + public Counter counter(String s) { + return null; + } + + @Override + public Counter counter(String s, Tag... tags) { + return null; + } + + @Override + public Counter counter(Metadata metadata) { + return null; + } + + @Override + public Counter counter(Metadata metadata, Tag... tags) { + return null; + } + + @Override + public ConcurrentGauge concurrentGauge(String s) { + return null; + } + + @Override + public ConcurrentGauge concurrentGauge(String s, Tag... tags) { + return null; + } + + @Override + public ConcurrentGauge concurrentGauge(Metadata metadata) { + return null; + } + + @Override + public ConcurrentGauge concurrentGauge(Metadata metadata, Tag... tags) { + return null; + } + + @Override + public Histogram histogram(String s) { + return null; + } + + @Override + public Histogram histogram(String s, Tag... tags) { + return null; + } + + @Override + public Histogram histogram(Metadata metadata) { + return null; + } + + @Override + public Histogram histogram(Metadata metadata, Tag... tags) { + return null; + } + + @Override + public Meter meter(String s) { + return null; + } + + @Override + public Meter meter(String s, Tag... tags) { + return null; + } + + @Override + public Meter meter(Metadata metadata) { + return null; + } + + @Override + public Meter meter(Metadata metadata, Tag... tags) { + return null; + } + + @Override + public Timer timer(String s) { + return null; + } + + @Override + public Timer timer(String s, Tag... tags) { + return null; + } + + @Override + public Timer timer(Metadata metadata) { + return null; + } + + @Override + public Timer timer(Metadata metadata, Tag... tags) { + return null; + } + + @Override + public boolean remove(String s) { + return false; + } + + @Override + public boolean remove(MetricID metricID) { + return false; + } + + @Override + public void removeMatching(MetricFilter metricFilter) { + + } + + @Override + public SortedSet getNames() { + return null; + } + + @Override + public SortedSet getMetricIDs() { + return null; + } + + @Override + public SortedMap getGauges() { + return null; + } + + @Override + public SortedMap getGauges(MetricFilter metricFilter) { + return null; + } + + @Override + public SortedMap getCounters() { + return null; + } + + @Override + public SortedMap getCounters(MetricFilter metricFilter) { + return null; + } + + @Override + public SortedMap getConcurrentGauges() { + return null; + } + + @Override + public SortedMap getConcurrentGauges(MetricFilter metricFilter) { + return null; + } + + @Override + public SortedMap getHistograms() { + return null; + } + + @Override + public SortedMap getHistograms(MetricFilter metricFilter) { + return null; + } + + @Override + public SortedMap getMeters() { + return null; + } + + @Override + public SortedMap getMeters(MetricFilter metricFilter) { + return null; + } + + @Override + public SortedMap getTimers() { + return null; + } + + @Override + public SortedMap getTimers(MetricFilter metricFilter) { + return null; + } + + @Override + public Map getMetrics() { + return null; + } + + @Override + public Map getMetadata() { + return null; + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index a2c8e1bc810bf..8ef684c80ad19 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -35,6 +35,7 @@ import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -44,6 +45,7 @@ import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; @@ -75,7 +77,9 @@ NativeImageSystemPropertyBuildItem disableJmx() { @BuildStep public void build(BuildProducer annotationsTransformer, BuildProducer feature, BuildProducer additionalBean, - BuildProducer additionalBda) throws Exception { + BuildProducer additionalBda, + Capabilities capabilities, + BuildProducer systemProperty) throws Exception { feature.produce(new FeatureBuildItem(FeatureBuildItem.SMALLRYE_FAULT_TOLERANCE)); @@ -143,6 +147,12 @@ public void transform(TransformationContext context) { DefaultCommandListenersProvider.class, MetricsCollectorFactory.class); additionalBean.produce(builder.build()); + + if (!capabilities.isCapabilityPresent(Capabilities.METRICS)) { + //disable fault tolerance metrics with the MP sys props and provides a No-op metric registry. + additionalBean.produce(new AdditionalBeanBuildItem(NoopMetricRegistry.class)); + systemProperty.produce(new SystemPropertyBuildItem("MP_Fault_Tolerance_Metrics_Enabled", "false")); + } } @BuildStep diff --git a/extensions/smallrye-fault-tolerance/runtime/pom.xml b/extensions/smallrye-fault-tolerance/runtime/pom.xml index 8061e423b4576..8284aa06e61a6 100644 --- a/extensions/smallrye-fault-tolerance/runtime/pom.xml +++ b/extensions/smallrye-fault-tolerance/runtime/pom.xml @@ -49,11 +49,6 @@ org.jboss.logging commons-logging-jboss-logging - - - io.quarkus - quarkus-smallrye-metrics - com.oracle.substratevm svm diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java index 6e64c10ff6e19..d38e136d64711 100644 --- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java +++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java @@ -49,9 +49,11 @@ import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; @@ -211,15 +213,21 @@ AutoInjectAnnotationBuildItem autoInjectMetric() { return new AutoInjectAnnotationBuildItem(SmallRyeMetricsDotNames.METRIC); } + @BuildStep + public CapabilityBuildItem capability() { + return new CapabilityBuildItem(Capabilities.METRICS); + } + + @BuildStep + public FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.SMALLRYE_METRICS); + } + @BuildStep @Record(STATIC_INIT) public void build(BeanContainerBuildItem beanContainerBuildItem, SmallRyeMetricsRecorder metrics, - BuildProducer reflectiveClasses, - BuildProducer feature) { - - feature.produce(new FeatureBuildItem(FeatureBuildItem.SMALLRYE_METRICS)); - + BuildProducer reflectiveClasses) { for (DotName metricsAnnotation : METRICS_ANNOTATIONS) { reflectiveClasses.produce(new ReflectiveClassBuildItem(false, false, metricsAnnotation.toString())); } From adecf7cdd3e9e6ed59d3e3ee92802a79882dc119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 12 Nov 2019 12:27:41 +0100 Subject: [PATCH 072/602] Add more test to smallrye-faultolerance --- .../test/FaultToleranceTest.java | 103 ++++++++++++++++++ .../test/asynchronous/AsynchronousBean.java | 17 +++ .../test/bukhead/BulkheadBean.java | 25 +++++ .../circuitbreaker/CircuitBreakerBean.java | 20 ++++ .../test/fallback/FallbackTest.java | 30 ----- .../faulttolerance/test/retry/RetryBean.java | 21 ++++ .../test/timeout/TimeoutBean.java | 14 +++ 7 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/FaultToleranceTest.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/AsynchronousBean.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/bukhead/BulkheadBean.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/CircuitBreakerBean.java delete mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackTest.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/RetryBean.java create mode 100644 extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/timeout/TimeoutBean.java diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/FaultToleranceTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/FaultToleranceTest.java new file mode 100644 index 0000000000000..803a5e9ae0a0a --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/FaultToleranceTest.java @@ -0,0 +1,103 @@ +package io.quarkus.smallrye.faulttolerance.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.inject.Inject; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.smallrye.faulttolerance.test.asynchronous.AsynchronousBean; +import io.quarkus.smallrye.faulttolerance.test.bukhead.BulkheadBean; +import io.quarkus.smallrye.faulttolerance.test.circuitbreaker.CircuitBreakerBean; +import io.quarkus.smallrye.faulttolerance.test.fallback.FallbackBean; +import io.quarkus.smallrye.faulttolerance.test.retry.RetryBean; +import io.quarkus.smallrye.faulttolerance.test.timeout.TimeoutBean; +import io.quarkus.test.QuarkusUnitTest; + +public class FaultToleranceTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(FallbackBean.class, BulkheadBean.class, TimeoutBean.class, RetryBean.class, + CircuitBreakerBean.class, AsynchronousBean.class)); + + @Inject + FallbackBean fallback; + @Inject + BulkheadBean bulkhead; + @Inject + TimeoutBean timeout; + @Inject + RetryBean retry; + @Inject + CircuitBreakerBean circuitbreaker; + @Inject + AsynchronousBean asynchronous; + + @Test + public void testFallback() { + assertEquals(FallbackBean.RecoverFallback.class.getName(), fallback.ping()); + } + + @Test + public void testBulkhead() throws InterruptedException { + AtomicBoolean success = new AtomicBoolean(true); + AtomicBoolean bulkheadFailures = new AtomicBoolean(false); + ExecutorService executorService = Executors.newFixedThreadPool(10); + for (int i = 0; i < 20; i++) { + executorService.submit(() -> { + try { + int bulk = bulkhead.bulkhead(); + if (bulk > 5) { + success.set(false); + } + } catch (BulkheadException be) { + bulkheadFailures.set(true); + } + }); + Thread.sleep(1);//give some chance to the bulkhead to happens + } + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + assertTrue(success.get()); + assertTrue(bulkheadFailures.get()); + } + + @Test + public void testTimeout() throws InterruptedException { + assertThrows(TimeoutException.class, () -> timeout.timeout()); + } + + @Test + public void testRetry() { + assertTrue(retry.retry()); + } + + @Test + public void testCircuitBreaker() { + circuitbreaker.breakCircuit(); + assertThrows(RuntimeException.class, () -> circuitbreaker.breakCircuit()); + assertThrows(RuntimeException.class, () -> circuitbreaker.breakCircuit()); + assertThrows(RuntimeException.class, () -> circuitbreaker.breakCircuit()); + assertThrows(RuntimeException.class, () -> circuitbreaker.breakCircuit()); + assertThrows(CircuitBreakerOpenException.class, () -> circuitbreaker.breakCircuit()); + } + + @Test + public void testAsynchronous() { + asynchronous.asynchronous().thenAccept(s -> assertEquals("hello", s)); + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/AsynchronousBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/AsynchronousBean.java new file mode 100644 index 0000000000000..1c9ad722f68ec --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/asynchronous/AsynchronousBean.java @@ -0,0 +1,17 @@ +package io.quarkus.smallrye.faulttolerance.test.asynchronous; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +@ApplicationScoped +public class AsynchronousBean { + + @Asynchronous + public CompletionStage asynchronous() { + return CompletableFuture.completedFuture("hello"); + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/bukhead/BulkheadBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/bukhead/BulkheadBean.java new file mode 100644 index 0000000000000..90432b936089b --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/bukhead/BulkheadBean.java @@ -0,0 +1,25 @@ +package io.quarkus.smallrye.faulttolerance.test.bukhead; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Bulkhead; + +@ApplicationScoped +public class BulkheadBean { + private AtomicInteger integer = new AtomicInteger(); + + @Bulkhead(5) + public int bulkhead() { + int i = integer.incrementAndGet(); + try { + Thread.sleep(10);//artificially generates contention + return i; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + integer.decrementAndGet(); + } + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/CircuitBreakerBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/CircuitBreakerBean.java new file mode 100644 index 0000000000000..6b6b062cd7b41 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/circuitbreaker/CircuitBreakerBean.java @@ -0,0 +1,20 @@ +package io.quarkus.smallrye.faulttolerance.test.circuitbreaker; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; + +@ApplicationScoped +public class CircuitBreakerBean { + AtomicBoolean breakTheCircuit = new AtomicBoolean(); + + @CircuitBreaker(requestVolumeThreshold = 5) + public void breakCircuit() { + if (!breakTheCircuit.getAndSet(true)) { + return; + } + throw new RuntimeException("let's break it !"); + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackTest.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackTest.java deleted file mode 100644 index 5ffd3debeff4a..0000000000000 --- a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.quarkus.smallrye.faulttolerance.test.fallback; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import javax.inject.Inject; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.smallrye.faulttolerance.test.fallback.FallbackBean.RecoverFallback; -import io.quarkus.test.QuarkusUnitTest; - -public class FallbackTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(FallbackBean.class)); - - @Inject - FallbackBean bean; - - @Test - public void testFallback() { - assertEquals(RecoverFallback.class.getName(), bean.ping()); - } - -} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/RetryBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/RetryBean.java new file mode 100644 index 0000000000000..4609fe28214d2 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/retry/RetryBean.java @@ -0,0 +1,21 @@ +package io.quarkus.smallrye.faulttolerance.test.retry; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Retry; + +@ApplicationScoped +public class RetryBean { + private AtomicBoolean retries = new AtomicBoolean(); + + @Retry + public boolean retry() { + if (!retries.get()) { + retries.set(true); + throw new RuntimeException("should retry"); + } + return retries.get(); + } +} diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/timeout/TimeoutBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/timeout/TimeoutBean.java new file mode 100644 index 0000000000000..466963c6584a2 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/timeout/TimeoutBean.java @@ -0,0 +1,14 @@ +package io.quarkus.smallrye.faulttolerance.test.timeout; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Timeout; + +@ApplicationScoped +public class TimeoutBean { + + @Timeout(50) + public void timeout() throws InterruptedException { + Thread.sleep(100); + } +} From 3afe8aba85bde72758556fa04f4c83d8c48a9cba Mon Sep 17 00:00:00 2001 From: xstefank Date: Mon, 25 Nov 2019 11:01:36 +0100 Subject: [PATCH 073/602] Fix typo in REST Client guide --- docs/src/main/asciidoc/rest-client.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 5963c103d40b0..8fcebfa698ef2 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -76,7 +76,7 @@ public class Country { } ---- -The model above in only a subset of the fields provided by the service, but it suffices for the purposes of this guide. +The model above is only a subset of the fields provided by the service, but it suffices for the purposes of this guide. == Create the interface From 530b62071aff2949b0e7fd205605df17c2090836 Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Wed, 30 Oct 2019 22:01:25 +0100 Subject: [PATCH 074/602] Add health check to Artemis extension --- .../agroal/runtime/AgroalBuildTimeConfig.java | 2 +- extensions/artemis-core/deployment/pom.xml | 5 +++ .../deployment/ArtemisBuildTimeConfig.java | 15 ++++++++ .../core/deployment/ArtemisCoreProcessor.java | 12 +++++++ extensions/artemis-core/runtime/pom.xml | 6 ++++ .../health/ServerLocatorHealthCheck.java | 30 ++++++++++++++++ .../jms/deployment/ArtemisJmsProcessor.java | 9 +++++ extensions/artemis-jms/runtime/pom.xml | 6 ++++ .../health/ConnectionFactoryHealthCheck.java | 30 ++++++++++++++++ integration-tests/artemis-core/pom.xml | 11 ++++++ .../it/artemis/ArtemisConsumerTest.java | 4 ++- .../it/artemis/ArtemisHealthCheckITCase.java | 8 +++++ .../it/artemis/ArtemisHealthCheckTest.java | 35 +++++++++++++++++++ .../it/artemis/ArtemisProducerTest.java | 4 ++- integration-tests/artemis-jms/pom.xml | 11 ++++++ .../it/artemis/ArtemisHealthCheckITCase.java | 8 +++++ .../it/artemis/ArtemisHealthCheckTest.java | 35 +++++++++++++++++++ 17 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisBuildTimeConfig.java create mode 100644 extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/health/ServerLocatorHealthCheck.java create mode 100644 extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/health/ConnectionFactoryHealthCheck.java create mode 100644 integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java create mode 100644 integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java create mode 100644 integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java create mode 100644 integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalBuildTimeConfig.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalBuildTimeConfig.java index 505fe89777675..508232ca9f424 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalBuildTimeConfig.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalBuildTimeConfig.java @@ -26,7 +26,7 @@ public class AgroalBuildTimeConfig { public Map namedDataSources; /** - * Whether or not an healtcheck is published in case the smallrye-health extension is present (default to true). + * Whether or not an health check is published in case the smallrye-health extension is present */ @ConfigItem(name = "health.enabled", defaultValue = "true") public boolean healthEnabled; diff --git a/extensions/artemis-core/deployment/pom.xml b/extensions/artemis-core/deployment/pom.xml index b5212a74ca088..d25a4fff99334 100644 --- a/extensions/artemis-core/deployment/pom.xml +++ b/extensions/artemis-core/deployment/pom.xml @@ -28,6 +28,11 @@ quarkus-netty-deployment + + io.quarkus + quarkus-smallrye-health-spi + + io.quarkus quarkus-artemis-core diff --git a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisBuildTimeConfig.java b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisBuildTimeConfig.java new file mode 100644 index 0000000000000..767ff493eff92 --- /dev/null +++ b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisBuildTimeConfig.java @@ -0,0 +1,15 @@ +package io.quarkus.artemis.core.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "artemis", phase = ConfigPhase.BUILD_TIME) +public class ArtemisBuildTimeConfig { + + /** + * Whether or not an health check is published in case the smallrye-health extension is present + */ + @ConfigItem(name = "health.enabled", defaultValue = "true") + public boolean healthEnabled; +} diff --git a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java index a3cd731d87130..5066119485db0 100644 --- a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java +++ b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java @@ -27,6 +27,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; public class ArtemisCoreProcessor { @@ -78,6 +79,17 @@ void build(CombinedIndexBuildItem indexBuildItem, } } + @BuildStep + HealthBuildItem health(ArtemisBuildTimeConfig buildConfig, Optional artemisJms) { + if (artemisJms.isPresent()) { + return null; + } + + return new HealthBuildItem( + "io.quarkus.artemis.core.runtime.health.ServerLocatorHealthCheck", + buildConfig.healthEnabled, "artemis"); + } + @BuildStep void load(BuildProducer additionalBean, BuildProducer feature, Optional artemisJms) { diff --git a/extensions/artemis-core/runtime/pom.xml b/extensions/artemis-core/runtime/pom.xml index 2471ba5700fe6..44c16bcb4815e 100644 --- a/extensions/artemis-core/runtime/pom.xml +++ b/extensions/artemis-core/runtime/pom.xml @@ -29,6 +29,12 @@ quarkus-netty + + io.quarkus + quarkus-smallrye-health + true + + org.apache.activemq artemis-core-client diff --git a/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/health/ServerLocatorHealthCheck.java b/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/health/ServerLocatorHealthCheck.java new file mode 100644 index 0000000000000..9eed5dac5337d --- /dev/null +++ b/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/health/ServerLocatorHealthCheck.java @@ -0,0 +1,30 @@ +package io.quarkus.artemis.core.runtime.health; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; + +@Readiness +@ApplicationScoped +public class ServerLocatorHealthCheck implements HealthCheck { + + @Inject + ServerLocator serverLocator; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Artemis Core health check"); + try (ClientSessionFactory factory = serverLocator.createSessionFactory()) { + builder.up(); + } catch (Exception e) { + builder.down(); + } + return builder.build(); + } +} diff --git a/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java index 9890871e44af2..34d1e1c6f33a2 100644 --- a/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java +++ b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java @@ -2,6 +2,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.artemis.core.deployment.ArtemisBuildTimeConfig; import io.quarkus.artemis.core.deployment.ArtemisJmsBuildItem; import io.quarkus.artemis.core.runtime.ArtemisRuntimeConfig; import io.quarkus.artemis.jms.runtime.ArtemisJmsProducer; @@ -11,6 +12,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; public class ArtemisJmsProcessor { @@ -23,6 +25,13 @@ void load(BuildProducer additionalBean, BuildProducerquarkus-artemis-core + + io.quarkus + quarkus-smallrye-health + true + + org.apache.activemq artemis-jms-client diff --git a/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/health/ConnectionFactoryHealthCheck.java b/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/health/ConnectionFactoryHealthCheck.java new file mode 100644 index 0000000000000..c171de77079a8 --- /dev/null +++ b/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/health/ConnectionFactoryHealthCheck.java @@ -0,0 +1,30 @@ +package io.quarkus.artemis.jms.runtime.health; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; + +@Readiness +@ApplicationScoped +public class ConnectionFactoryHealthCheck implements HealthCheck { + + @Inject + ConnectionFactory connectionFactory; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Artemis JMS health check"); + try (Connection connection = connectionFactory.createConnection()) { + builder.up(); + } catch (Exception e) { + builder.down(); + } + return builder.build(); + } +} diff --git a/integration-tests/artemis-core/pom.xml b/integration-tests/artemis-core/pom.xml index da1a42e3fecad..7946393a5a3c7 100644 --- a/integration-tests/artemis-core/pom.xml +++ b/integration-tests/artemis-core/pom.xml @@ -30,7 +30,18 @@ quarkus-artemis-core + + + io.quarkus + quarkus-smallrye-health + + + + io.quarkus + quarkus-jackson + test + io.quarkus quarkus-junit5 diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java index 284b70c4108f8..58455bcbc93be 100644 --- a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java @@ -1,5 +1,7 @@ package io.quarkus.it.artemis; +import javax.ws.rs.core.Response.Status; + import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.junit.jupiter.api.Assertions; @@ -24,7 +26,7 @@ public void test() throws Exception { } Response response = RestAssured.with().body(body).get("/artemis"); - Assertions.assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response.statusCode()); + Assertions.assertEquals(Status.OK.getStatusCode(), response.statusCode()); Assertions.assertEquals(body, response.getBody().asString()); } } diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java new file mode 100644 index 0000000000000..4883f90c882bd --- /dev/null +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.artemis; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class ArtemisHealthCheckITCase extends ArtemisHealthCheckTest { + +} diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java new file mode 100644 index 0000000000000..5f43b5e05aea7 --- /dev/null +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java @@ -0,0 +1,35 @@ +package io.quarkus.it.artemis; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.Response; + +@QuarkusTest +@QuarkusTestResource(ArtemisTestResource.class) +public class ArtemisHealthCheckTest { + + @Test + public void test() { + Response response = RestAssured.with().get("/health/ready"); + Assertions.assertEquals(Status.OK.getStatusCode(), response.statusCode()); + + Map body = response.as(new TypeRef>() { + }); + Assertions.assertEquals("UP", body.get("status")); + + @SuppressWarnings("unchecked") + List> checks = (List>) body.get("checks"); + Assertions.assertEquals(1, checks.size()); + Assertions.assertEquals("Artemis Core health check", checks.get(0).get("name")); + } +} diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java index 73e034ad30675..058d962ff41e9 100644 --- a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java @@ -1,5 +1,7 @@ package io.quarkus.it.artemis; +import javax.ws.rs.core.Response.Status; + import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.junit.jupiter.api.Assertions; @@ -18,7 +20,7 @@ public class ArtemisProducerTest implements ArtemisHelper { public void test() throws Exception { String body = createBody(); Response response = RestAssured.with().body(body).post("/artemis"); - Assertions.assertEquals(javax.ws.rs.core.Response.Status.NO_CONTENT.getStatusCode(), response.statusCode()); + Assertions.assertEquals(Status.NO_CONTENT.getStatusCode(), response.statusCode()); try (ClientSession session = createSession()) { session.start(); diff --git a/integration-tests/artemis-jms/pom.xml b/integration-tests/artemis-jms/pom.xml index 80a3f7ca3ef8d..ceb5020dd7767 100644 --- a/integration-tests/artemis-jms/pom.xml +++ b/integration-tests/artemis-jms/pom.xml @@ -30,7 +30,18 @@ quarkus-artemis-jms + + + io.quarkus + quarkus-smallrye-health + + + + io.quarkus + quarkus-jackson + test + io.quarkus quarkus-junit5 diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java new file mode 100644 index 0000000000000..4883f90c882bd --- /dev/null +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.artemis; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class ArtemisHealthCheckITCase extends ArtemisHealthCheckTest { + +} diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java new file mode 100644 index 0000000000000..2841fedde0b46 --- /dev/null +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java @@ -0,0 +1,35 @@ +package io.quarkus.it.artemis; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response.Status; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.Response; + +@QuarkusTest +@QuarkusTestResource(ArtemisTestResource.class) +public class ArtemisHealthCheckTest { + + @Test + public void test() { + Response response = RestAssured.with().get("/health/ready"); + Assertions.assertEquals(Status.OK.getStatusCode(), response.statusCode()); + + Map body = response.as(new TypeRef>() { + }); + Assertions.assertEquals("UP", body.get("status")); + + @SuppressWarnings("unchecked") + List> checks = (List>) body.get("checks"); + Assertions.assertEquals(1, checks.size()); + Assertions.assertEquals("Artemis JMS health check", checks.get(0).get("name")); + } +} From c6875bbca3da9f59640306d14b53e5cb7fb38cbb Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Thu, 31 Oct 2019 07:40:26 +0100 Subject: [PATCH 075/602] Use Quarkus JSON-P in Artemis instead of Apache Geronimo and Johnzon --- extensions/artemis-core/runtime/pom.xml | 13 +++++++++++++ integration-tests/artemis-core/pom.xml | 8 ++++++++ integration-tests/artemis-jms/pom.xml | 8 ++++++++ 3 files changed, 29 insertions(+) diff --git a/extensions/artemis-core/runtime/pom.xml b/extensions/artemis-core/runtime/pom.xml index 44c16bcb4815e..2ce771b64827b 100644 --- a/extensions/artemis-core/runtime/pom.xml +++ b/extensions/artemis-core/runtime/pom.xml @@ -24,6 +24,11 @@ quarkus-arc + + io.quarkus + quarkus-jsonp + + io.quarkus quarkus-netty @@ -39,6 +44,14 @@ org.apache.activemq artemis-core-client + + org.apache.geronimo.specs + geronimo-json_1.0_spec + + + org.apache.johnzon + johnzon-core + commons-logging commons-logging diff --git a/integration-tests/artemis-core/pom.xml b/integration-tests/artemis-core/pom.xml index 7946393a5a3c7..9e0624186b2de 100644 --- a/integration-tests/artemis-core/pom.xml +++ b/integration-tests/artemis-core/pom.xml @@ -58,6 +58,14 @@ ${artemis.version} test + + org.apache.geronimo.specs + geronimo-json_1.0_spec + + + org.apache.johnzon + johnzon-core + org.jboss.logmanager jboss-logmanager diff --git a/integration-tests/artemis-jms/pom.xml b/integration-tests/artemis-jms/pom.xml index ceb5020dd7767..1d58cb92c961d 100644 --- a/integration-tests/artemis-jms/pom.xml +++ b/integration-tests/artemis-jms/pom.xml @@ -58,6 +58,14 @@ ${artemis.version} test + + org.apache.geronimo.specs + geronimo-json_1.0_spec + + + org.apache.johnzon + johnzon-core + org.jboss.logmanager jboss-logmanager From c4e319358f95707e7fbe2453304cb2f458b5808c Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sun, 24 Nov 2019 09:43:07 +0530 Subject: [PATCH 076/602] Disable the unnecessary JVM debug launch of integration tests --- .../io/quarkus/maven/it/CreateProjectMojoIT.java | 4 +++- .../test/java/io/quarkus/maven/it/DevMojoIT.java | 9 +++++++-- .../java/io/quarkus/maven/it/RemoteDevMojoIT.java | 5 ++++- .../quarkus/maven/it/RunAndCheckMojoTestBase.java | 13 ++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java index 739f982f0829e..781fbc8792b58 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java @@ -302,7 +302,9 @@ public void generateNewProjectAndRun() throws Exception { // As the directory is not empty (log) navigate to the artifactID directory testDir = new File(testDir, "acme"); running = new RunningInvoker(testDir, false); - running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + final Properties mvnRunProps = new Properties(); + mvnRunProps.setProperty("debug", "false"); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap(), mvnRunProps); String resp = getHttpResponse(); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 5e18a30c11fd2..88d0601f9fc61 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import java.util.Properties; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -297,7 +298,9 @@ public void testThatTheApplicationIsReloadedOnConfigChange() throws MavenInvocat testDir = initProject("projects/classic", "projects/project-classic-run-config-change"); assertThat(testDir).isDirectory(); running = new RunningInvoker(testDir, false); - running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + final Properties mvnRunProps = new Properties(); + mvnRunProps.setProperty("debug", "false"); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap(), mvnRunProps); String resp = getHttpResponse(); @@ -328,7 +331,9 @@ public void testThatAddingConfigFileWorksCorrectly() throws MavenInvocationExcep testDir = initProject("projects/classic-noconfig", "projects/project-classic-run-noconfig-add-config"); assertThat(testDir).isDirectory(); running = new RunningInvoker(testDir, false); - running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + final Properties mvnRunProps = new Properties(); + mvnRunProps.setProperty("debug", "false"); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap(), mvnRunProps); String resp = getHttpResponse(); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java index a2650cac3c40f..7e406857ec62e 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import java.util.Properties; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -92,7 +93,9 @@ public void testThatTheApplicationIsReloadedOnConfigChange() throws MavenInvocat agentDir = initProject("projects/classic", "projects/project-classic-run-config-change-local"); assertThat(testDir).isDirectory(); running = new RunningInvoker(testDir, false); - running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + final Properties mvnRunProps = new Properties(); + mvnRunProps.setProperty("debug", "false"); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap(), mvnRunProps); String resp = getHttpResponse(); runningAgent = new RunningInvoker(agentDir, false); diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java index bcf232c760925..1b1340aa4bcb7 100644 --- a/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java +++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/RunAndCheckMojoTestBase.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Properties; import org.apache.maven.shared.invoker.MavenInvocationException; import org.junit.jupiter.api.AfterEach; @@ -34,8 +35,16 @@ protected void run(String... options) throws FileNotFoundException, MavenInvocat final List args = new ArrayList<>(2 + options.length); args.add("compile"); args.add("quarkus:dev"); + boolean hasDebugOptions = false; for (String option : options) { args.add(option); + if (option.trim().startsWith("-Ddebug=") || option.trim().startsWith("-Dsuspend=")) { + hasDebugOptions = true; + } + } + if (!hasDebugOptions) { + // if no explicit debug options have been specified, let's just disable debugging + args.add("-Ddebug=false"); } running.execute(args, Collections.emptyMap()); } @@ -55,7 +64,9 @@ protected void runAndCheck(String... options) throws FileNotFoundException, Mave protected void runAndExpectError() throws FileNotFoundException, MavenInvocationException { assertThat(testDir).isDirectory(); running = new RunningInvoker(testDir, false); - running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + final Properties mvnRunProps = new Properties(); + mvnRunProps.setProperty("debug", "false"); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap(), mvnRunProps); getHttpErrorResponse(); } From da8e3cc3b215803c42a6bb7799d502dc4e78ab80 Mon Sep 17 00:00:00 2001 From: Rohan Maity Date: Mon, 25 Nov 2019 12:39:50 +0530 Subject: [PATCH 077/602] update GraalVM install link Signed-off-by: Rohan Maity --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcfa1d63b7a24..fe98c74a608d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ If you have not done so on this machine, you need to: * Install Git and configure your GitHub access * Install Java SDK (OpenJDK recommended) -* Install [GraalVM](http://www.graalvm.org/downloads/) (community edition is enough) +* Install [GraalVM](https://quarkus.io/guides/building-native-image) * Install platform C developer tools: * Linux * Make sure headers are available on your system (you'll hit 'Basic header file missing ()' error if they aren't). From b8d48bb3322d9e620991938d857acb49117f2054 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Mon, 25 Nov 2019 09:11:02 +0100 Subject: [PATCH 078/602] Update MP-Metrics to 2.2.1, enable GlogalTagsTest --- build-parent/pom.xml | 2 +- tcks/microprofile-metrics/api/pom.xml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 768a32d5591d5..f08feddd138d9 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -60,7 +60,7 @@ 2.1 1.3 - 2.2 + 2.2.1 2.0.2 1.0 1.3.4 diff --git a/tcks/microprofile-metrics/api/pom.xml b/tcks/microprofile-metrics/api/pom.xml index a2e6a36fb1aa6..ca2d0faef95e3 100644 --- a/tcks/microprofile-metrics/api/pom.xml +++ b/tcks/microprofile-metrics/api/pom.xml @@ -31,10 +31,6 @@ tier=integration - - - org/eclipse/microprofile/metrics/tck/tags/GlobalTagsTest.java - From a43954f419974fb20e0a3a3e3030f9f6979c060f Mon Sep 17 00:00:00 2001 From: aurea munoz Date: Mon, 25 Nov 2019 11:11:00 +0100 Subject: [PATCH 079/602] fix: add spring-security extension metadata Related to #5225 --- .../main/resources/META-INF/quarkus-extension.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 extensions/spring-security/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/extensions/spring-security/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/spring-security/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..4796b70b7d479 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "Quarkus Extension for Spring Security API" +metadata: + keywords: + - "spring-security" + - "spring" + - "security" + guide: "https://quarkus.io/guides/spring-security" + categories: + - "compatibility" + status: "preview" From b2812d3dd8b11752086880cb944184ef93d96a16 Mon Sep 17 00:00:00 2001 From: masini Date: Sat, 31 Aug 2019 12:24:15 +0000 Subject: [PATCH 080/602] Addess Access Log Configuration Sample Documentation --- docs/src/main/asciidoc/http-reference.adoc | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 94e6937b8565c..8db8aac5b2d46 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -182,13 +182,38 @@ To use Servlet you need to explicitly include `quarkus-undertow`: ---- - === undertow-handlers.conf You can make use of the Undertow predicate language using an `undertow-handlers.conf` file. This file should be placed in the `META-INF` directory of your application jar. This file contains handlers defined using the link:http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#predicates-attributes-and-handlers[Undertow predicate language]. +=== Configuring HTTP Access Logs + +You can add HTTP request logging adding the AccessHandler configuration to the undertow-handler.conf file. + +The simplest possible configuration can be a standard Apache `common` Log Format: + +[source,properties] +---- +access-log('common') +---- + +this will log every request to Quarkus configured logging system under the `io.undertow.accesslog` category. + +In case you can also configure another category such this: + +---- +access-log(format='common', category='my.own.category') +---- + +Finally the logging format can be customized: + +---- +access-log(format='%h %l %u %t "%r" %s %b %D "%{i,Referer}" "%{i,User-Agent}" "%{i,X-Request-ID}"', category='my.own.category') +---- + === web.xml If you are using a `web.xml` file as your configuration file, you can place it in the `src/main/resources/META-INF` directory. + From 37fbd6db36343eefd576b1a731c1a6fe8a6bbed1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 25 Nov 2019 15:54:05 +0100 Subject: [PATCH 081/602] Adjustments to access logging documentation --- docs/src/main/asciidoc/http-reference.adoc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 8db8aac5b2d46..38518a7b4f6ce 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -190,25 +190,27 @@ link:http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#predicates- === Configuring HTTP Access Logs -You can add HTTP request logging adding the AccessHandler configuration to the undertow-handler.conf file. +You can add HTTP request logging by configuring the `AccessHandler` in the `undertow-handlers.conf` file. The simplest possible configuration can be a standard Apache `common` Log Format: -[source,properties] +[source] ---- access-log('common') ---- -this will log every request to Quarkus configured logging system under the `io.undertow.accesslog` category. +This will log every request using the standard Quarkus logging infrastructure under the `io.undertow.accesslog` category. -In case you can also configure another category such this: +You can customize the category like this: +[source] ---- access-log(format='common', category='my.own.category') ---- Finally the logging format can be customized: +[source] ---- access-log(format='%h %l %u %t "%r" %s %b %D "%{i,Referer}" "%{i,User-Agent}" "%{i,X-Request-ID}"', category='my.own.category') ---- From a00fdb49cc7946df201fc5f0e87826f60e44fdeb Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 25 Nov 2019 15:57:31 +0100 Subject: [PATCH 082/602] Various minor adjustments to the HTTP reference documentation --- docs/src/main/asciidoc/http-reference.adoc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/http-reference.adoc b/docs/src/main/asciidoc/http-reference.adoc index 38518a7b4f6ce..58a854518e0b4 100644 --- a/docs/src/main/asciidoc/http-reference.adoc +++ b/docs/src/main/asciidoc/http-reference.adoc @@ -41,12 +41,12 @@ want to use the HTTP root as this affects everything that Quarkus serves. If both are specified then all non-Servlet web endpoints will be relative to `quarkus.http.root-path`, while Servlet's will be served relative to `{quarkus.http.root-path}/{quarkus.servlet.context-path}`. -If Restassured is used for testing and `quarkus.http.root-path` is set then Quarkus will automatically configure the +If REST Assured is used for testing and `quarkus.http.root-path` is set then Quarkus will automatically configure the base URL for use in Quarkus tests, so test URL's should not include the root path. == Supporting secure connections with SSL -In order to have Undertow support secure connections, you must either provide a certificate and associated key file, or supply a keystore. +In order to have Quarkus support secure connections, you must either provide a certificate and associated key file, or supply a keystore. In both cases, a password must be provided. See the designated paragraph for a detailed description of how to provide it. @@ -153,8 +153,10 @@ quarkus.http.cors.exposed-headers=Content-Disposition quarkus.http.cors.access-control-max-age=24H ---- -== Http Limits Configuration +== HTTP Limits Configuration + The following properties are supported. + [cols=" Date: Mon, 25 Nov 2019 18:24:01 +0200 Subject: [PATCH 083/602] Ensure that NoopMetricRegistry is available at runtime Fixes: #5748 --- .../deployment/SmallRyeFaultToleranceProcessor.java | 5 ++++- .../smallrye/faulttolerance/runtime}/NoopMetricRegistry.java | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) rename extensions/smallrye-fault-tolerance/{deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment => runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime}/NoopMetricRegistry.java (97%) diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index 8ef684c80ad19..afb2570201829 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -8,6 +8,7 @@ import javax.annotation.Priority; import javax.inject.Inject; +import javax.inject.Singleton; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; @@ -49,6 +50,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.smallrye.faulttolerance.runtime.NoopMetricRegistry; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFallbackHandlerProvider; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFaultToleranceOperationProvider; import io.quarkus.smallrye.faulttolerance.runtime.SmallryeFaultToleranceRecorder; @@ -150,7 +152,8 @@ public void transform(TransformationContext context) { if (!capabilities.isCapabilityPresent(Capabilities.METRICS)) { //disable fault tolerance metrics with the MP sys props and provides a No-op metric registry. - additionalBean.produce(new AdditionalBeanBuildItem(NoopMetricRegistry.class)); + additionalBean.produce(AdditionalBeanBuildItem.builder().addBeanClass(NoopMetricRegistry.class).setRemovable() + .setDefaultScope(DotName.createSimple(Singleton.class.getName())).build()); systemProperty.produce(new SystemPropertyBuildItem("MP_Fault_Tolerance_Metrics_Enabled", "false")); } } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/NoopMetricRegistry.java similarity index 97% rename from extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java rename to extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/NoopMetricRegistry.java index 3f79aca78c3e8..3edb68c783b9a 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/NoopMetricRegistry.java +++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/NoopMetricRegistry.java @@ -1,14 +1,11 @@ -package io.quarkus.smallrye.faulttolerance.deployment; +package io.quarkus.smallrye.faulttolerance.runtime; import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; -import javax.inject.Singleton; - import org.eclipse.microprofile.metrics.*; -@Singleton public class NoopMetricRegistry extends MetricRegistry { @Override From 3adaef2a52781a644cff4667e95aac252cb6d65c Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 25 Nov 2019 16:24:03 +0100 Subject: [PATCH 084/602] MP Rest Client - workaround for CustomInvokeWithJsonPProviderTest --- tcks/microprofile-rest-client/pom.xml | 3 --- ...veProcessor.java => RestClientProcessor.java} | 16 ++++++++++++++-- .../tck/restclient/RestClientTckExtension.java | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) rename tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/{SslArchiveProcessor.java => RestClientProcessor.java} (56%) diff --git a/tcks/microprofile-rest-client/pom.xml b/tcks/microprofile-rest-client/pom.xml index 6476bff8e3843..4947d131ad085 100644 --- a/tcks/microprofile-rest-client/pom.xml +++ b/tcks/microprofile-rest-client/pom.xml @@ -44,9 +44,6 @@ org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest - - - io.quarkus.tck.restclient.CustomInvokeWithJsonPProviderTest diff --git a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/SslArchiveProcessor.java b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientProcessor.java similarity index 56% rename from tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/SslArchiveProcessor.java rename to tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientProcessor.java index 5b3b4462d0e23..47cddf6d3fc00 100644 --- a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/SslArchiveProcessor.java +++ b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientProcessor.java @@ -4,9 +4,10 @@ import org.jboss.arquillian.test.spi.TestClass; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.container.ClassContainer; import org.jboss.shrinkwrap.api.spec.WebArchive; -public class SslArchiveProcessor implements ApplicationArchiveProcessor { +public class RestClientProcessor implements ApplicationArchiveProcessor { @Override public void process(Archive applicationArchive, TestClass testClass) { // Only apply the processor to SSL tests @@ -20,8 +21,19 @@ public void process(Archive applicationArchive, TestClass testClass) { } WebArchive war = applicationArchive.as(WebArchive.class); - war.addAsResource(new StringAsset("quarkus.ssl.native=true"), "application.properties"); } + + // Make sure the test class and all of its superclasses are added to the test deployment + // This ensures that all the classes from the hierarchy are loaded by the RuntimeClassLoader + if (ClassContainer.class.isInstance(applicationArchive) && testClass.getJavaClass().getSuperclass() != null) { + ClassContainer classContainer = ClassContainer.class.cast(applicationArchive); + Class clazz = testClass.getJavaClass().getSuperclass(); + while (clazz != Object.class && clazz != null) { + classContainer.addClass(clazz); + clazz = clazz.getSuperclass(); + } + + } } } diff --git a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientTckExtension.java b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientTckExtension.java index 79e25e9423942..031390141ecf4 100644 --- a/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientTckExtension.java +++ b/tcks/microprofile-rest-client/src/test/java/io/quarkus/tck/restclient/RestClientTckExtension.java @@ -6,6 +6,6 @@ public class RestClientTckExtension implements LoadableExtension { @Override public void register(ExtensionBuilder builder) { - builder.service(ApplicationArchiveProcessor.class, SslArchiveProcessor.class); + builder.service(ApplicationArchiveProcessor.class, RestClientProcessor.class); } } From e93653491e9f6f82a102fd62d19b843e0c79f59a Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Mon, 11 Nov 2019 00:13:46 +0100 Subject: [PATCH 085/602] Refactoring of project-related commands to support passing in an 'invocation' containing the arguments and other contextual data, such as a platform descriptor, etc --- .../cli/commands/AddExtensionCommand.java | 28 +- .../gradle/tasks/QuarkusAddExtension.java | 9 +- .../gradle/tasks/QuarkusListExtensions.java | 14 +- .../gradle/tasks/QuarkusPlatformTask.java | 59 +-- .../io/quarkus/maven/CreateProjectMojo.java | 23 +- .../java/io/quarkus/maven/CreateUtils.java | 4 +- .../components/MavenVersionEnforcer.java | 71 ++- .../quarkus/cli/commands/AddExtensions.java | 113 +++-- .../quarkus/cli/commands/CreateProject.java | 85 ++-- .../quarkus/cli/commands/ListExtensions.java | 55 +- .../quarkus/cli/commands/QuarkusCommand.java | 6 + .../cli/commands/QuarkusCommandException.java | 14 + .../commands/QuarkusCommandInvocation.java | 52 ++ .../cli/commands/QuarkusCommandOutcome.java | 22 + .../io/quarkus/cli/commands/ValueMap.java | 64 +++ .../quarkus/cli/commands/file/BuildFile.java | 10 +- .../cli/commands/file/GradleBuildFile.java | 35 +- .../cli/commands/file/MavenBuildFile.java | 61 ++- .../LegacyQuarkusCommandInvocation.java | 28 ++ .../quarkus/generators/ProjectGenerator.java | 8 +- .../rest/BasicRestProjectGenerator.java | 77 +-- .../io/quarkus/maven/utilities/MojoUtils.java | 146 +----- .../loader/json/ArtifactResolver.java | 6 +- .../platform/tools/ToolsConstants.java | 11 + .../io/quarkus/platform/tools/ToolsUtils.java | 63 +++ .../tools/config/QuarkusPlatformConfig.java | 2 +- .../commands/AbstractAddExtensionsTest.java | 14 +- .../cli/commands/CreateProjectTest.java | 15 +- .../cli/commands/ListExtensionsTest.java | 5 +- .../cli/commands/PlatformAwareTestBase.java | 60 +++ .../rest/BasicRestProjectGeneratorTest.java | 19 +- ...QuarkusJsonPlatformDescriptorResolver.java | 221 +++++--- .../json/demo/JsonDescriptorResolverDemo.java | 31 -- .../json/demo/QuarkusPlatformConfigDemo.java | 25 - ...kusJsonPlatformDescriptorResolverTest.java | 13 + .../quarkus-bom-descriptor/extensions.json | 9 + .../src/test/resources/quarkus-bom/pom.xml | 475 ++++++++++++++++++ .../src/test/resources/quarkus.properties | 1 + integration-tests/kogito-maven/pom.xml | 6 + .../kogito/maven/it/KogitoDevModeIT.java | 2 +- .../resources/projects/simple-kogito/pom.xml | 18 +- integration-tests/kotlin/pom.xml | 6 + .../maven/it/KotlinCreateMavenProjectIT.java | 8 +- .../resources/projects/classic-kotlin/pom.xml | 18 +- integration-tests/kubernetes/pom.xml | 10 +- integration-tests/maven/pom.xml | 23 +- .../io/quarkus/maven/it/AddExtensionIT.java | 8 +- .../quarkus/maven/it/CreateProjectMojoIT.java | 19 +- .../io/quarkus/maven/it/GenerateConfigIT.java | 7 +- .../java/io/quarkus/maven/it/PackageIT.java | 4 +- .../resources/projects/multimodule/pom.xml | 10 + .../projects/multimodule/runner/pom.xml | 6 +- integration-tests/scala/pom.xml | 6 + .../maven/it/ScalaCreateMavenProjectIT.java | 30 +- .../resources/projects/classic-scala/pom.xml | 18 +- test-framework/maven/pom.xml | 4 + .../io/quarkus/maven/it/MojoTestBase.java | 46 +- .../it/QuarkusPlatformAwareMojoTestBase.java | 53 ++ .../maven/it/assertions/SetupVerifier.java | 20 +- 59 files changed, 1580 insertions(+), 696 deletions(-) create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommand.java create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandException.java create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandInvocation.java create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandOutcome.java create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ValueMap.java create mode 100644 independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/legacy/LegacyQuarkusCommandInvocation.java create mode 100644 independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/PlatformAwareTestBase.java delete mode 100644 independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/JsonDescriptorResolverDemo.java delete mode 100644 independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/QuarkusPlatformConfigDemo.java create mode 100644 independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json create mode 100644 independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml create mode 100644 independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties create mode 100644 test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java diff --git a/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java b/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java index 9845f41fdae58..351b1e1f88c84 100644 --- a/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java +++ b/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java @@ -1,8 +1,8 @@ package io.quarkus.cli.commands; import java.io.File; -import java.io.IOException; import java.util.Collections; +import java.util.List; import org.aesh.command.Command; import org.aesh.command.CommandDefinition; @@ -13,9 +13,9 @@ import org.aesh.command.option.Option; import org.aesh.io.Resource; +import io.quarkus.cli.commands.legacy.LegacyQuarkusCommandInvocation; import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.dependencies.Extension; -import io.quarkus.maven.utilities.MojoUtils; /** * @author Ståle Pedersen @@ -38,19 +38,22 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command commandInvocation.println(commandInvocation.getHelpInfo("quarkus add-extension")); return CommandResult.SUCCESS; } else { - if (!findExtension(extension)) { + final QuarkusCommandInvocation quarkusInvocation = new LegacyQuarkusCommandInvocation(); + if (!findExtension(extension, quarkusInvocation.getPlatformDescriptor().getExtensions())) { commandInvocation.println("Can not find any extension named: " + extension); return CommandResult.SUCCESS; - } else if (pom.isLeaf()) { + } + if (pom.isLeaf()) { try { - File pomFile = new File(pom.getAbsolutePath()); + quarkusInvocation.setValue(AddExtensions.EXTENSIONS, Collections.singleton(extension)); + final File pomFile = new File(pom.getAbsolutePath()); AddExtensions project = new AddExtensions(new FileProjectWriter(pomFile.getParentFile())); - AddExtensionResult result = project.addExtensions(Collections.singleton(extension)); - if (!result.succeeded()) { + QuarkusCommandOutcome result = project.execute(quarkusInvocation); + if (!result.isSuccess()) { throw new CommandException("Unable to add an extension matching " + extension); } - } catch (IOException e) { - e.printStackTrace(); + } catch (Exception e) { + throw new CommandException("Unable to add an extension matching " + extension, e); } } @@ -59,10 +62,11 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command return CommandResult.SUCCESS; } - private boolean findExtension(String name) { - for (Extension ext : MojoUtils.loadExtensions()) { - if (ext.getName().equalsIgnoreCase(name)) + private boolean findExtension(String name, List extensions) { + for (Extension ext : extensions) { + if (ext.getName().equalsIgnoreCase(name)) { return true; + } } return false; } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java index c820723b5d2aa..7aa0f171bcd8d 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java @@ -3,7 +3,6 @@ import static java.util.Arrays.stream; import static java.util.stream.Collectors.toSet; -import java.io.IOException; import java.util.List; import java.util.Set; @@ -13,6 +12,7 @@ import org.gradle.api.tasks.options.Option; import io.quarkus.cli.commands.AddExtensions; +import io.quarkus.cli.commands.QuarkusCommandInvocation; import io.quarkus.cli.commands.file.GradleBuildFile; import io.quarkus.cli.commands.writer.FileProjectWriter; @@ -43,16 +43,17 @@ public void addExtension() { } @Override - protected void doExecute() { + protected void doExecute(QuarkusCommandInvocation invocation) { Set extensionsSet = getExtensionsToAdd() .stream() .flatMap(ext -> stream(ext.split(","))) .map(String::trim) .collect(toSet()); + invocation.setValue(AddExtensions.EXTENSIONS, extensionsSet); try { new AddExtensions(new GradleBuildFile(new FileProjectWriter(getProject().getProjectDir()))) - .addExtensions(extensionsSet); - } catch (IOException e) { + .execute(invocation); + } catch (Exception e) { throw new GradleException("Failed to add extensions " + getExtensionsToAdd(), e); } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index 3850f4797dda4..bf5879806f2d9 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -1,7 +1,5 @@ package io.quarkus.gradle.tasks; -import java.io.IOException; - import org.gradle.api.GradleException; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; @@ -9,6 +7,7 @@ import org.gradle.api.tasks.options.Option; import io.quarkus.cli.commands.ListExtensions; +import io.quarkus.cli.commands.QuarkusCommandInvocation; import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.gradle.GradleBuildFileFromConnector; @@ -66,13 +65,14 @@ public void listExtensions() { } @Override - protected void doExecute() { + protected void doExecute(QuarkusCommandInvocation invocation) { + invocation.setValue(ListExtensions.ALL, isAll()) + .setValue(ListExtensions.FORMAT, getFormat()) + .setValue(ListExtensions.SEARCH, getSearchPattern()); try { new ListExtensions(new GradleBuildFileFromConnector(new FileProjectWriter(getProject().getProjectDir()))) - .listExtensions( - isAll(), - getFormat(), getSearchPattern()); - } catch (IOException e) { + .execute(invocation); + } catch (Exception e) { throw new GradleException("Unable to list extensions", e); } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java index 324149d43d70c..fce4ef5b23af5 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusPlatformTask.java @@ -6,9 +6,10 @@ import java.nio.file.Path; import java.util.Properties; +import io.quarkus.cli.commands.QuarkusCommandInvocation; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver; -import io.quarkus.platform.tools.config.QuarkusPlatformConfig; +import io.quarkus.platform.tools.MessageWriter; public abstract class QuarkusPlatformTask extends QuarkusTask { @@ -17,50 +18,36 @@ public abstract class QuarkusPlatformTask extends QuarkusTask { } protected void execute() { - try { - setupPlatformDescriptor(); - doExecute(); - } finally { - QuarkusPlatformConfig.clearThreadLocal(); - } + final MessageWriter msgWriter = new GradleMessageWriter(getProject().getLogger()); + final QuarkusPlatformDescriptor platformDescr = getPlatformDescriptor(msgWriter); + doExecute(new QuarkusCommandInvocation(platformDescr, msgWriter)); } - protected abstract void doExecute(); - - protected void setupPlatformDescriptor() { - - if (QuarkusPlatformConfig.hasThreadLocal()) { - getProject().getLogger().debug("Quarkus platform descriptor has already been initialized"); - return; - } else { - getProject().getLogger().debug("Initializing Quarkus platform descriptor"); - } + protected abstract void doExecute(QuarkusCommandInvocation invocation); + private QuarkusPlatformDescriptor getPlatformDescriptor(MessageWriter msgWriter) { final Path currentDir = getProject().getProjectDir().toPath(); final Path gradlePropsPath = currentDir.resolve("gradle.properties"); - if (Files.exists(gradlePropsPath)) { - final Properties props = new Properties(); - try (InputStream is = Files.newInputStream(gradlePropsPath)) { - props.load(is); - } catch (IOException e) { - throw new IllegalStateException("Failed to load " + gradlePropsPath, e); - } - - final QuarkusPlatformDescriptor platform = QuarkusJsonPlatformDescriptorResolver.newInstance() - .setArtifactResolver(extension().resolveAppModel()) - .setMessageWriter(new GradleMessageWriter(getProject().getLogger())) - .resolveFromBom( - getRequiredProperty(props, "quarkusPlatformGroupId"), - getRequiredProperty(props, "quarkusPlatformArtifactId"), - getRequiredProperty(props, "quarkusPlatformVersion")); - - QuarkusPlatformConfig.threadLocalConfigBuilder().setPlatformDescriptor(platform).build(); - - } else { + if (!Files.exists(gradlePropsPath)) { getProject().getLogger() .warn("Failed to locate " + gradlePropsPath + " to determine the Quarkus Platform BOM coordinates"); + return null; } + final Properties props = new Properties(); + try (InputStream is = Files.newInputStream(gradlePropsPath)) { + props.load(is); + } catch (IOException e) { + throw new IllegalStateException("Failed to load " + gradlePropsPath, e); + } + + return QuarkusJsonPlatformDescriptorResolver.newInstance() + .setArtifactResolver(extension().resolveAppModel()) + .setMessageWriter(msgWriter) + .resolveFromBom( + getRequiredProperty(props, "quarkusPlatformGroupId"), + getRequiredProperty(props, "quarkusPlatformArtifactId"), + getRequiredProperty(props, "quarkusPlatformVersion")); } private static String getRequiredProperty(Properties props, String name) { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 36ccd6ddd3643..581dcfebc062d 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -54,7 +55,8 @@ import io.quarkus.generators.SourceType; import io.quarkus.maven.components.MavenVersionEnforcer; import io.quarkus.maven.components.Prompter; -import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.tools.ToolsUtils; /** * This goal helps in setting up Quarkus Maven project with quarkus-maven-plugin, with sensible defaults @@ -147,7 +149,8 @@ public void execute() throws MojoExecutionException { } catch (AppModelResolverException e1) { throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e1); } - CreateUtils.setGlobalPlatformDescriptor(bomGroupId, bomArtifactId, bomVersion, mvn, getLog()); + final QuarkusPlatformDescriptor platform = CreateUtils.setGlobalPlatformDescriptor(bomGroupId, bomArtifactId, + bomVersion, mvn, getLog()); // We detect the Maven version during the project generation to indicate the user immediately that the installed // version may not be supported. @@ -221,12 +224,12 @@ public void execute() throws MojoExecutionException { } } if (BuildTool.MAVEN.equals(buildToolEnum)) { - createMavenWrapper(createdDependenciesBuildFile); + createMavenWrapper(createdDependenciesBuildFile, ToolsUtils.readQuarkusProperties(platform)); } else if (BuildTool.GRADLE.equals(buildToolEnum)) { - createGradleWrapper(buildFile.getParentFile()); + createGradleWrapper(buildFile.getParentFile(), ToolsUtils.readQuarkusProperties(platform)); } } catch (IOException e) { - throw new MojoExecutionException(e.getMessage(), e); + throw new MojoExecutionException("Failed to generate Quarkus project", e); } if (success) { printUserInstructions(projectRoot); @@ -236,11 +239,11 @@ public void execute() throws MojoExecutionException { } } - private void createGradleWrapper(File projectDirectory) { + private void createGradleWrapper(File projectDirectory, Properties props) { try { String gradleName = IS_WINDOWS ? "gradle.bat" : "gradle"; ProcessBuilder pb = new ProcessBuilder(gradleName, "wrapper", - "--gradle-version=" + MojoUtils.getGradleWrapperVersion()).directory(projectDirectory) + "--gradle-version=" + ToolsUtils.getGradleWrapperVersion(props)).directory(projectDirectory) .inheritIO(); Process x = pb.start(); @@ -259,7 +262,7 @@ private void createGradleWrapper(File projectDirectory) { } - private void createMavenWrapper(File createdPomFile) { + private void createMavenWrapper(File createdPomFile, Properties props) { try { // we need to modify the maven environment used by the wrapper plugin since the project could have been // created in a directory other than the current @@ -277,10 +280,10 @@ private void createMavenWrapper(File createdPomFile) { plugin( groupId("io.takari"), artifactId("maven"), - version(MojoUtils.getMavenWrapperVersion())), + version(ToolsUtils.getMavenWrapperVersion(props))), goal("wrapper"), configuration( - element(name("maven"), MojoUtils.getProposedMavenVersion())), + element(name("maven"), ToolsUtils.getProposedMavenVersion(props))), executionEnvironment( newProject, newSession, diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java index a8904865d6f25..2c4ffe167e162 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateUtils.java @@ -50,7 +50,8 @@ private static boolean isVersionRange(String versionStr) { return versionStr.indexOf(',') >= 0; } - static void setGlobalPlatformDescriptor(final String bomGroupId, final String bomArtifactId, final String bomVersion, + static QuarkusPlatformDescriptor setGlobalPlatformDescriptor(final String bomGroupId, final String bomArtifactId, + final String bomVersion, MavenArtifactResolver mvn, Log log) throws MojoExecutionException { final QuarkusJsonPlatformDescriptorResolver platformResolver = QuarkusJsonPlatformDescriptorResolver.newInstance() @@ -80,6 +81,7 @@ static void setGlobalPlatformDescriptor(final String bomGroupId, final String bo } QuarkusPlatformConfig.defaultConfigBuilder().setPlatformDescriptor(platform).build(); + return platform; } public static String getDerivedPath(String className) { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/components/MavenVersionEnforcer.java b/devtools/maven/src/main/java/io/quarkus/maven/components/MavenVersionEnforcer.java index 2230406a9b00b..406799e676320 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/components/MavenVersionEnforcer.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/components/MavenVersionEnforcer.java @@ -1,6 +1,9 @@ package io.quarkus.maven.components; +import java.io.IOException; +import java.io.InputStream; import java.util.List; +import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.versioning.*; @@ -9,19 +12,43 @@ import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.component.annotations.Component; -import io.quarkus.maven.utilities.MojoUtils; - @Component(role = MavenVersionEnforcer.class, instantiationStrategy = "per-lookup") public class MavenVersionEnforcer { public void ensureMavenVersion(Log log, MavenSession session) throws MojoExecutionException { - String supported = MojoUtils.get("supported-maven-versions"); + final String supported; + try { + supported = getSupportedMavenVersions(); + } catch (IOException e) { + throw new MojoExecutionException("Failed to ensure Quarkus Maven version compatibility", e); + } String mavenVersion = session.getSystemProperties().getProperty("maven.version"); - log.debug("Detected Maven Version: " + mavenVersion); + if (log.isDebugEnabled()) { + log.debug("Detected Maven Version: " + mavenVersion); + } DefaultArtifactVersion detectedVersion = new DefaultArtifactVersion(mavenVersion); enforce(log, supported, detectedVersion); } + private static String getSupportedMavenVersions() throws IOException { + return loadQuarkusProperties().getProperty("supported-maven-versions"); + } + + private static Properties loadQuarkusProperties() throws IOException { + final String resource = "quarkus.properties"; + final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource); + if (is == null) { + throw new IOException("Could not locate " + resource + " on the classpath"); + } + final Properties props = new Properties(); + try { + props.load(is); + } catch (IOException e) { + throw new IOException("Failed to load " + resource + " from the classpath", e); + } + return props; + } + /** * Compares the specified Maven version to see if it is allowed by the defined version range. * @@ -35,27 +62,23 @@ private void enforce(Log log, throws MojoExecutionException { if (StringUtils.isBlank(requiredMavenVersionRange)) { throw new MojoExecutionException("Maven version can't be empty."); - } else { - VersionRange vr; - String msg = "Detected Maven Version (" + actualMavenVersion + ") "; - - if (actualMavenVersion.toString().equals(requiredMavenVersionRange)) { - log.debug(msg + " is allowed in " + requiredMavenVersionRange + "."); - } else { - try { - vr = VersionRange.createFromVersionSpec(requiredMavenVersionRange); - if (containsVersion(vr, actualMavenVersion)) { - log.debug(msg + " is allowed in " + requiredMavenVersionRange + "."); - } else { - String message = msg + " is not supported, it must be in " + vr + "."; - throw new MojoExecutionException(message); - } - } catch (InvalidVersionSpecificationException e) { - throw new MojoExecutionException("The requested Maven version " - + requiredMavenVersionRange + " is invalid.", e); + } + if (!actualMavenVersion.toString().equals(requiredMavenVersionRange)) { + try { + final VersionRange vr = VersionRange.createFromVersionSpec(requiredMavenVersionRange); + if (!containsVersion(vr, actualMavenVersion)) { + throw new MojoExecutionException(getDetectedVersionStr(actualMavenVersion.toString()) + + " is not supported, it must be in " + vr + "."); } + } catch (InvalidVersionSpecificationException e) { + throw new MojoExecutionException("The requested Maven version " + + requiredMavenVersionRange + " is invalid.", e); } } + if (log.isDebugEnabled()) { + log.debug( + getDetectedVersionStr(actualMavenVersion.toString()) + " is allowed in " + requiredMavenVersionRange + "."); + } } /** @@ -84,4 +107,8 @@ private static boolean containsVersion(VersionRange allowedRange, ArtifactVersio } return matched; } + + private static String getDetectedVersionStr(String version) { + return "Detected Maven Version (" + version + ") "; + } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index c7a108d5f48e1..ea01c1997e0f0 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -9,16 +9,20 @@ import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; -import org.apache.maven.model.Dependency; - import io.quarkus.cli.commands.file.BuildFile; import io.quarkus.cli.commands.file.MavenBuildFile; +import io.quarkus.cli.commands.legacy.LegacyQuarkusCommandInvocation; import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.dependencies.Extension; import io.quarkus.generators.BuildTool; -import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.tools.ToolsConstants; +import io.quarkus.platform.tools.ToolsUtils; + +public class AddExtensions implements QuarkusCommand { -public class AddExtensions { + public static final String NAME = "add-extensions"; + public static final String EXTENSIONS = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "extensions"); + public static final String OUTCOME_UPDATED = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME, "outcome", "updated"); private BuildFile buildFile; private final static Printer PRINTER = new Printer(); @@ -184,59 +188,76 @@ private static boolean matchesArtifactId(String artifactId, String q) { } public AddExtensionResult addExtensions(final Set extensions) throws IOException { - if (extensions == null || extensions.isEmpty()) { - return new AddExtensionResult(false, true); + final QuarkusCommandOutcome outcome; + try { + outcome = execute(new LegacyQuarkusCommandInvocation().setValue(EXTENSIONS, extensions)); + } catch (QuarkusCommandException e) { + throw new IOException("Failed to list extensions", e); + } + return new AddExtensionResult(outcome.getValue(OUTCOME_UPDATED, false), outcome.isSuccess()); + + } + + @Override + public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { + + final Set extensions = invocation.getValue(EXTENSIONS, Collections.emptySet()); + if (extensions.isEmpty()) { + return QuarkusCommandOutcome.success().setValue(OUTCOME_UPDATED, false); } boolean updated = false; boolean success = true; - List dependenciesFromBom = getDependenciesFromBom(); - - List registry = MojoUtils.loadExtensions(); - - for (String query : extensions) { - - if (query.contains(":")) { - // GAV case. - updated = buildFile.addExtensionAsGAV(query) || updated; - } else { - SelectionResult result = select(query, registry, false); - if (!result.matches()) { - StringBuilder sb = new StringBuilder(); - // We have 3 cases, we can still have a single candidate, but the match is on label - // or we have several candidates, or none - Set candidates = result.getExtensions(); - if (candidates.isEmpty()) { - // No matches at all. - PRINTER.nok(" Cannot find a dependency matching '" + query + "', maybe a typo?"); - success = false; - } else { - sb.append(Printer.NOK).append(" Multiple extensions matching '").append(query).append("'"); - result.getExtensions() - .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") - .append(extension.managementKey())); - sb.append(System.lineSeparator()) - .append(" Be more specific e.g using the exact name or the full GAV."); - PRINTER.print(sb.toString()); - success = false; - } - } else { // Matches. - for (Extension extension : result) { - // Don't set success to false even if the dependency is not added; as it's should be idempotent. - updated = buildFile.addDependency(dependenciesFromBom, extension) || updated; + + final List registry = invocation.getPlatformDescriptor().getExtensions(); + + try { + for (String query : extensions) { + + if (query.contains(":")) { + // GAV case. + updated = buildFile.addExtensionAsGAV(query) || updated; + } else { + SelectionResult result = select(query, registry, false); + if (!result.matches()) { + StringBuilder sb = new StringBuilder(); + // We have 3 cases, we can still have a single candidate, but the match is on label + // or we have several candidates, or none + Set candidates = result.getExtensions(); + if (candidates.isEmpty()) { + // No matches at all. + PRINTER.nok(" Cannot find a dependency matching '" + query + "', maybe a typo?"); + success = false; + } else { + sb.append(Printer.NOK).append(" Multiple extensions matching '").append(query).append("'"); + result.getExtensions() + .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") + .append(extension.managementKey())); + sb.append(System.lineSeparator()) + .append(" Be more specific e.g using the exact name or the full GAV."); + PRINTER.print(sb.toString()); + success = false; + } + } else { // Matches. + for (Extension extension : result) { + // Don't set success to false even if the dependency is not added; as it's should be idempotent. + updated = buildFile.addDependency(invocation.getPlatformDescriptor(), extension) || updated; + } } } } + } catch (IOException e) { + throw new QuarkusCommandException("Failed to add extensions", e); } if (updated) { - buildFile.close(); + try { + buildFile.close(); + } catch (IOException e) { + throw new QuarkusCommandException("Failed to update the project", e); + } } - return new AddExtensionResult(updated, success); - } - - private List getDependenciesFromBom() { - return MojoUtils.getPlatformDescriptor().getManagedDependencies(); + return new QuarkusCommandOutcome(success).setValue(OUTCOME_UPDATED, updated); } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java index da5322312b8a8..e8cc6b2aed37a 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java @@ -12,37 +12,33 @@ import static io.quarkus.generators.ProjectGenerator.PROJECT_VERSION; import static io.quarkus.generators.ProjectGenerator.QUARKUS_VERSION; import static io.quarkus.generators.ProjectGenerator.SOURCE_TYPE; -import static io.quarkus.maven.utilities.MojoUtils.getBomGroupId; -import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId; -import static io.quarkus.maven.utilities.MojoUtils.getBomVersion; -import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion; -import static io.quarkus.maven.utilities.MojoUtils.getQuarkusVersion; - import java.io.IOException; import java.util.Map; import java.util.Optional; +import java.util.Properties; import java.util.Set; import org.apache.maven.model.Model; import io.quarkus.cli.commands.file.BuildFile; import io.quarkus.cli.commands.file.MavenBuildFile; +import io.quarkus.cli.commands.legacy.LegacyQuarkusCommandInvocation; import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.generators.BuildTool; import io.quarkus.generators.ProjectGeneratorRegistry; import io.quarkus.generators.SourceType; import io.quarkus.generators.rest.BasicRestProjectGenerator; -import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.tools.ToolsUtils; /** * @author Ståle Pedersen */ -public class CreateProject { +public class CreateProject implements QuarkusCommand { private ProjectWriter writer; private String groupId; private String artifactId; - private String version = getPluginVersion(); + private String version; private SourceType sourceType = SourceType.JAVA; private BuildFile buildFile; private BuildTool buildTool; @@ -100,45 +96,58 @@ public Model getModel() { } public boolean doCreateProject(final Map context) throws IOException { + try { + return execute(new LegacyQuarkusCommandInvocation(context)).isSuccess(); + } catch (QuarkusCommandException e) { + throw new IOException("Failed to create project", e); + } + } + + public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { if (!writer.init()) { - return false; + return QuarkusCommandOutcome.failure(); } - MojoUtils.getAllProperties().forEach((k, v) -> context.put(k.replace("-", "_"), v)); + final Properties quarkusProps = ToolsUtils.readQuarkusProperties(invocation.getPlatformDescriptor()); + quarkusProps.forEach((k, v) -> invocation.setProperty(k.toString().replace("-", "_"), v.toString())); - context.put(PROJECT_GROUP_ID, groupId); - context.put(PROJECT_ARTIFACT_ID, artifactId); - context.put(PROJECT_VERSION, version); - context.put(BOM_GROUP_ID, getBomGroupId()); - context.put(BOM_ARTIFACT_ID, getBomArtifactId()); - context.put(BOM_VERSION, getBomVersion()); - context.put(QUARKUS_VERSION, getQuarkusVersion()); - context.put(SOURCE_TYPE, sourceType); - context.put(BUILD_FILE, getBuildFile()); + invocation.setProperty(PROJECT_GROUP_ID, groupId); + invocation.setProperty(PROJECT_ARTIFACT_ID, artifactId); + invocation.setProperty(PROJECT_VERSION, version); + invocation.setProperty(BOM_GROUP_ID, invocation.getPlatformDescriptor().getBomGroupId()); + invocation.setProperty(BOM_ARTIFACT_ID, invocation.getPlatformDescriptor().getBomArtifactId()); - if (extensions != null && extensions.stream().anyMatch(e -> e.toLowerCase().contains("spring-web"))) { - context.put(IS_SPRING, Boolean.TRUE); - } + try (BuildFile buildFile = getBuildFile()) { + String bomVersion = invocation.getPlatformDescriptor().getBomVersion(); - if (className != null) { - className = sourceType.stripExtensionFrom(className); - int idx = className.lastIndexOf('.'); - if (idx >= 0) { - final String packageName = className.substring(0, idx); - className = className.substring(idx + 1); - context.put(PACKAGE_NAME, packageName); + invocation.setProperty(BOM_VERSION, bomVersion); + invocation.setProperty(QUARKUS_VERSION, invocation.getPlatformDescriptor().getQuarkusVersion()); + invocation.setValue(SOURCE_TYPE, sourceType); + invocation.setValue(BUILD_FILE, buildFile); + + if (extensions != null && extensions.stream().anyMatch(e -> e.toLowerCase().contains("spring-web"))) { + invocation.setValue(IS_SPRING, Boolean.TRUE); + } + + if (className != null) { + className = sourceType.stripExtensionFrom(className); + int idx = className.lastIndexOf('.'); + if (idx >= 0) { + final String packageName = className.substring(0, idx); + className = className.substring(idx + 1); + invocation.setProperty(PACKAGE_NAME, packageName); + } + invocation.setProperty(CLASS_NAME, className); } - context.put(CLASS_NAME, className); - } - ProjectGeneratorRegistry.get(BasicRestProjectGenerator.NAME).generate(writer, context); + ProjectGeneratorRegistry.get(BasicRestProjectGenerator.NAME).generate(writer, invocation); - // call close at the end to save file - try (BuildFile buildFile = getBuildFile()) { - buildFile.completeFile(groupId, artifactId, version); + // call close at the end to save file + buildFile.completeFile(groupId, artifactId, version, invocation.getPlatformDescriptor(), quarkusProps); + } catch (IOException e) { + throw new QuarkusCommandException("Failed to create project", e); } - - return true; + return QuarkusCommandOutcome.success(); } private BuildFile getBuildFile() throws IOException { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java index a456f9034f0ec..467c1023f2987 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java @@ -1,8 +1,5 @@ package io.quarkus.cli.commands; -import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion; -import static io.quarkus.maven.utilities.MojoUtils.loadExtensions; - import java.io.IOException; import java.util.Collections; import java.util.List; @@ -17,9 +14,18 @@ import io.quarkus.cli.commands.file.BuildFile; import io.quarkus.cli.commands.file.GradleBuildFile; +import io.quarkus.cli.commands.legacy.LegacyQuarkusCommandInvocation; import io.quarkus.dependencies.Extension; +import io.quarkus.platform.tools.ToolsConstants; +import io.quarkus.platform.tools.ToolsUtils; + +public class ListExtensions implements QuarkusCommand { + public static final String NAME = "list-extensions"; + private static final String PARAM_PREFIX = ToolsUtils.dotJoin(ToolsConstants.QUARKUS, NAME); + public static final String ALL = ToolsUtils.dotJoin(PARAM_PREFIX, "all"); + public static final String FORMAT = ToolsUtils.dotJoin(PARAM_PREFIX, "format"); + public static final String SEARCH = ToolsUtils.dotJoin(PARAM_PREFIX, "search"); -public class ListExtensions { private static final String FULL_FORMAT = "%-8s %-50s %-50s %-25s%n%s"; private static final String CONCISE_FORMAT = "%-50s %-50s"; private static final String NAME_FORMAT = "%-50s"; @@ -32,9 +38,31 @@ public ListExtensions(final BuildFile buildFile) throws IOException { } public void listExtensions(boolean all, String format, String search) throws IOException { - final Map installed = findInstalled(); + try { + execute(new LegacyQuarkusCommandInvocation() + .setValue(ALL, all) + .setValue(FORMAT, format) + .setValue(SEARCH, search)); + } catch (QuarkusCommandException e) { + throw new IOException("Failed to list extensions", e); + } + } + + @Override + public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { + + final boolean all = invocation.getValue(ALL, true); + final String format = invocation.getValue(FORMAT, "concise"); + final String search = invocation.getValue(SEARCH, "*"); - Stream extensionsStream = loadExtensions().stream(); + Map installed; + try { + installed = findInstalled(); + } catch (IOException e) { + throw new QuarkusCommandException("Failed to determine the list of installed extensions", e); + } + + Stream extensionsStream = invocation.getPlatformDescriptor().getExtensions().stream(); extensionsStream = extensionsStream.filter(e -> filterUnlisted(e)); if (search != null && !"*".equalsIgnoreCase(search)) { final Pattern searchPattern = Pattern.compile(".*" + search + ".*", Pattern.CASE_INSENSITIVE); @@ -82,6 +110,8 @@ public void listExtensions(boolean all, String format, String search) throws IOE "pom.xml or use `./mvnw quarkus:add-extension -Dextensions=\"artifactId\"`"); } } + + return QuarkusCommandOutcome.success(); } private boolean filterUnlisted(Extension e) { @@ -114,31 +144,31 @@ private void nameFormatter(String[] cols) { private void display(Extension extension, final Map installed, boolean all, Consumer formatter) { - if (!all && installed.containsKey(String.format("%s:%s", extension.getGroupId(), extension.getArtifactId()))) { + final Dependency dependency = installed.get(extension.getGroupId() + ":" + extension.getArtifactId()); + if (!all && dependency != null) { return; } - final Dependency dependency = installed.get(String.format("%s:%s", extension.getGroupId(), extension.getArtifactId())); String label = ""; String version = ""; final String extracted = extractVersion(dependency); if (extracted != null) { - if (getPluginVersion().equalsIgnoreCase(extracted)) { + if (extracted.equalsIgnoreCase(extension.getVersion())) { label = "current"; version = String.format("%s", extracted); } else { label = "update"; - version = String.format("%s <> %s", extracted, getPluginVersion()); + version = String.format("%s <> %s", extracted, extension.getVersion()); } } String[] result = new String[] { label, extension.getName(), extension.getArtifactId(), version, extension.getGuide() }; - + for(int i=0;i { + + protected final QuarkusPlatformDescriptor platformDescr; + protected final MessageWriter log; + protected final Properties props; + + public QuarkusCommandInvocation(QuarkusPlatformDescriptor platformDescr, MessageWriter log) { + this(platformDescr, log, new HashMap<>(), new Properties(System.getProperties())); + } + + public QuarkusCommandInvocation(QuarkusPlatformDescriptor platformDescr, MessageWriter log, Map values, Properties props) { + super(values); + this.platformDescr = platformDescr; + this.log = log; + this.props = props; + } + + public MessageWriter getMessageWriter() { + return log; + } + + public QuarkusPlatformDescriptor getPlatformDescriptor() { + return platformDescr; + } + + public String getProperty(String name) { + final String value = props.getProperty(name, NOT_SET); + return value == NOT_SET ? System.getProperty(name) : value; + } + + public boolean hasProperty(String name) { + return props.getOrDefault(name, NOT_SET) != NOT_SET; + } + + public QuarkusCommandInvocation setProperty(String name, String value) { + props.setProperty(name, value); + return this; + } + + public Properties getProperties() { + return props; + } +} diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandOutcome.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandOutcome.java new file mode 100644 index 0000000000000..be8747ea3d9df --- /dev/null +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/QuarkusCommandOutcome.java @@ -0,0 +1,22 @@ +package io.quarkus.cli.commands; + +public class QuarkusCommandOutcome extends ValueMap { + + public static QuarkusCommandOutcome success() { + return new QuarkusCommandOutcome(true); + } + + public static QuarkusCommandOutcome failure() { + return new QuarkusCommandOutcome(false); + } + + private final boolean success; + + public QuarkusCommandOutcome(boolean success) { + this.success = success; + } + + public boolean isSuccess() { + return success; + } +} diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ValueMap.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ValueMap.java new file mode 100644 index 0000000000000..a58cc1a26ddb7 --- /dev/null +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/ValueMap.java @@ -0,0 +1,64 @@ +package io.quarkus.cli.commands; + +import java.util.HashMap; +import java.util.Map; + +public class ValueMap> { + + static final String NOT_SET = "QUARKUS_VALUE_NOT_SET"; + + protected final Map values; + + public ValueMap() { + this(new HashMap<>()); + } + + public ValueMap(Map values) { + this.values = values == null ? new HashMap<>() : values; + } + + public ValueMap(ValueMap values) { + this(values.values); + } + + public Object getValue(String name) { + return getValue(name, null); + } + + @SuppressWarnings("unchecked") + public T getValue(String name, T defaultValue) { + final Object value = values.getOrDefault(name, NOT_SET); + if(value == NOT_SET) { + return defaultValue; + } + if(value == null) { + return null; + } + return (T) value; + } + + public boolean getValue(String name, boolean defaultValue) { + final Object value = getValue(name, null); + if (value == null) { + return defaultValue; + } + if (Boolean.class.equals(value.getClass())) { + return ((Boolean) value).booleanValue(); + } + return Boolean.parseBoolean(value.toString()); + } + + public boolean hasValue(String name) { + return values.getOrDefault(name, NOT_SET) != NOT_SET; + } + + @SuppressWarnings("unchecked") + public V setValue(String name, Object value) { + values.put(name, value); + return (V) this; + } + + public V setValue(String name, boolean value) { + return setValue(name, Boolean.valueOf(value)); + } +} diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java index 01557b7f39cf3..5169fea4fd4b3 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/BuildFile.java @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.TreeMap; import org.apache.maven.model.Dependency; @@ -18,6 +19,7 @@ import io.quarkus.generators.BuildTool; import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.maven.utilities.QuarkusDependencyPredicate; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; public abstract class BuildFile implements Closeable { @@ -36,11 +38,11 @@ protected void write(String fileName, String content) throws IOException { writer.write(fileName, content); } - public boolean addDependency(List dependenciesFromBom, Extension extension) throws IOException { + public boolean addDependency(QuarkusPlatformDescriptor platform, Extension extension) throws IOException { if (!hasDependency(extension)) { PRINTER.ok(" Adding extension " + extension.managementKey()); Dependency dep; - if(containsBOM() && isDefinedInBom(dependenciesFromBom, extension)) { + if(containsBOM(platform.getBomGroupId(), platform.getBomArtifactId()) && isDefinedInBom(platform.getManagedDependencies(), extension)) { dep = extension.toDependency(true); } else { dep = extension.toDependency(false); @@ -79,7 +81,7 @@ protected boolean isDefinedInBom(List dependencies, Extension extens && dependency.getArtifactId().equalsIgnoreCase(extension.getArtifactId())); } - protected abstract boolean containsBOM() throws IOException; + protected abstract boolean containsBOM(String groupId, String artifactId) throws IOException; public abstract List getDependencies() throws IOException; @@ -124,7 +126,7 @@ protected Map mapDependencies(final List depende protected abstract List getManagedDependencies() throws IOException; - public abstract void completeFile(String groupId, String artifactId, String version) throws IOException; + public abstract void completeFile(String groupId, String artifactId, String version, QuarkusPlatformDescriptor platform, Properties props) throws IOException; public BuildTool getBuildTool() { return buildTool; diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java index 40679ff77912b..63ef0b47a871a 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java @@ -1,7 +1,5 @@ package io.quarkus.cli.commands.file; -import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -16,7 +14,8 @@ import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.dependencies.Extension; import io.quarkus.generators.BuildTool; -import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.tools.ToolsUtils; public class GradleBuildFile extends BuildFile { @@ -41,13 +40,13 @@ public void close() throws IOException { } @Override - public void completeFile(String groupId, String artifactId, String version) throws IOException { + public void completeFile(String groupId, String artifactId, String version, QuarkusPlatformDescriptor platform, Properties props) throws IOException { completeSettingsContent(artifactId); - completeBuildContent(groupId, version); - completeProperties(); + completeBuildContent(groupId, version, platform, props); + completeProperties(platform); } - private void completeBuildContent(String groupId, String version) throws IOException { + private void completeBuildContent(String groupId, String version, QuarkusPlatformDescriptor platform, Properties props) throws IOException { final String buildContent = getModel().getBuildContent(); StringBuilder res = new StringBuilder(buildContent); if (!buildContent.contains("io.quarkus:quarkus-gradle-plugin")) { @@ -57,7 +56,9 @@ private void completeBuildContent(String groupId, String version) throws IOExcep res.append(" mavenLocal()").append(System.lineSeparator()); res.append(" }").append(System.lineSeparator()); res.append(" dependencies {").append(System.lineSeparator()); - res.append(" classpath \"io.quarkus:quarkus-gradle-plugin:").append(getPluginVersion()).append("\"") + res.append(" classpath \"io.quarkus:quarkus-gradle-plugin:") + .append(ToolsUtils.getPluginVersion(props)) + .append("\"") .append(System.lineSeparator()); res.append(" }").append(System.lineSeparator()); res.append("}").append(System.lineSeparator()); @@ -65,7 +66,7 @@ private void completeBuildContent(String groupId, String version) throws IOExcep if (!buildContent.contains("apply plugin: 'io.quarkus'") && !buildContent.contains("id 'io.quarkus'")) { res.append(System.lineSeparator()).append("apply plugin: 'io.quarkus'").append(System.lineSeparator()); } - if (!containsBOM()) { + if (!containsBOM(platform.getBomGroupId(), platform.getBomArtifactId())) { res.append(System.lineSeparator()); res.append("dependencies {").append(System.lineSeparator()); res.append(" implementation enforcedPlatform(\"${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}\")") @@ -117,19 +118,19 @@ private void completeSettingsContent(String artifactId) throws IOException { getModel().setSettingsContent(res.toString()); } - private void completeProperties() throws IOException { + private void completeProperties(QuarkusPlatformDescriptor platform) throws IOException { Properties props = getModel().getPropertiesContent(); if (props.getProperty("quarkusPluginVersion") == null) { - props.setProperty("quarkusPluginVersion", getPluginVersion()); + props.setProperty("quarkusPluginVersion", ToolsUtils.getPluginVersion(ToolsUtils.readQuarkusProperties(platform))); } if(props.getProperty("quarkusPlatformGroupId") == null) { - props.setProperty("quarkusPlatformGroupId", MojoUtils.getBomGroupId()); + props.setProperty("quarkusPlatformGroupId", platform.getBomGroupId()); } if(props.getProperty("quarkusPlatformArtifactId") == null) { - props.setProperty("quarkusPlatformArtifactId", MojoUtils.getBomArtifactId()); + props.setProperty("quarkusPlatformArtifactId", platform.getBomArtifactId()); } if(props.getProperty("quarkusPlatformVersion") == null) { - props.setProperty("quarkusPlatformVersion", MojoUtils.getBomVersion()); + props.setProperty("quarkusPlatformVersion", platform.getBomVersion()); } } @@ -186,10 +187,10 @@ protected boolean hasDependency(Extension extension) throws IOException { } @Override - protected boolean containsBOM() throws IOException { - String buildContent = getModel().getBuildContent(); + protected boolean containsBOM(String groupId, String artifactId) throws IOException { + String buildContent = getModel().getBuildContent(); return buildContent.contains("enforcedPlatform(\"${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:") - || buildContent.contains("enforcedPlatform(\"" + MojoUtils.getBomGroupId() + ":" + MojoUtils.getBomArtifactId() + ":"); + || buildContent.contains("enforcedPlatform(\"" + groupId + ":" + artifactId + ":"); } @Override diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/MavenBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/MavenBuildFile.java index c8ff970cbdc6b..11ab9015722be 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/MavenBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/MavenBuildFile.java @@ -1,12 +1,6 @@ package io.quarkus.cli.commands.file; import static io.quarkus.maven.utilities.MojoUtils.configuration; -import static io.quarkus.maven.utilities.MojoUtils.getBomGroupId; -import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId; -import static io.quarkus.maven.utilities.MojoUtils.getBomVersion; -import static io.quarkus.maven.utilities.MojoUtils.getPluginArtifactId; -import static io.quarkus.maven.utilities.MojoUtils.getPluginGroupId; -import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion; import static io.quarkus.maven.utilities.MojoUtils.plugin; import java.io.ByteArrayInputStream; @@ -34,6 +28,8 @@ import io.quarkus.generators.BuildTool; import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.maven.utilities.MojoUtils.Element; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.tools.ToolsUtils; public class MavenBuildFile extends BuildFile { @@ -79,35 +75,38 @@ public List getDependencies() throws IOException { } @Override - protected boolean containsBOM() throws IOException { + protected boolean containsBOM(String groupId, String artifactId) throws IOException { if(getModel() == null || getModel().getDependencyManagement() == null) { return false; } List dependencies = getModel().getDependencyManagement().getDependencies(); return dependencies.stream() // Find bom - .filter(dependency -> "import".equalsIgnoreCase(dependency.getScope())) - .filter(dependency -> "pom".equalsIgnoreCase(dependency.getType())) + .filter(dependency -> "import".equals(dependency.getScope())) + .filter(dependency -> "pom".equals(dependency.getType())) // Does it matches the bom artifact name - .anyMatch(d -> d.getArtifactId().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_VALUE)); + .anyMatch(dependency -> dependency.getArtifactId().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_VALUE) + && dependency.getGroupId().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_GROUP_ID_VALUE)); } @Override - public void completeFile(String groupId, String artifactId, String version) throws IOException { - addQuarkusProperties(); - addBom(); - addMainPluginConfig(); - addNativeProfile(); + public void completeFile(String groupId, String artifactId, String version, QuarkusPlatformDescriptor platform, Properties props) throws IOException { + addQuarkusProperties(platform); + addBom(platform); + final String pluginGroupId = ToolsUtils.getPluginGroupId(props); + final String pluginArtifactId = ToolsUtils.getPluginArtifactId(props); + addMainPluginConfig(pluginGroupId, pluginArtifactId); + addNativeProfile(pluginGroupId, pluginArtifactId); } - private void addBom() throws IOException { + private void addBom(QuarkusPlatformDescriptor platform) throws IOException { boolean hasBom = false; DependencyManagement dm = getModel().getDependencyManagement(); if (dm == null) { dm = new DependencyManagement(); getModel().setDependencyManagement(dm); } else { - hasBom = containsBOM(); + hasBom = containsBOM(platform.getBomGroupId(), platform.getBomArtifactId()); } if (!hasBom) { @@ -122,14 +121,14 @@ private void addBom() throws IOException { } } - private void addNativeProfile() throws IOException { + private void addNativeProfile(String pluginGroupId, String pluginArtifactId) throws IOException { final boolean match = getModel().getProfiles().stream().anyMatch(p -> p.getId().equals("native")); if (!match) { PluginExecution exec = new PluginExecution(); exec.addGoal("native-image"); exec.setConfiguration(configuration(new Element("enableHttpUrlHandler", "true"))); - Plugin plg = plugin(getPluginGroupId(), getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE); + Plugin plg = plugin(pluginGroupId, pluginArtifactId, MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE); plg.addExecution(exec); BuildBase buildBase = new BuildBase(); @@ -149,14 +148,14 @@ private void addNativeProfile() throws IOException { } } - private void addMainPluginConfig() throws IOException { - if (!hasPlugin()) { + private void addMainPluginConfig(String groupId, String artifactId) throws IOException { + if (!hasPlugin(groupId, artifactId)) { Build build = createBuildSectionIfRequired(); - Plugin plugin = plugin(getPluginGroupId(), getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE); + Plugin plugin = plugin(groupId, artifactId, MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE); if (isParentPom()) { addPluginManagementSection(plugin); //strip the quarkusVersion off - plugin = plugin(getPluginGroupId(), getPluginArtifactId()); + plugin = plugin(groupId, artifactId); } PluginExecution pluginExec = new PluginExecution(); pluginExec.addGoal("build"); @@ -165,7 +164,7 @@ private void addMainPluginConfig() throws IOException { } } - private boolean hasPlugin() throws IOException { + private boolean hasPlugin(String groupId, String artifactId) throws IOException { if(getModel() == null) { return false; } @@ -183,8 +182,8 @@ private boolean hasPlugin() throws IOException { } return plugins != null && build.getPlugins() .stream() - .anyMatch(p -> p.getGroupId().equalsIgnoreCase(getPluginGroupId()) && - p.getArtifactId().equalsIgnoreCase(getPluginArtifactId())); + .anyMatch(p -> p.getGroupId().equalsIgnoreCase(groupId) && + p.getArtifactId().equalsIgnoreCase(artifactId)); } private void addPluginManagementSection(Plugin plugin) throws IOException { @@ -209,16 +208,16 @@ private Build createBuildSectionIfRequired() throws IOException { return build; } - private void addQuarkusProperties() throws IOException { + private void addQuarkusProperties(QuarkusPlatformDescriptor platform) throws IOException { Properties properties = getModel().getProperties(); if (properties == null) { properties = new Properties(); getModel().setProperties(properties); } - properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME, getPluginVersion()); - properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_GROUP_ID_NAME, getBomGroupId()); - properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_NAME, getBomArtifactId()); - properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME, getBomVersion()); + properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME, ToolsUtils.getPluginVersion(ToolsUtils.readQuarkusProperties(platform))); + properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_GROUP_ID_NAME, platform.getBomGroupId()); + properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_NAME, platform.getBomArtifactId()); + properties.putIfAbsent(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME, platform.getBomVersion()); } private boolean isParentPom() throws IOException { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/legacy/LegacyQuarkusCommandInvocation.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/legacy/LegacyQuarkusCommandInvocation.java new file mode 100644 index 0000000000000..75c7b2df5866b --- /dev/null +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/legacy/LegacyQuarkusCommandInvocation.java @@ -0,0 +1,28 @@ +package io.quarkus.cli.commands.legacy; + +import java.util.Map; +import java.util.Properties; + +import io.quarkus.cli.commands.QuarkusCommandInvocation; +import io.quarkus.platform.tools.config.QuarkusPlatformConfig; + +public class LegacyQuarkusCommandInvocation extends QuarkusCommandInvocation { + + public LegacyQuarkusCommandInvocation() { + this(null); + } + + public LegacyQuarkusCommandInvocation(Map params) { + super(QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor(), + QuarkusPlatformConfig.getGlobalDefault().getMessageWriter(), + params, + new Properties(System.getProperties())); + if (params != null && !params.isEmpty()) { + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() != null) { + setProperty(entry.getKey(), entry.getValue().toString()); + } + } + } + } +} diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/generators/ProjectGenerator.java b/independent-projects/tools/common/src/main/java/io/quarkus/generators/ProjectGenerator.java index cef618415b3b8..fdc955b093f3c 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/generators/ProjectGenerator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/generators/ProjectGenerator.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.util.Map; +import io.quarkus.cli.commands.QuarkusCommandInvocation; +import io.quarkus.cli.commands.legacy.LegacyQuarkusCommandInvocation; import io.quarkus.cli.commands.writer.ProjectWriter; public interface ProjectGenerator { @@ -24,5 +26,9 @@ public interface ProjectGenerator { String getName(); - void generate(final ProjectWriter writer, Map parameters) throws IOException; + default void generate(ProjectWriter writer, Map parameters) throws IOException { + generate(writer, new LegacyQuarkusCommandInvocation(parameters)); + } + + void generate(ProjectWriter writer, QuarkusCommandInvocation invocation) throws IOException; } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java index f004f131b9688..c364b98982900 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java @@ -4,9 +4,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; +import io.quarkus.cli.commands.QuarkusCommandInvocation; import io.quarkus.cli.commands.file.BuildFile; import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.generators.BuildTool; @@ -27,8 +26,8 @@ public String getName() { } @Override - public void generate(final ProjectWriter writer, Map parameters) throws IOException { - final BasicRestProject project = new BasicRestProject(writer, parameters); + public void generate(final ProjectWriter writer, QuarkusCommandInvocation invocation) throws IOException { + final BasicRestProject project = new BasicRestProject(writer, invocation); project.initProject(); project.setupContext(); @@ -44,7 +43,7 @@ public void generate(final ProjectWriter writer, Map parameters) } private class BasicRestProject { - private Map context; + private QuarkusCommandInvocation invocation; private String path = "/hello"; private ProjectWriter writer; private String srcMainPath; @@ -52,15 +51,19 @@ private class BasicRestProject { private String nativeTestMainPath; private SourceType type; - private BasicRestProject(final ProjectWriter writer, final Map parameters) { + private BasicRestProject(final ProjectWriter writer, QuarkusCommandInvocation invocation) { this.writer = writer; - this.context = new HashMap<>(parameters); - this.type = (SourceType) context.get(SOURCE_TYPE); + this.invocation = invocation; + this.type = invocation.getValue(SOURCE_TYPE, SourceType.JAVA); } @SuppressWarnings("unchecked") private T get(final String key, final T defaultValue) { - return (T) context.getOrDefault(key, defaultValue); + Object value = invocation.getValue(key); + if(value == null) { + value = invocation.getProperty(key); + } + return value == null ? defaultValue : (T) value; } private boolean initProject() throws IOException { @@ -83,12 +86,17 @@ private boolean initProject() throws IOException { private boolean initBuildTool() throws IOException { BuildTool buildTool = getBuildTool(); - context.putIfAbsent(ADDITIONAL_GITIGNORE_ENTRIES, buildTool.getGitIgnoreEntries()); - context.putIfAbsent(BUILD_DIRECTORY, buildTool.getBuildDirectory()); + if (!invocation.hasProperty(ADDITIONAL_GITIGNORE_ENTRIES)) { + invocation.setProperty(ADDITIONAL_GITIGNORE_ENTRIES, buildTool.getGitIgnoreEntries()); + } + if (!invocation.hasProperty(BUILD_DIRECTORY)) { + invocation.setProperty(BUILD_DIRECTORY, buildTool.getBuildDirectory()); + } + boolean newProject = !writer.exists(buildTool.getDependenciesFile()); if (newProject) { for (String buildFile : buildTool.getBuildFiles()) { - generate(type.getBuildFileResourceTemplate(getName(), buildFile), context, buildFile, buildFile); + generate(type.getBuildFileResourceTemplate(getName(), buildFile), invocation, buildFile, buildFile); } } else { String[] gav; @@ -107,8 +115,12 @@ private boolean initBuildTool() throws IOException { } } } - context.put(PROJECT_GROUP_ID, gav[0]); - context.put(PROJECT_ARTIFACT_ID, gav[1]); + if (gav[0] != null) { + invocation.setProperty(PROJECT_GROUP_ID, gav[0]); + } + if (gav[1] != null) { + invocation.setProperty(PROJECT_ARTIFACT_ID, gav[1]); + } } return newProject; } @@ -118,18 +130,17 @@ private BuildTool getBuildTool() { return buildFileManager == null ? BuildTool.MAVEN : buildFileManager.getBuildTool(); } - private void generate(final String templateName, final Map context, final String outputFilePath, + private void generate(final String templateName, QuarkusCommandInvocation invocation, final String outputFilePath, final String resourceType) throws IOException { if (!writer.exists(outputFilePath)) { - String path = templateName; - String template = MojoUtils.getPlatformDescriptor().getTemplate(path); + String template = invocation.getPlatformDescriptor().getTemplate(templateName); if (template == null) { - throw new IOException("Template resource is missing: " + path); + throw new IOException("Template resource is missing: " + templateName); } - for (Entry e : context.entrySet()) { + for (Entry e : invocation.getProperties().entrySet()) { if (e.getValue() != null) { // Exclude null values (classname and path can be null) - template = template.replace(format("${%s}", e.getKey()), e.getValue().toString()); + template = template.replace(format("${%s}", e.getKey().toString()), e.getValue().toString()); } } writer.write(outputFilePath, template); @@ -141,7 +152,7 @@ private void createIndexPage() throws IOException { String resources = "src/main/resources/META-INF/resources"; String index = writer.mkdirs(resources) + "/index.html"; if (!writer.exists(index)) { - generate("templates/index.ftl", context, index, "welcome page"); + generate("templates/index.ftl", invocation, index, "welcome page"); } } @@ -149,19 +160,19 @@ private void createIndexPage() throws IOException { private void createDockerFiles() throws IOException { String dockerRoot = "src/main/docker"; String dockerRootDir = writer.mkdirs(dockerRoot); - generate("templates/dockerfile-native.ftl", context, dockerRootDir + "/Dockerfile.native", + generate("templates/dockerfile-native.ftl", invocation, dockerRootDir + "/Dockerfile.native", "native docker file"); - generate("templates/dockerfile-jvm.ftl", context, dockerRootDir + "/Dockerfile.jvm", "jvm docker file"); + generate("templates/dockerfile-jvm.ftl", invocation, dockerRootDir + "/Dockerfile.jvm", "jvm docker file"); } private void createDockerIgnore() throws IOException { String docker = writer.mkdirs("") + ".dockerignore"; - generate("templates/dockerignore.ftl", context, docker, "docker ignore"); + generate("templates/dockerignore.ftl", invocation, docker, "docker ignore"); } private void createGitIgnore() throws IOException { String gitignore = writer.mkdirs("") + ".gitignore"; - generate("templates/gitignore.ftl", context, gitignore, "git ignore"); + generate("templates/gitignore.ftl", invocation, gitignore, "git ignore"); } private void createApplicationConfig() throws IOException { @@ -173,8 +184,8 @@ private void createApplicationConfig() throws IOException { } private void setupContext() throws IOException { - if (context.get(CLASS_NAME) != null) { - String packageName = (String) context.get(PACKAGE_NAME); + if (invocation.getProperty(CLASS_NAME) != null) { + String packageName = invocation.getProperty(PACKAGE_NAME); if (packageName != null) { String packageDir = srcMainPath + '/' + packageName.replace('.', '/'); @@ -197,7 +208,7 @@ private void setupContext() throws IOException { } private void createClasses() throws IOException { - Object className = context.get(CLASS_NAME); + Object className = invocation.getProperty(CLASS_NAME); // If className is null we disable the generation of the JAX-RS resource. if (className != null) { String extension = type.getExtension(); @@ -206,13 +217,13 @@ private void createClasses() throws IOException { String itTestClassFile = nativeTestMainPath + '/' + "Native" + className + "IT" + extension; String name = getName(); String srcResourceTemplate = type.getSrcResourceTemplate(name); - Object isSpring = context.get(IS_SPRING); - if (isSpring != null && (Boolean) context.get(IS_SPRING).equals(Boolean.TRUE)) { + Object isSpring = invocation.getValue(IS_SPRING); + if (isSpring != null && (Boolean) invocation.getValue(IS_SPRING).equals(Boolean.TRUE)) { srcResourceTemplate = type.getSrcSpringControllerTemplate(name); } - generate(srcResourceTemplate, context, classFile, "resource code"); - generate(type.getTestResourceTemplate(name), context, testClassFile, "test code"); - generate(type.getNativeTestResourceTemplate(name), context, itTestClassFile, "IT code"); + generate(srcResourceTemplate, invocation, classFile, "resource code"); + generate(type.getTestResourceTemplate(name), invocation, testClassFile, "test code"); + generate(type.getNativeTestResourceTemplate(name), invocation, itTestClassFile, "IT code"); } } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java index 97f021f9e3206..974106295b401 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java @@ -12,8 +12,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import java.util.function.Function; - import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; @@ -23,15 +21,6 @@ import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; - -import io.quarkus.bootstrap.resolver.AppModelResolverException; -import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; -import io.quarkus.dependencies.Extension; -import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; -import io.quarkus.platform.descriptor.loader.json.ArtifactResolver; -import io.quarkus.platform.tools.config.QuarkusPlatformConfig; /** * @author kameshs @@ -65,87 +54,10 @@ private static String toPropExpr(String name) { return "${" + name + "}"; } - private static Properties properties; - - public static QuarkusPlatformDescriptor getPlatformDescriptor() { - return QuarkusPlatformConfig.hasThreadLocal() - ? QuarkusPlatformConfig.getThreadLocal().getPlatformDescriptor() - : QuarkusPlatformConfig.getGlobalDefault().getPlatformDescriptor(); - } - - private static Properties getProperties() { - if(properties == null) { - try { - properties = getPlatformDescriptor().loadResource("quarkus.properties", is -> { - final Properties props = new Properties(); - props.load(is); - return props; - }); - } catch (IOException e) { - throw new IllegalStateException("The quarkus.properties file cannot be read", e); - } - } - return properties; - } - private MojoUtils() { // Avoid direct instantiation } - public static Map getAllProperties() { - Map all = new HashMap<>(); - getProperties().stringPropertyNames().forEach(s -> all.put(s, getProperties().getProperty(s))); - return all; - } - - public static String getPluginArtifactId() { - return get("plugin-artifactId"); - } - - public static String getPluginGroupId() { - return get("plugin-groupId"); - } - - public static String getPluginKey() { - return MojoUtils.getPluginGroupId() + ":" + MojoUtils.getPluginArtifactId(); - } - - public static String getPluginVersion() { - return getPlatformDescriptor().getQuarkusVersion(); - } - - public static String getBomArtifactId() { - return getPlatformDescriptor().getBomArtifactId(); - } - - public static String getBomGroupId() { - return getPlatformDescriptor().getBomGroupId(); - } - - public static String getBomVersion() { - return getPlatformDescriptor().getBomVersion(); - } - - public static String getQuarkusVersion() { - return getPlatformDescriptor().getQuarkusVersion(); - } - - public static String getProposedMavenVersion() { - return get("proposed-maven-version"); - } - - public static String getMavenWrapperVersion() { - return get("maven-wrapper-version"); - } - - public static String getGradleWrapperVersion() { - return get("gradle-wrapper-version"); - } - - public static String get(String key) { - return getProperties().getProperty(key); - } - /** * Checks whether the project has the dependency * @@ -268,10 +180,6 @@ public static void write(Model model, OutputStream fileOutputStream) throws IOEx } } - public static List loadExtensions() { - return getPlatformDescriptor().getExtensions(); - } - public static String credentials(final Dependency d) { return String.format("%s:%s", d.getGroupId(), d.getArtifactId()); } @@ -384,16 +292,19 @@ public static String[] readGavFromSettingsGradle(ByteArrayInputStream buildFileI * classpath of the context classloader */ public static Path getClassOrigin(Class cls) throws IOException { - final String pluginClassPath = cls.getName().replace('.', '/') + ".class"; - URL url = cls.getClassLoader().getResource(pluginClassPath); + return getResourceOrigin(cls.getClassLoader(), cls.getName().replace('.', '/') + ".class"); + } + + public static Path getResourceOrigin(ClassLoader cl, final String name) throws IOException { + URL url = cl.getResource(name); if (url == null) { - throw new IOException("Failed to locate the origin of " + cls); + throw new IOException("Failed to locate the origin of " + name); } String classLocation = url.toExternalForm(); if (url.getProtocol().equals("jar")) { - classLocation = classLocation.substring(4, classLocation.length() - pluginClassPath.length() - 2); + classLocation = classLocation.substring(4, classLocation.length() - name.length() - 2); } else { - classLocation = classLocation.substring(0, classLocation.length() - pluginClassPath.length()); + classLocation = classLocation.substring(0, classLocation.length() - name.length()); } return urlSpecToPath(classLocation); } @@ -406,45 +317,4 @@ private static Path urlSpecToPath(String urlSpec) throws IOException { "Failed to create an instance of " + Path.class.getName() + " from " + urlSpec, e); } } - - public static ArtifactResolver toJsonArtifactResolver(MavenArtifactResolver mvn) { - return new ArtifactResolver() { - - @Override - public T process(String groupId, String artifactId, String classifier, String type, String version, - Function processor) { - final DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, classifier, type, version); - try { - return processor.apply(mvn.resolve(artifact).getArtifact().getFile().toPath()); - } catch (AppModelResolverException e) { - throw new IllegalStateException("Failed to resolve " + artifact, e); - } - } - - @Override - public List getManagedDependencies(String groupId, String artifactId, String classifier, String type, String version) { - final List deps; - Artifact a = new DefaultArtifact(groupId, artifactId, classifier, type, version); - try { - deps = mvn.resolveDescriptor(a).getManagedDependencies(); - } catch (AppModelResolverException e) { - throw new IllegalStateException("Failed to resolve descriptor for " + a, e); - } - final List result = new ArrayList<>(deps.size()); - for(org.eclipse.aether.graph.Dependency dep : deps) { - a = dep.getArtifact(); - final Dependency d = new Dependency(); - d.setGroupId(a.getGroupId()); - d.setArtifactId(a.getArtifactId()); - d.setClassifier(a.getClassifier()); - d.setType(a.getExtension()); - d.setVersion(a.getVersion()); - d.setOptional(dep.isOptional()); - d.setScope(dep.getScope()); - result.add(d); - } - return result; - } - }; - } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java b/independent-projects/tools/common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java index 5c042c04d39ea..c148010b03989 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/platform/descriptor/loader/json/ArtifactResolver.java @@ -6,13 +6,15 @@ import org.apache.maven.model.Dependency; +import io.quarkus.bootstrap.resolver.AppModelResolverException; + public interface ArtifactResolver { - default T process(String groupId, String artifactId, String version, Function processor) { + default T process(String groupId, String artifactId, String version, Function processor) throws AppModelResolverException { return process(groupId, artifactId, null, "jar", version, processor); } - T process(String groupId, String artifactId, String classifier, String type, String version, Function processor); + T process(String groupId, String artifactId, String classifier, String type, String version, Function processor) throws AppModelResolverException; List getManagedDependencies(String groupId, String artifactId, String classifier, String type, String version); } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsConstants.java b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsConstants.java index 4fb257c72e88c..9f23b61615aa1 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsConstants.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsConstants.java @@ -4,9 +4,20 @@ public interface ToolsConstants { String IO_QUARKUS = "io.quarkus"; + String QUARKUS = "quarkus"; String QUARKUS_CORE_GROUP_ID = IO_QUARKUS; String QUARKUS_CORE_ARTIFACT_ID = "quarkus-core"; + String QUARKUS_MAVEN_PLUGIN = "quarkus-maven-plugin"; + String DEFAULT_PLATFORM_BOM_GROUP_ID = IO_QUARKUS; String DEFAULT_PLATFORM_BOM_ARTIFACT_ID = "quarkus-universe-bom"; + + String PROP_QUARKUS_PLUGIN_GROUP_ID = "plugin-groupId"; + String PROP_QUARKUS_PLUGIN_ARTIFACT_ID = "plugin-artifactId"; + String PROP_QUARKUS_PLUGIN_VERSION = "plugin-version"; + + String PROP_PROPOSED_MVN_VERSION = "proposed-maven-version"; + String PROP_MVN_WRAPPER_VERSION = "maven-wrapper-version"; + String PROP_GRADLE_WRAPPER_VERSION = "gradle-wrapper-version"; } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java index cdf5a000a6961..da18875257164 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java @@ -1,5 +1,10 @@ package io.quarkus.platform.tools; +import java.io.IOException; +import java.util.Properties; + +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; + public class ToolsUtils { public static String requireProperty(String name) { @@ -21,4 +26,62 @@ public static String getProperty(String name, String defaultValue) { public static boolean isNullOrEmpty(String arg) { return arg == null || arg.isEmpty(); } + + public static String dotJoin(String... parts) { + if(parts.length == 0) { + return null; + } + if(parts.length == 1) { + return parts[0]; + } + final StringBuilder buf = new StringBuilder(); + buf.append(parts[0]); + int i = 1; + while(i < parts.length) { + buf.append('.').append(parts[i++]); + } + return buf.toString(); + } + + public static Properties readQuarkusProperties(QuarkusPlatformDescriptor platformDescr) { + final Properties properties; + try { + properties = platformDescr.loadResource("quarkus.properties", is -> { + final Properties props = new Properties(); + props.load(is); + return props; + }); + } catch (IOException e) { + throw new IllegalStateException("Failed to read quarkus.properties", e); + } + return properties; + } + + public static String getPluginArtifactId(Properties props) { + return props.getProperty(ToolsConstants.PROP_QUARKUS_PLUGIN_ARTIFACT_ID); + } + + public static String getPluginGroupId(Properties props) { + return props.getProperty(ToolsConstants.PROP_QUARKUS_PLUGIN_GROUP_ID); + } + + public static String getPluginVersion(Properties props) { + return props.getProperty(ToolsConstants.PROP_QUARKUS_PLUGIN_VERSION); + } + + public static String getPluginKey(Properties props) { + return getPluginGroupId(props) + ":" + getPluginArtifactId(props); + } + + public static String getProposedMavenVersion(Properties props) { + return props.getProperty(ToolsConstants.PROP_PROPOSED_MVN_VERSION); + } + + public static String getMavenWrapperVersion(Properties props) { + return props.getProperty(ToolsConstants.PROP_MVN_WRAPPER_VERSION); + } + + public static String getGradleWrapperVersion(Properties props) { + return props.getProperty(ToolsConstants.PROP_GRADLE_WRAPPER_VERSION); + } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java index 3b87e65038f4c..be00c06f0c6e9 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/platform/tools/config/QuarkusPlatformConfig.java @@ -98,7 +98,7 @@ public static QuarkusPlatformConfig newInstance() { return builder().build(); } - public static QuarkusPlatformConfig getGlobalDefault() { + public static synchronized QuarkusPlatformConfig getGlobalDefault() { final QuarkusPlatformConfig c = globalConfig.get(); if(c != null) { return c; diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/AbstractAddExtensionsTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/AbstractAddExtensionsTest.java index 1ad48cdda768b..27beedc2f76bb 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/AbstractAddExtensionsTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/AbstractAddExtensionsTest.java @@ -11,9 +11,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.quarkus.maven.utilities.MojoUtils; - -abstract class AbstractAddExtensionsTest { +abstract class AbstractAddExtensionsTest extends PlatformAwareTestBase { private final Path projectPath = Paths.get("target/extensions-test"); @@ -87,7 +85,7 @@ void addExtensionTwiceInOneBatch() throws IOException { final T project = readProject(); hasDependency(project, "quarkus-agroal"); Assertions.assertEquals(1, - countDependencyOccurrences(project, MojoUtils.getPluginGroupId(), "quarkus-agroal", null)); + countDependencyOccurrences(project, getPluginGroupId(), "quarkus-agroal", null)); Assertions.assertTrue(result.isUpdated()); Assertions.assertTrue(result.succeeded()); } @@ -100,7 +98,7 @@ void addExtensionTwiceInTwoBatches() throws IOException { final T project1 = readProject(); hasDependency(project1, "quarkus-agroal"); Assertions.assertEquals(1, - countDependencyOccurrences(project1, MojoUtils.getPluginGroupId(), "quarkus-agroal", null)); + countDependencyOccurrences(project1, getPluginGroupId(), "quarkus-agroal", null)); Assertions.assertTrue(result1.isUpdated()); Assertions.assertTrue(result1.succeeded()); @@ -108,7 +106,7 @@ void addExtensionTwiceInTwoBatches() throws IOException { final T project2 = readProject(); hasDependency(project2, "quarkus-agroal"); Assertions.assertEquals(1, - countDependencyOccurrences(project2, MojoUtils.getPluginGroupId(), "quarkus-agroal", null)); + countDependencyOccurrences(project2, getPluginGroupId(), "quarkus-agroal", null)); Assertions.assertFalse(result2.isUpdated()); Assertions.assertTrue(result2.succeeded()); } @@ -193,7 +191,7 @@ void testVertxWithDot() throws IOException { } private void hasDependency(T project, String artifactId) { - hasDependency(project, MojoUtils.getPluginGroupId(), artifactId, null); + hasDependency(project, getPluginGroupId(), artifactId, null); } private void hasDependency(T project, String groupId, String artifactId, String version) { @@ -201,7 +199,7 @@ private void hasDependency(T project, String groupId, String artifactId, String } private void doesNotHaveDependency(T project, String artifactId) { - Assertions.assertTrue(countDependencyOccurrences(project, MojoUtils.getPluginGroupId(), artifactId, null) == 0); + Assertions.assertTrue(countDependencyOccurrences(project, getPluginGroupId(), artifactId, null) == 0); } protected abstract T createProject() throws IOException; diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java index 0aaed54bf8f75..e09c94274020f 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java @@ -1,6 +1,5 @@ package io.quarkus.cli.commands; -import static io.quarkus.maven.utilities.MojoUtils.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; @@ -40,7 +39,7 @@ import io.quarkus.cli.commands.writer.ZipProjectWriter; import io.quarkus.maven.utilities.MojoUtils; -public class CreateProjectTest { +public class CreateProjectTest extends PlatformAwareTestBase { @Test public void create() throws IOException { final File file = new File("target/basic-rest"); @@ -138,7 +137,8 @@ public void createOnTopPomWithoutResource() throws IOException { Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) - .contains(getPluginArtifactId(), TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -179,7 +179,8 @@ public void createOnTopPomWithResource() throws IOException { Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) - .contains(getPluginArtifactId(), TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -221,7 +222,8 @@ public void createOnTopPomWithSpringController() throws IOException { Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) - .contains(getPluginArtifactId(), TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -263,7 +265,8 @@ public void createNewWithCustomizations() throws IOException { assertThat(new File(testDir, "src/main/java/org/acme/MyApplication.java")).doesNotExist(); assertThat(contentOf(pom, "UTF-8")) - .contains(getPluginArtifactId(), TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); + assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java index 24710ddf6dd02..854a4cadde7ff 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java @@ -1,7 +1,5 @@ package io.quarkus.cli.commands; -import static io.quarkus.maven.utilities.MojoUtils.getPluginGroupId; -import static io.quarkus.maven.utilities.MojoUtils.getPluginVersion; import static io.quarkus.maven.utilities.MojoUtils.readPom; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -14,7 +12,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; - import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; @@ -25,7 +22,7 @@ import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.maven.utilities.QuarkusDependencyPredicate; -public class ListExtensionsTest { +public class ListExtensionsTest extends PlatformAwareTestBase { @Test public void listWithBom() throws IOException { diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/PlatformAwareTestBase.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/PlatformAwareTestBase.java new file mode 100644 index 0000000000000..60bc1b0c14dd6 --- /dev/null +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/PlatformAwareTestBase.java @@ -0,0 +1,60 @@ +package io.quarkus.cli.commands; + +import java.io.IOException; +import java.util.Properties; + +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.tools.config.QuarkusPlatformConfig; + +public class PlatformAwareTestBase { + + private QuarkusPlatformDescriptor platformDescr; + private Properties quarkusProps; + private String pluginGroupId; + private String pluginArtifactId; + private String pluginVersion; + + protected QuarkusPlatformDescriptor getPlatformDescriptor() { + return platformDescr == null ? platformDescr = QuarkusPlatformConfig.builder().build().getPlatformDescriptor() + : platformDescr; + } + + private Properties getQuarkusProperties() { + if(quarkusProps == null) { + try { + quarkusProps = getPlatformDescriptor().loadResource("quarkus.properties", is -> { + final Properties props = new Properties(); + props.load(is); + return props; + }); + } catch (IOException e) { + throw new IllegalStateException("Failed to load quarkus.properties", e); + } + } + return quarkusProps; + } + + protected String getPluginGroupId() { + return pluginGroupId == null ? pluginGroupId = getQuarkusProperties().getProperty("plugin-groupId") : pluginGroupId; + } + + protected String getPluginArtifactId() { + return pluginArtifactId == null ? pluginArtifactId = getQuarkusProperties().getProperty("plugin-artifactId") : pluginArtifactId; + } + + protected String getPluginVersion() { + return pluginVersion == null ? pluginVersion = getQuarkusProperties().getProperty("plugin-version") : pluginVersion; + } + + protected String getBomGroupId() { + return getPlatformDescriptor().getBomGroupId(); + } + + protected String getBomArtifactId() { + return getPlatformDescriptor().getBomArtifactId(); + } + + protected String getBomVersion() { + return getPlatformDescriptor().getBomVersion(); + } +} diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java index 4716e6c5bcc8e..846cebeb545e3 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java @@ -8,7 +8,6 @@ import static io.quarkus.generators.ProjectGenerator.PROJECT_GROUP_ID; import static io.quarkus.generators.ProjectGenerator.PROJECT_VERSION; import static io.quarkus.generators.ProjectGenerator.SOURCE_TYPE; -import static io.quarkus.maven.utilities.MojoUtils.getBomVersion; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -35,14 +34,16 @@ import com.google.common.collect.ImmutableMap; +import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.cli.commands.PlatformAwareTestBase; import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.generators.SourceType; import io.quarkus.maven.utilities.MojoUtils; -class BasicRestProjectGeneratorTest { +class BasicRestProjectGeneratorTest extends PlatformAwareTestBase { - private static final Map BASIC_PROJECT_CONTEXT = ImmutableMap. builder() + private final Map BASIC_PROJECT_CONTEXT = ImmutableMap. builder() .put(PROJECT_GROUP_ID, "org.example") .put(PROJECT_ARTIFACT_ID, "quarkus-app") .put(PROJECT_VERSION, "0.0.1-SNAPSHOT") @@ -61,11 +62,13 @@ void generateMultipleTimes() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(20); final BasicRestProjectGenerator basicRestProjectGenerator = new BasicRestProjectGenerator(); List> collect = IntStream.range(0, 20).boxed().map(i -> (Callable) () -> { - File file = Files.createTempDirectory("test").toFile(); - FileProjectWriter writer = new FileProjectWriter(file); - basicRestProjectGenerator.generate(writer, BASIC_PROJECT_CONTEXT); + final File file = Files.createTempDirectory("test").toFile(); + try (FileProjectWriter writer = new FileProjectWriter(file)) { + basicRestProjectGenerator.generate(writer, BASIC_PROJECT_CONTEXT); + } finally { + IoUtils.recursiveDelete(file.toPath()); + } latch.countDown(); - file.delete(); return null; }).collect(Collectors.toList()); executorService.invokeAll(collect); @@ -97,7 +100,7 @@ void generateFilesWithJaxRsResource() throws Exception { argThat(argument -> argument.contains("org.example") && argument.contains("quarkus-app0.0.1-SNAPSHOT") - && argument.contains("<" + MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME + ">" + MojoUtils.getPluginVersion())));// + ""))); + && argument.contains("<" + MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_NAME + ">" + getPluginVersion() + ""))); verify(mockWriter, times(1)).write(eq("src/main/java/org/example/ExampleResource.java"), argThat(argument -> argument.contains("@Path(\"/hello\")"))); verify(mockWriter, times(1)).write(eq("src/test/java/org/example/ExampleResourceTest.java"), anyString()); diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java index e73d469639705..eb33e7c8271d7 100644 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/QuarkusJsonPlatformDescriptorResolver.java @@ -15,6 +15,7 @@ import java.util.ServiceLoader; import java.util.function.Function; +import org.apache.commons.lang3.StringUtils; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; @@ -46,8 +47,15 @@ */ public class QuarkusJsonPlatformDescriptorResolver { + private static final String BUNDLED_QUARKUS_BOM_PATH = "quarkus-bom/pom.xml"; + private static final String BUNDLED_QUARKUS_PROPERTIES_PATH = "quarkus.properties"; + private static final String BUNDLED_EXTENSIONS_JSON_PATH = "quarkus-bom-descriptor/extensions.json"; + + private static final String QUARKUS_PLATFORM_DESCRIPTOR_JSON = "quarkus-platform-descriptor-json"; + private static final String DEFAULT_QUARKUS_PLATFORM_VERSION_RANGE = "[1.0.0.CR2,2)"; private static final String DEFAULT_NON_QUARKUS_VERSION_RANGE = "[0,)"; + public static final String PROP_PLATFORM_JSON_GROUP_ID = "quarkus.platform.json.groupId"; public static final String PROP_PLATFORM_JSON_ARTIFACT_ID = "quarkus.platform.json.artifactId"; public static final String PROP_PLATFORM_JSON_VERSION = "quarkus.platform.json.version"; @@ -133,10 +141,9 @@ public QuarkusJsonPlatformDescriptorResolver setMessageWriter(MessageWriter msgW public QuarkusPlatformDescriptor resolve() { - if(log == null) { - log = new DefaultMessageWriter(); - } + ensureLoggerInitialized(); + AppModelResolver artifactResolver = this.artifactResolver; if(artifactResolver == null) { try { artifactResolver = new BootstrapAppModelResolver(MavenArtifactResolver.builder().build()); @@ -147,7 +154,7 @@ public QuarkusPlatformDescriptor resolve() { try { if (jsonDescriptor != null) { - return loadFromFile(jsonDescriptor); + return loadFromFile(artifactResolver, jsonDescriptor); } return resolveJsonDescriptor(artifactResolver); } catch (VersionNotAvailableException | PlatformDescriptorLoadingException e) { @@ -155,7 +162,26 @@ public QuarkusPlatformDescriptor resolve() { } } - private QuarkusPlatformDescriptor loadFromFile(Path jsonFile) throws PlatformDescriptorLoadingException, VersionNotAvailableException { + public QuarkusPlatformDescriptor resolveBundled() { + ensureLoggerInitialized(); + final Model bundledBom = loadBundledPom(); + if(bundledBom == null) { + throw new IllegalStateException("Failed to locate bundled Quarkus platform BOM on the classpath"); + } + try { + return loadFromBomCoords(null, getGroupId(bundledBom), getArtifactId(bundledBom), getVersion(bundledBom), bundledBom); + } catch (Exception e) { + throw new IllegalStateException("Failed to load bundled Quarkus platform", e); + } + } + + private void ensureLoggerInitialized() { + if(log == null) { + log = new DefaultMessageWriter(); + } + } + + private QuarkusPlatformDescriptor loadFromFile(AppModelResolver artifactResolver, Path jsonFile) throws PlatformDescriptorLoadingException, VersionNotAvailableException { log.debug("Loading Quarkus platform descriptor from %s", jsonFile); if(!Files.exists(jsonFile)) { throw new IllegalArgumentException("Failed to locate extensions JSON file at " + jsonFile); @@ -175,7 +201,7 @@ private QuarkusPlatformDescriptor loadFromFile(Path jsonFile) throws PlatformDes log.debug("Loaded Quarkus platform is based on Quarkus %s", quarkusCoreVersion); try (InputStream is = Files.newInputStream(jsonFile)) { - return loadPlatformDescriptor(artifactResolver, is, quarkusCoreVersion); + return loadPlatformDescriptor(toLoaderResolver(artifactResolver), is, quarkusCoreVersion); } catch (VersionNotAvailableException e) { throw e; } catch (Exception e) { @@ -354,33 +380,20 @@ private QuarkusPlatformDescriptor loadFromBomCoords(AppModelResolver artifactRes } log.debug("Resolving Quarkus platform BOM %s:%s::pom:%s", bomGroupId, bomArtifactId, bomVersion); - bundledBom = loadBundledPomIfNull(bundledBom); + final Model theBundledBom = loadBundledPomIfNull(bundledBom); // Check whether the BOM on the classpath is matching the requested one - if(bundledBom != null - && bomArtifactId.equals(getArtifactId(bundledBom)) - && bomVersion.equals(getVersion(bundledBom)) - && bomGroupId.equals(getGroupId(bundledBom))) { + if(theBundledBom != null + && bomArtifactId.equals(getArtifactId(theBundledBom)) + && bomVersion.equals(getVersion(theBundledBom)) + && bomGroupId.equals(getGroupId(theBundledBom))) { log.debug("The requested Quarkus platform BOM version is available on the classpath"); // If the BOM matches, there should also be the JSON file - final InputStream jsonStream = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("quarkus-bom-descriptor/extensions.json"); + final InputStream jsonStream = getCpResourceAsStream(BUNDLED_EXTENSIONS_JSON_PATH); if (jsonStream != null) { // The JSON is available, now there also should be quarkus.properties - final InputStream quarkusPropsStream = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("quarkus.properties"); - if (quarkusPropsStream != null) { - final Properties props = new Properties(); - try { - props.load(quarkusPropsStream); - } catch (IOException e) { - throw new IllegalStateException("Failed to load quarkus.properties from the classpath", e); - } - final String quarkusVersion = props.getProperty("plugin-version"); - if (quarkusVersion == null) { - throw new IllegalStateException( - "quarkus.properties loaded from the classpath is missing plugin-version property"); - } - return loadPlatformDescriptor(artifactResolver, jsonStream, quarkusVersion); + final String quarkusVersion = getBundledPlatformQuarkusVersionOrNull(); + if (quarkusVersion != null) { + return loadPlatformDescriptor(getBundledResolver(theBundledBom), jsonStream, quarkusVersion); } else { log.debug("Failed to locate quarkus.properties on the classpath"); } @@ -399,7 +412,7 @@ private void failedDetermineDefaultPlatformCoords() { private QuarkusPlatformDescriptor loadFromJsonArtifact(AppModelResolver artifactResolver, AppArtifact jsonArtifact) throws VersionNotAvailableException { try { log.debug("Attempting to resolve Quarkus platform descriptor %s", jsonArtifact); - return loadFromFile(artifactResolver.resolve(jsonArtifact)); + return loadFromFile(artifactResolver, artifactResolver.resolve(jsonArtifact)); } catch(PlatformDescriptorLoadingException e) { // the artifact was successfully resolved but processing of it has failed throw new IllegalStateException("Failed to load Quarkus platform descriptor " + jsonArtifact, e); @@ -409,7 +422,7 @@ private QuarkusPlatformDescriptor loadFromJsonArtifact(AppModelResolver artifact final AppArtifact fallbackArtifact = new AppArtifact(jsonArtifact.getGroupId(), jsonArtifact.getArtifactId() + "-descriptor-json", null, "json", jsonArtifact.getVersion()); log.debug("Attempting to resolve Quarkus platform descriptor %s", fallbackArtifact); try { - return loadFromFile(artifactResolver.resolve(fallbackArtifact)); + return loadFromFile(artifactResolver, artifactResolver.resolve(fallbackArtifact)); } catch (AppModelResolverException e1) { throw new VersionNotAvailableException("Failed to resolve Quarkus platform descriptor " + jsonArtifact, e); } catch(Exception e2) { @@ -419,14 +432,14 @@ private QuarkusPlatformDescriptor loadFromJsonArtifact(AppModelResolver artifact } @SuppressWarnings("rawtypes") - private QuarkusPlatformDescriptor loadPlatformDescriptor(AppModelResolver mvn, final InputStream jsonStream, + private QuarkusPlatformDescriptor loadPlatformDescriptor(ArtifactResolver mvn, final InputStream jsonStream, String quarkusCoreVersion) throws PlatformDescriptorLoadingException, VersionNotAvailableException { ClassLoader jsonDescrLoaderCl = null; // check whether the quarkus-platform-descriptor-json used in the platform is already on the classpath - final String pomPropsPath = "META-INF/maven/" + ToolsConstants.IO_QUARKUS + "/quarkus-platform-descriptor-json/pom.properties"; - final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(pomPropsPath); + final String pomPropsPath = "META-INF/maven/" + ToolsConstants.IO_QUARKUS + "/" + QUARKUS_PLATFORM_DESCRIPTOR_JSON + "/pom.properties"; + final InputStream is = getCpResourceAsStream(pomPropsPath); if(is != null) { final Properties props = new Properties(); try { @@ -447,11 +460,14 @@ private QuarkusPlatformDescriptor loadPlatformDescriptor(AppModelResolver mvn, f boolean externalLoader = false; if(jsonDescrLoaderCl == null) { - final AppArtifact jsonDescrArtifact = new AppArtifact(ToolsConstants.IO_QUARKUS, "quarkus-platform-descriptor-json", null, "jar", quarkusCoreVersion); + final AppArtifact jsonDescrArtifact = new AppArtifact(ToolsConstants.IO_QUARKUS, QUARKUS_PLATFORM_DESCRIPTOR_JSON, null, "jar", quarkusCoreVersion); log.debug("Resolving Quarkus JSON platform descriptor loader %s", jsonDescrArtifact); final URL jsonDescrUrl; try { - final Path path = mvn.resolve(jsonDescrArtifact); + final Path path = mvn.process(jsonDescrArtifact.getGroupId(), jsonDescrArtifact.getArtifactId(), + jsonDescrArtifact.getClassifier(), jsonDescrArtifact.getType(), jsonDescrArtifact.getVersion(), p -> { + return p; + }); resourceLoader = Files.isDirectory(path) ? new DirectoryResourceLoader(path) : new ZipResourceLoader(path); log.debug("Quarkus platform resources will be loaded from %s", path); jsonDescrUrl = path.toUri().toURL(); @@ -476,44 +492,11 @@ private QuarkusPlatformDescriptor loadPlatformDescriptor(AppModelResolver mvn, f throw new PlatformDescriptorLoadingException( "Located more than one implementation of " + QuarkusJsonPlatformDescriptorLoader.class.getName()); } - final ArtifactResolver loaderResolver = new ArtifactResolver() { - - @Override - public T process(String groupId, String artifactId, String classifier, String type, String version, - Function processor) { - final AppArtifact artifact = new AppArtifact(groupId, artifactId, classifier, type, version); - try { - return processor.apply(artifactResolver.resolve(artifact)); - } catch (AppModelResolverException e) { - throw new IllegalStateException("Failed to resolve " + artifact, e); - } - } - - @Override - public List getManagedDependencies(String groupId, String artifactId, String classifier, - String type, String version) { - if (!"pom".equals(type)) { - throw new IllegalStateException("This implementation expects artifacts of type pom"); - } - final Path pom; - final AppArtifact pomArtifact = new AppArtifact(groupId, artifactId, classifier, type, version); - try { - pom = artifactResolver.resolve(pomArtifact); - } catch (AppModelResolverException e) { - throw new IllegalStateException("Failed to resolve " + pomArtifact, e); - } - try { - return ModelUtils.readModel(pom).getDependencyManagement().getDependencies(); - } catch (IOException e) { - throw new IllegalStateException("Failed to read model of " + pom, e); - } - } - }; try { return jsonDescrLoader.load( new QuarkusJsonPlatformDescriptorLoaderContext( - loaderResolver, + mvn, resourceLoader == null ? new ClassPathResourceLoader() : resourceLoader, log) { @Override @@ -564,7 +547,7 @@ private Model loadBundledPomIfNull(Model model) { } private Model loadBundledPom() { - final InputStream bomIs = Thread.currentThread().getContextClassLoader().getResourceAsStream("quarkus-bom/pom.xml"); + final InputStream bomIs = getCpResourceAsStream(BUNDLED_QUARKUS_BOM_PATH); if(bomIs == null) { log.debug("Failed to locate quarkus-bom/pom.xml on the classpath"); return null; @@ -623,4 +606,102 @@ private static String getVersion(Model model) { } throw new IllegalStateException("Failed to determine the version for the POM of " + model.getArtifactId()); } + + private ArtifactResolver toLoaderResolver(AppModelResolver mvn) { + return new ArtifactResolver() { + + @Override + public T process(String groupId, String artifactId, String classifier, String type, String version, + Function processor) throws AppModelResolverException { + final AppArtifact artifact = new AppArtifact(groupId, artifactId, classifier, type, version); + return processor.apply(mvn.resolve(artifact)); + } + + @Override + public List getManagedDependencies(String groupId, String artifactId, String classifier, + String type, String version) { + if (!"pom".equals(type)) { + throw new IllegalStateException("This implementation expects artifacts of type pom"); + } + final Path pom; + final AppArtifact pomArtifact = new AppArtifact(groupId, artifactId, classifier, type, version); + try { + pom = mvn.resolve(pomArtifact); + } catch (AppModelResolverException e) { + throw new IllegalStateException("Failed to resolve " + pomArtifact, e); + } + try { + return ModelUtils.readModel(pom).getDependencyManagement().getDependencies(); + } catch (IOException e) { + throw new IllegalStateException("Failed to read model of " + pom, e); + } + } + }; + } + + private ArtifactResolver getBundledResolver(final Model model) { + + final Path platformResources; + try { + platformResources = MojoUtils.getResourceOrigin(Thread.currentThread().getContextClassLoader(), BUNDLED_QUARKUS_BOM_PATH); + } catch (IOException e) { + throw new IllegalStateException("Failed to locate the bundled Quarkus platform resources on the classpath"); + } + + final ArtifactResolver bundledResolver = new ArtifactResolver() { + + @Override + public T process(String groupId, String artifactId, String classifier, String type, String version, + Function processor) { + if (QUARKUS_PLATFORM_DESCRIPTOR_JSON.equals(artifactId) + && ToolsConstants.IO_QUARKUS.equals(groupId) + && "jar".equals(type) + && StringUtils.isEmpty(classifier) + && version.equals(getBundledPlatformQuarkusVersionOrNull())) { + return processor.apply(platformResources); + } + throw new IllegalArgumentException("Unexpected artifact coordinates " + groupId + ":" + artifactId + ":" + + classifier + ":" + type + ":" + version); + } + + @Override + public List getManagedDependencies(String groupId, String artifactId, String classifier, + String type, String version) { + if(getArtifactId(model).equals(artifactId) + && "pom".equals(type) + && getVersion(model).equals(version) + && StringUtils.isEmpty(classifier) + && getGroupId(model).equals(groupId)) { + return model.getDependencyManagement().getDependencies(); + } + throw new IllegalArgumentException( + "Expected " + getGroupId(model) + ":" + getArtifactId(model) + "::pom:" + getVersion(model) + " but received " + + groupId + ":" + artifactId + ":" + classifier + ":" + type + ":" + version); + } + }; + return bundledResolver; + } + + private static String getBundledPlatformQuarkusVersionOrNull() { + final InputStream quarkusPropsStream = getCpResourceAsStream(BUNDLED_QUARKUS_PROPERTIES_PATH); + if (quarkusPropsStream == null) { + return null; + } + final Properties props = new Properties(); + try { + props.load(quarkusPropsStream); + } catch (IOException e) { + throw new IllegalStateException("Failed to load quarkus.properties from the classpath", e); + } + final String quarkusVersion = props.getProperty("plugin-version"); + if (quarkusVersion == null) { + throw new IllegalStateException( + "quarkus.properties loaded from the classpath is missing plugin-version property"); + } + return quarkusVersion; + } + + private static InputStream getCpResourceAsStream(String name) { + return Thread.currentThread().getContextClassLoader().getResourceAsStream(name); + } } diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/JsonDescriptorResolverDemo.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/JsonDescriptorResolverDemo.java deleted file mode 100644 index bdae5af02551c..0000000000000 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/JsonDescriptorResolverDemo.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.quarkus.platform.descriptor.resolver.json.demo; - -import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; -import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; -import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; -import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver; -import io.quarkus.platform.tools.DefaultMessageWriter; - -public class JsonDescriptorResolverDemo { - - public static void main(String... args) throws Exception { - - final DefaultMessageWriter log = new DefaultMessageWriter(); - log.setDebugEnabled(true); - - final QuarkusPlatformDescriptor platform = QuarkusJsonPlatformDescriptorResolver.newInstance() - .setMessageWriter(log) - .setArtifactResolver( - new BootstrapAppModelResolver( - MavenArtifactResolver.builder() - .setOffline(true) - .build())) - .resolve(); - //.resolveFromJsonArtifactId("quarkus-bom-descriptor-json"); - - log.info("Platform BOM: " + platform.getBomGroupId() + ":" + platform.getBomArtifactId() + ":" + platform.getBomVersion()); - log.info("Extensions total: " + platform.getExtensions().size()); - - log.info(platform.getTemplate("templates/basic-rest/java/resource-template.ftl")); - } -} diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/QuarkusPlatformConfigDemo.java b/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/QuarkusPlatformConfigDemo.java deleted file mode 100644 index 6a4c6d215b1d8..0000000000000 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/main/java/io/quarkus/platform/descriptor/resolver/json/demo/QuarkusPlatformConfigDemo.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.quarkus.platform.descriptor.resolver.json.demo; - -import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; -import io.quarkus.platform.tools.DefaultMessageWriter; -import io.quarkus.platform.tools.config.QuarkusPlatformConfig; - -public class QuarkusPlatformConfigDemo { - - public static void main(String... args) throws Exception { - - final DefaultMessageWriter log = new DefaultMessageWriter(); - log.setDebugEnabled(true); - - final QuarkusPlatformDescriptor platform = QuarkusPlatformConfig.defaultConfigBuilder() - .setMessageWriter(log) - .build() - .getPlatformDescriptor(); - - log.info("Platform BOM: " + platform.getBomGroupId() + ":" + platform.getBomArtifactId() + ":" + platform.getBomVersion()); - log.info("Extensions total: " + platform.getExtensions().size()); - log.info("Managed deps total: " + platform.getManagedDependencies().size()); - - log.info(platform.getTemplate("templates/basic-rest/java/resource-template.ftl")); - } -} diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java index 28768bb96ec42..3686fd212a1fb 100644 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/QuarkusJsonPlatformDescriptorResolverTest.java @@ -74,7 +74,13 @@ protected void doSetup() throws Exception { final TsArtifact universeJson = new TsArtifact(DEFAULT_PLATFORM_BOM_GROUP_ID, "other-universe" + "-descriptor-json", null, "json", "1.0.0.CR80") .setContent(new TestPlatformJsonDescriptorProvider(universeBom)); install(universeJson); + } + @Test + public void testResolveBundled() throws Exception { + final QuarkusPlatformDescriptor platform = newResolver().resolveBundled(); + assertBundledPlatform(platform, "1.0.0.CR90"); + assertEquals("1.0.0.CR90", platform.getQuarkusVersion()); } @Test @@ -168,4 +174,11 @@ private static void assertDefaultPlatform(QuarkusPlatformDescriptor platform, St assertEquals(ToolsConstants.DEFAULT_PLATFORM_BOM_ARTIFACT_ID, platform.getBomArtifactId()); assertEquals(version, platform.getBomVersion()); } + + private static void assertBundledPlatform(QuarkusPlatformDescriptor platform, String version) { + assertNotNull(platform); + assertEquals(ToolsConstants.IO_QUARKUS, platform.getBomGroupId()); + assertEquals("quarkus-bom", platform.getBomArtifactId()); + assertEquals(version, platform.getBomVersion()); + } } diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json new file mode 100644 index 0000000000000..309c411e5344b --- /dev/null +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom-descriptor/extensions.json @@ -0,0 +1,9 @@ +{ + "bom": { + "groupId": "io.quarkus", + "artifactId": "quarkus-bom", + "version": "1.0.0.CR90" + }, + "quarkus-core-version": "1.0.0.CR90", + "extensions": [] +} \ No newline at end of file diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml new file mode 100644 index 0000000000000..f325ff000f749 --- /dev/null +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus-bom/pom.xml @@ -0,0 +1,475 @@ + + + 4.0.0 + io.quarkus + quarkus-bom + 1.0.0.CR90 + Quarkus - BOM + pom + + + + + + + + + io.quarkus + quarkus-core + ${project.version} + + + + + diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties new file mode 100644 index 0000000000000..cf81a1ccd6879 --- /dev/null +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/resources/quarkus.properties @@ -0,0 +1 @@ +plugin-version = 1.0.0.CR90 \ No newline at end of file diff --git a/integration-tests/kogito-maven/pom.xml b/integration-tests/kogito-maven/pom.xml index 9f6ee86794de0..0224044b6497f 100644 --- a/integration-tests/kogito-maven/pom.xml +++ b/integration-tests/kogito-maven/pom.xml @@ -32,6 +32,12 @@ + + + src/test/resources + true + + org.apache.maven.plugins diff --git a/integration-tests/kogito-maven/src/test/java/io/quarkus/kogito/maven/it/KogitoDevModeIT.java b/integration-tests/kogito-maven/src/test/java/io/quarkus/kogito/maven/it/KogitoDevModeIT.java index c30a56707152b..cccdbea02d8df 100644 --- a/integration-tests/kogito-maven/src/test/java/io/quarkus/kogito/maven/it/KogitoDevModeIT.java +++ b/integration-tests/kogito-maven/src/test/java/io/quarkus/kogito/maven/it/KogitoDevModeIT.java @@ -14,7 +14,7 @@ public class KogitoDevModeIT extends RunAndCheckMojoTestBase { @Test public void testThatTheKogitoApplicationRuns() throws MavenInvocationException, IOException { - testDir = initProject("projects/simple-kogito", "projects/project-classic-run-kogito"); + testDir = getTargetDir("projects/simple-kogito"); run("-e"); await() diff --git a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml index 098998ca5d0af..d9420f3d82182 100644 --- a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml +++ b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml @@ -13,27 +13,27 @@ - @project.groupId@ + io.quarkus quarkus-resteasy ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-arc ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-jsonp ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-kogito ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-junit5 ${quarkus.version} test @@ -48,8 +48,8 @@ - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} @@ -67,8 +67,8 @@ - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} diff --git a/integration-tests/kotlin/pom.xml b/integration-tests/kotlin/pom.xml index 774bf6691db07..59f47ddb654ba 100644 --- a/integration-tests/kotlin/pom.xml +++ b/integration-tests/kotlin/pom.xml @@ -32,6 +32,12 @@ + + + src/test/resources + true + + org.apache.maven.plugins diff --git a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java index 4fdb3bf298984..fd8256f0247c6 100644 --- a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java +++ b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinCreateMavenProjectIT.java @@ -16,10 +16,10 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; -import io.quarkus.maven.it.MojoTestBase; +import io.quarkus.maven.it.QuarkusPlatformAwareMojoTestBase; import io.quarkus.maven.utilities.MojoUtils; -public class KotlinCreateMavenProjectIT extends MojoTestBase { +public class KotlinCreateMavenProjectIT extends QuarkusPlatformAwareMojoTestBase { private Invoker invoker; private File testDir; @@ -76,12 +76,12 @@ private InvocationResult setup(Properties params) throws MavenInvocationException, FileNotFoundException, UnsupportedEncodingException { params.setProperty("platformArtifactId", "quarkus-bom"); - params.setProperty("platformVersion", MojoUtils.getPluginVersion()); + params.setProperty("platformVersion", getPluginVersion()); InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); request.setGoals(Collections.singletonList( - MojoUtils.getPluginKey() + ":" + MojoUtils.getPluginVersion() + ":create")); + getPluginGroupId() + ":" + getPluginArtifactId() + ":" + getPluginVersion() + ":create")); request.setProperties(params); getEnv().forEach(request::addShellEnvironment); File log = new File(testDir, "build-create-" + testDir.getName() + ".log"); diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml index 2d50897ddfd9d..4d766c8c16462 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml @@ -14,27 +14,27 @@ - @project.groupId@ + io.quarkus quarkus-resteasy ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-arc ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-kotlin-deployment ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-undertow-websockets ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-junit5 ${quarkus.version} test @@ -56,8 +56,8 @@ src/test/kotlin - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} @@ -113,8 +113,8 @@ - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} diff --git a/integration-tests/kubernetes/pom.xml b/integration-tests/kubernetes/pom.xml index 8dbc2286aaec8..7f7d5b865eb05 100644 --- a/integration-tests/kubernetes/pom.xml +++ b/integration-tests/kubernetes/pom.xml @@ -37,12 +37,12 @@ - - + + + src/it true - src/main/resources - - + + maven-invoker-plugin diff --git a/integration-tests/maven/pom.xml b/integration-tests/maven/pom.xml index 7eb93d0166abe..2677dbefb200b 100644 --- a/integration-tests/maven/pom.xml +++ b/integration-tests/maven/pom.xml @@ -41,12 +41,16 @@ - - - src/main/resources + + + src/test/resources true - - + + + src/it + true + + io.quarkus @@ -96,16 +100,17 @@ quarkus-platform-descriptor-json ${project.version} - - io.quarkus - quarkus-bootstrap-core - org.apache.maven * + + io.quarkus + quarkus-platform-descriptor-resolver-json + ${project.version} + diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java index d72a80dad274a..09ae5218fa24b 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java @@ -12,9 +12,7 @@ import org.apache.maven.shared.invoker.*; import org.junit.jupiter.api.Test; -import io.quarkus.maven.utilities.MojoUtils; - -class AddExtensionIT extends MojoTestBase { +class AddExtensionIT extends QuarkusPlatformAwareMojoTestBase { private static final String QUARKUS_GROUPID = "io.quarkus"; private static final String VERTX_ARTIFACT_ID = "quarkus-vertx"; @@ -101,11 +99,11 @@ private void addExtension(boolean plural, String ext) InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); request.setGoals(Collections.singletonList( - MojoUtils.getPluginKey() + ":" + MojoUtils.getPluginVersion() + ":add-extension")); + getPluginGroupId() + ":" + getPluginArtifactId() + ":" + getPluginVersion() + ":add-extension")); Properties properties = new Properties(); properties.setProperty("platformGroupId", "io.quarkus"); properties.setProperty("platformArtifactId", "quarkus-bom"); - properties.setProperty("platformVersion", MojoUtils.getPluginVersion()); + properties.setProperty("platformVersion", getPluginVersion()); if (plural) { properties.setProperty("extensions", ext); } else { diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java index 781fbc8792b58..55713acd02471 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java @@ -26,7 +26,7 @@ /** * @author Clement Escoffier */ -public class CreateProjectMojoIT extends MojoTestBase { +public class CreateProjectMojoIT extends QuarkusPlatformAwareMojoTestBase { private Invoker invoker; private RunningInvoker running; @@ -84,18 +84,21 @@ public void testProjectGenerationFromEmptyPom() throws Exception { setup(new Properties()); assertThat(new File(testDir, "pom.xml")).isFile(); - assertThat(FileUtils.readFileToString(new File(testDir, "pom.xml"), "UTF-8")) - .contains(MojoUtils.getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, - MojoUtils.getPluginGroupId()); assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).exists(); assertThat(FileUtils.readFileToString(new File(testDir, "pom.xml"), "UTF-8")) - .containsIgnoringCase(MojoUtils.getBomArtifactId()); + .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, + getPluginGroupId()); + + final Model model = loadPom(testDir); + assertThat(model.getProperties().getProperty(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME)) + .isEqualTo(getPluginVersion()); + assertThat(model.getProperties().getProperty(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_NAME)) + .isEqualTo(getBomArtifactId()); - Model model = loadPom(testDir); assertThat(model.getDependencyManagement().getDependencies().stream() .anyMatch(d -> d.getArtifactId().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_VALUE) && d.getVersion().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_VALUE) @@ -320,12 +323,12 @@ private InvocationResult setup(Properties params) params.setProperty("platformGroupId", ToolsConstants.IO_QUARKUS); params.setProperty("platformArtifactId", "quarkus-bom"); - params.setProperty("platformVersion", MojoUtils.getPluginVersion()); + params.setProperty("platformVersion", getPluginVersion()); InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); request.setGoals(Collections.singletonList( - MojoUtils.getPluginKey() + ":" + MojoUtils.getPluginVersion() + ":create")); + getPluginGroupId() + ":" + getPluginArtifactId() + ":" + getPluginVersion() + ":create")); request.setDebug(false); request.setShowErrors(false); request.setProperties(params); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java index d64d407abfd18..8f497863575a3 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java @@ -22,9 +22,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.quarkus.maven.utilities.MojoUtils; - -class GenerateConfigIT extends MojoTestBase { +class GenerateConfigIT extends QuarkusPlatformAwareMojoTestBase { private static final String PROJECT_SOURCE_DIR = "projects/classic"; private File testDir; @@ -63,7 +61,8 @@ private void generateConfig(String filename) InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); request.setGoals(Collections - .singletonList(MojoUtils.getPluginKey() + ":" + MojoUtils.getPluginVersion() + ":generate-config")); + .singletonList(getPluginGroupId() + ":" + getPluginArtifactId() + ":" + + getPluginVersion() + ":generate-config")); Properties properties = new Properties(); properties.setProperty("file", filename); request.setProperties(properties); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java index 949f8079f52fa..22e0e04e8ce44 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java @@ -92,14 +92,14 @@ private void createAndVerifyUberJar() throws IOException, MavenInvocationExcepti @Test public void testCustomPackaging() throws MavenInvocationException, FileNotFoundException, InterruptedException { - testDir = initProject("projects/custom-packaging-plugin", "projects/custom-packaging-plugin"); + testDir = getTargetDir("projects/custom-packaging-plugin"); running = new RunningInvoker(testDir, false); MavenProcessInvocationResult result = running.execute(Collections.singletonList("install"), Collections.emptyMap()); assertThat(result.getProcess().waitFor()).isEqualTo(0); - testDir = initProject("projects/custom-packaging-app", "projects/custom-packaging-app"); + testDir = getTargetDir("projects/custom-packaging-app"); running = new RunningInvoker(testDir, false); result = running.execute(Collections.singletonList("package"), diff --git a/integration-tests/maven/src/test/resources/projects/multimodule/pom.xml b/integration-tests/maven/src/test/resources/projects/multimodule/pom.xml index c9fb70d49f20c..95607fb6f21ed 100644 --- a/integration-tests/maven/src/test/resources/projects/multimodule/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/multimodule/pom.xml @@ -42,6 +42,16 @@ pom import + + org.acme + quarkus-quickstart-multimodule-html + 1.0-SNAPSHOT + + + org.acme + quarkus-quickstart-multimodule-rest + 1.0-SNAPSHOT + diff --git a/integration-tests/maven/src/test/resources/projects/multimodule/runner/pom.xml b/integration-tests/maven/src/test/resources/projects/multimodule/runner/pom.xml index 556a296b454f4..3cc6aed402a3c 100644 --- a/integration-tests/maven/src/test/resources/projects/multimodule/runner/pom.xml +++ b/integration-tests/maven/src/test/resources/projects/multimodule/runner/pom.xml @@ -18,14 +18,12 @@ quarkus-resteasy - ${project.groupId} + org.acme quarkus-quickstart-multimodule-html - ${project.version} - ${project.groupId} + org.acme quarkus-quickstart-multimodule-rest - ${project.version} io.quarkus diff --git a/integration-tests/scala/pom.xml b/integration-tests/scala/pom.xml index 13a53f324f993..b2d901452c2b2 100644 --- a/integration-tests/scala/pom.xml +++ b/integration-tests/scala/pom.xml @@ -32,6 +32,12 @@ + + + src/test/resources + true + + org.apache.maven.plugins diff --git a/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java b/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java index e6bfd90f79ee1..92cb8977dcde0 100644 --- a/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java +++ b/integration-tests/scala/src/test/java/io/quarkus/scala/maven/it/ScalaCreateMavenProjectIT.java @@ -16,16 +16,17 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; -import io.quarkus.maven.it.MojoTestBase; +import io.quarkus.maven.it.QuarkusPlatformAwareMojoTestBase; import io.quarkus.maven.utilities.MojoUtils; -public class ScalaCreateMavenProjectIT extends MojoTestBase { +public class ScalaCreateMavenProjectIT extends QuarkusPlatformAwareMojoTestBase { private Invoker invoker; private File testDir; @Test public void testProjectGenerationFromScratchForScala() throws MavenInvocationException, IOException { + testDir = initEmptyProject("projects/project-generation-scala"); assertThat(testDir).isDirectory(); invoker = initInvoker(testDir); @@ -58,14 +59,14 @@ public void testProjectGenerationFromScratchForScala() throws MavenInvocationExc assertThat(dependencies.stream() .anyMatch(d -> d.getArtifactId().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_ARTIFACT_ID_VALUE) && d.getVersion().equals(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLATFORM_VERSION_VALUE) - && d.getScope().equalsIgnoreCase("import") - && d.getType().equalsIgnoreCase("pom"))).isTrue(); + && d.getScope().equals("import") + && d.getType().equals("pom"))).isTrue(); assertThat( - model.getDependencies().stream().anyMatch(d -> d.getArtifactId().equalsIgnoreCase("quarkus-resteasy") + model.getDependencies().stream().anyMatch(d -> d.getArtifactId().equals("quarkus-resteasy") && d.getVersion() == null)).isTrue(); assertThat( - model.getDependencies().stream().anyMatch(d -> d.getArtifactId().equalsIgnoreCase("quarkus-scala") + model.getDependencies().stream().anyMatch(d -> d.getArtifactId().equals("quarkus-scala") && d.getVersion() == null)).isTrue(); assertThat(model.getProfiles()).hasSize(1); @@ -75,20 +76,25 @@ public void testProjectGenerationFromScratchForScala() throws MavenInvocationExc private InvocationResult setup(Properties params) throws MavenInvocationException, FileNotFoundException, UnsupportedEncodingException { - params.setProperty("platformArtifactId", "quarkus-bom"); - params.setProperty("platformVersion", MojoUtils.getPluginVersion()); + try { + params.setProperty("platformGroupId", getBomGroupId()); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + params.setProperty("platformArtifactId", getBomArtifactId()); + params.setProperty("platformVersion", getPluginVersion()); InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); request.setGoals(Collections.singletonList( - MojoUtils.getPluginKey() + ":" + MojoUtils.getPluginVersion() + ":create")); + getPluginGroupId() + ":" + getPluginArtifactId() + ":" + getPluginVersion() + ":create")); request.setProperties(params); getEnv().forEach(request::addShellEnvironment); File log = new File(testDir, "build-create-" + testDir.getName() + ".log"); - PrintStreamLogger logger = new PrintStreamLogger(new PrintStream(new FileOutputStream(log), false, "UTF-8"), - InvokerLogger.DEBUG); + final PrintStreamLogger logger = new PrintStreamLogger(new PrintStream(new FileOutputStream(log), false, "UTF-8"), + InvokerLogger.INFO); invoker.setLogger(logger); return invoker.execute(request); } - } 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 a0bd5d5a40c7c..bbc9dc394971d 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 @@ -16,22 +16,22 @@ - @project.groupId@ + io.quarkus quarkus-resteasy ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-arc ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-scala-deployment ${quarkus.version} - @project.groupId@ + io.quarkus quarkus-undertow-websockets ${quarkus.version} @@ -41,7 +41,7 @@ ${scala.version} - @project.groupId@ + io.quarkus quarkus-junit5 ${quarkus.version} test @@ -58,8 +58,8 @@ src/test/scala - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} @@ -110,8 +110,8 @@ - @project.groupId@ - @project.artifactId@ + io.quarkus + quarkus-maven-plugin ${quarkus.version} diff --git a/test-framework/maven/pom.xml b/test-framework/maven/pom.xml index 4da93275ffbb6..fae4cf29b56e6 100644 --- a/test-framework/maven/pom.xml +++ b/test-framework/maven/pom.xml @@ -19,6 +19,10 @@ io.quarkus quarkus-devtools-common + + io.quarkus + quarkus-platform-descriptor-resolver-json + org.apache.maven.shared maven-invoker diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java index 31c9e0108dc13..9ba889e338e74 100644 --- a/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java +++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/MojoTestBase.java @@ -28,22 +28,8 @@ import org.apache.maven.shared.invoker.Invoker; import org.apache.maven.shared.utils.StringUtils; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import org.junit.jupiter.api.BeforeAll; - -import com.google.common.collect.ImmutableMap; - -import io.quarkus.maven.utilities.MojoUtils; public class MojoTestBase { - private static ImmutableMap VARIABLES; - - @BeforeAll - public static void init() { - VARIABLES = ImmutableMap.of( - "@project.groupId@", MojoUtils.getPluginGroupId(), - "@project.artifactId@", MojoUtils.getPluginArtifactId(), - "@project.version@", MojoUtils.getPluginVersion()); - } public static Invoker initInvoker(File root) { Invoker invoker = new DefaultInvoker(); @@ -73,6 +59,10 @@ public static File initProject(String name) { return initProject(name, name); } + public static File getTargetDir(String name) { + return new File("target/test-classes/" + name); + } + public static File initProject(String name, String output) { File tc = new File("target/test-classes"); if (!tc.isDirectory()) { @@ -81,7 +71,7 @@ public static File initProject(String name, String output) { .log(Level.FINE, "test-classes created? " + mkdirs); } - File in = new File("src/test/resources", name); + File in = new File(tc, name); if (!in.isDirectory()) { throw new RuntimeException("Cannot find directory: " + in.getAbsolutePath()); } @@ -98,36 +88,10 @@ public static File initProject(String name, String output) { } catch (IOException e) { throw new RuntimeException("Cannot copy project resources", e); } - filterPom(out); return out; } - private static void filterPom(File out) { - - File pom = new File(out, "pom.xml"); - if (pom.exists()) { - try { - filter(pom, VARIABLES); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } - } - for (File i : out.listFiles()) { - if (i.isDirectory()) { - pom = new File(i, "pom.xml"); - if (pom.exists()) { - try { - filter(pom, VARIABLES); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } - } - } - } - - } - public static void filter(File input, Map variables) throws IOException { assertThat(input).isFile(); String data = FileUtils.readFileToString(input, "UTF-8"); diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java new file mode 100644 index 0000000000000..70dc14920610a --- /dev/null +++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/QuarkusPlatformAwareMojoTestBase.java @@ -0,0 +1,53 @@ +package io.quarkus.maven.it; + +import java.util.Properties; + +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver; +import io.quarkus.platform.tools.ToolsUtils; + +public class QuarkusPlatformAwareMojoTestBase extends MojoTestBase { + + private QuarkusPlatformDescriptor platformDescr; + private Properties quarkusProps; + private String pluginGroupId; + private String pluginArtifactId; + private String pluginVersion; + + protected QuarkusPlatformDescriptor getPlatformDescriptor() { + return platformDescr == null ? platformDescr = QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled() + : platformDescr; + } + + private Properties getQuarkusProperties() { + if (quarkusProps == null) { + quarkusProps = ToolsUtils.readQuarkusProperties(getPlatformDescriptor()); + } + return quarkusProps; + } + + protected String getPluginGroupId() { + return pluginGroupId == null ? pluginGroupId = getQuarkusProperties().getProperty("plugin-groupId") : pluginGroupId; + } + + protected String getPluginArtifactId() { + return pluginArtifactId == null ? pluginArtifactId = getQuarkusProperties().getProperty("plugin-artifactId") + : pluginArtifactId; + } + + protected String getPluginVersion() { + return pluginVersion == null ? pluginVersion = getQuarkusProperties().getProperty("plugin-version") : pluginVersion; + } + + protected String getBomGroupId() { + return getPlatformDescriptor().getBomGroupId(); + } + + protected String getBomArtifactId() { + return getPlatformDescriptor().getBomArtifactId(); + } + + protected String getBomVersion() { + return getPlatformDescriptor().getBomVersion(); + } +} diff --git a/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java b/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java index 8cec0e34cdfb5..6f51cc0a7a731 100644 --- a/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java +++ b/test-framework/maven/src/main/java/io/quarkus/maven/it/assertions/SetupVerifier.java @@ -18,6 +18,9 @@ import org.codehaus.plexus.util.xml.Xpp3Dom; import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver; +import io.quarkus.platform.tools.ToolsConstants; public class SetupVerifier { @@ -49,7 +52,7 @@ public static void verifySetup(File pomFile) throws Exception { MavenProject project = new MavenProject(model); - Optional maybe = hasPlugin(project, MojoUtils.getPluginKey()); + Optional maybe = hasPlugin(project, ToolsConstants.IO_QUARKUS + ":" + ToolsConstants.QUARKUS_MAVEN_PLUGIN); assertThat(maybe).isNotEmpty(); //Check if the properties have been set correctly @@ -62,8 +65,8 @@ public static void verifySetup(File pomFile) throws Exception { // Check plugin is set Plugin plugin = maybe.orElseThrow(() -> new AssertionError("Plugin expected")); assertThat(plugin).isNotNull().satisfies(p -> { - assertThat(p.getArtifactId()).isEqualTo("quarkus-maven-plugin"); - assertThat(p.getGroupId()).isEqualTo("io.quarkus"); + assertThat(p.getArtifactId()).isEqualTo(ToolsConstants.QUARKUS_MAVEN_PLUGIN); + assertThat(p.getGroupId()).isEqualTo(ToolsConstants.IO_QUARKUS); assertThat(p.getVersion()).isEqualTo(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE); }); @@ -77,7 +80,8 @@ public static void verifySetup(File pomFile) throws Exception { assertThat(model.getProfiles()).hasSize(1); Profile profile = model.getProfiles().get(0); assertThat(profile.getId()).isEqualTo("native"); - Plugin actual = profile.getBuild().getPluginsAsMap().get(MojoUtils.getPluginKey()); + Plugin actual = profile.getBuild().getPluginsAsMap() + .get(ToolsConstants.IO_QUARKUS + ":" + ToolsConstants.QUARKUS_MAVEN_PLUGIN); assertThat(actual).isNotNull(); assertThat(actual.getExecutions()).hasSize(1).allSatisfy(exec -> { assertThat(exec.getGoals()).containsExactly("native-image"); @@ -107,7 +111,11 @@ public static void verifySetupWithVersion(File pomFile) throws Exception { Properties projectProps = project.getProperties(); assertNotNull(projectProps); assertFalse(projectProps.isEmpty()); - assertEquals(MojoUtils.getPluginVersion(), - projectProps.getProperty(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME)); + final String quarkusVersion = getPlatformDescriptor().getQuarkusVersion(); + assertEquals(quarkusVersion, projectProps.getProperty(MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_NAME)); + } + + private static QuarkusPlatformDescriptor getPlatformDescriptor() { + return QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled(); } } From b010f230ea1c9a4135dff4f5326e3f403ea8beab Mon Sep 17 00:00:00 2001 From: aurea munoz Date: Mon, 25 Nov 2019 10:56:45 +0100 Subject: [PATCH 086/602] doc: document how to use 999-SNAPSHOT of quarkus Related to #5687 --- docs/src/main/asciidoc/maven-tooling.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index 24f94e68ef531..01ad7f8cbb406 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -44,6 +44,18 @@ The following table lists the attributes you can pass to the `create` command: | `1.0-SNAPSHOT` | The version of the created project +| `platformGroupId` +| `io.quarkus` +| The group id of the target platform. Given that all the existing platforms are coming from `io.quarkus` this one won't practically be used explicitly. But it's still an option. + +| `platformArtifactId` +| `quarkus-universe-bom` +| The artifact id of the target platform BOM. It should be `quarkus-bom` in order to use the locally built Quarkus. + +| `platformVersion` +| If it's not specified, the latest one will be resolved. +| The version of the platform you want the project to use. It can also accept a version range, in which case the latest from the specified range will be used. + | `className` | _Not created if omitted_ | The fully qualified name of the generated resource @@ -58,6 +70,8 @@ The following table lists the attributes you can pass to the `create` command: |=== +By default, the command will target the latest version of `quarkus-universe-bom` (unless specific coordinates have been specified). If you run offline however, it will look for the latest locally available and if `quarkus-universe-bom` (satisfying the default version range which is currently up to 2.0) is not available locally, it will fallback to the bundled platform based on `quarkus-bom` (the version will match the version of the plugin). + If you decide to generate a REST resource (using the `className` attribute), the endpoint is exposed at: `http://localhost:8080/$path`. If you use the default `path`, the URL is: http://localhost:8080/hello. From ee92d8a67b792f01a8a5b0c6a7aee35ba1484a3b Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 22 Nov 2019 11:08:03 -0300 Subject: [PATCH 087/602] Add .dependabot/config.yml --- .dependabot/config.yml | 14 ++++++++++++++ azure-pipelines.yml | 1 + 2 files changed, 15 insertions(+) create mode 100644 .dependabot/config.yml diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 0000000000000..8d6b0660ae393 --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,14 @@ +version: 1 +update_configs: + - package_manager: "java:maven" + directory: "/bom/runtime" + update_schedule: "daily" + allowed_updates: + - match: + update_type: "security" + - match: + dependency_name: "org.flywaydb:flyway-core" + - match: + dependency_name: "org.eclipse.jgit:org.eclipse.jgit" + - match: + dependency_name: "io.fabric8:kubernetes-client-bom" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c11c706d73e67..995fffc882737 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,6 +18,7 @@ pr: - LICENSE.txt - dco.txt - .github/ISSUE_TEMPLATE/*.md + - .dependabot/config.yml variables: MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository/ From 337b49abd136482e6b9e05cbb1c45a5e69c10811 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 25 Nov 2019 19:18:38 -0300 Subject: [PATCH 088/602] Bump Jandex to 2.1.2.Final This version contains support for Dynamic Constants (JEP 309) which is essential for JDK 11 support --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 690a3cf84ab7c..bc2be410c4614 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -15,7 +15,7 @@ 1.11 - 2.1.1.Final + 2.1.2.Final 4.4.1.Final 0.31.0 0.4.1 From 14121f12022e9ab6dc58f7a48499e748880186de Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 26 Nov 2019 14:15:52 -0300 Subject: [PATCH 089/602] Set dependabot directory config to root This allows dependabot to also update dependency references that are not included in the BOM --- .dependabot/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 8d6b0660ae393..e9720acc479ad 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -1,7 +1,8 @@ +# Documentation in https://dependabot.com/docs/config-file/ version: 1 update_configs: - package_manager: "java:maven" - directory: "/bom/runtime" + directory: "/" update_schedule: "daily" allowed_updates: - match: From 80e11941045c7a5724f53ebee35b27730e3ba892 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2019 14:34:58 +0000 Subject: [PATCH 090/602] Bump slf4j.version from 1.7.25 to 1.7.29 Bumps `slf4j.version` from 1.7.25 to 1.7.29. Updates `slf4j-api` from 1.7.25 to 1.7.29 - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.25...v_1.7.29) Updates `jcl-over-slf4j` from 1.7.25 to 1.7.29 - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.25...v_1.7.29) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index bc2be410c4614..16d633d53a868 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -93,7 +93,7 @@ 2.2.13 1.0.6.Final 2.1.0.Final - 1.7.25 + 1.7.29 1.2.0.Final 1.5.0.Final-format-001 1.0.1.Final From 372fef98caa74eb043fb2954b899b569233bcfa2 Mon Sep 17 00:00:00 2001 From: Ken Finnigan Date: Wed, 13 Nov 2019 09:36:26 -0500 Subject: [PATCH 091/602] Fixes #4516 - Ensures Config is serializable - Passes Config serialization TCK test --- .../AbstractDelegatingConfigSource.java | 6 ++- .../AbstractRawDefaultConfigSource.java | 5 ++- .../configuration/CidrAddressConverter.java | 6 ++- .../DeploymentProfileConfigSource.java | 23 +++++++++++ .../configuration/DurationConverter.java | 4 +- .../configuration/ExpandingConfigSource.java | 25 +++++++++++- .../configuration/HyphenateEnumConverter.java | 4 +- .../configuration/InetAddressConverter.java | 5 ++- .../InetSocketAddressConverter.java | 5 ++- .../configuration/MemorySizeConverter.java | 4 +- .../runtime/configuration/PathConverter.java | 5 ++- .../runtime/configuration/RegexConverter.java | 5 ++- .../runtime/logging/LevelConverter.java | 5 ++- tcks/microprofile-config/pom.xml | 3 +- .../tck/config/CustomConfigProviderTest.java | 35 ++++++++++++++++- .../tck/config/CustomObjectInputStream.java | 25 ++++++++++++ .../http/TestHTTPConfigSourceProvider.java | 39 +++++++++++-------- 17 files changed, 171 insertions(+), 33 deletions(-) create mode 100644 tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomObjectInputStream.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java index 99bb69b3c0af0..bf7672e3eb2ee 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java @@ -1,5 +1,6 @@ package io.quarkus.runtime.configuration; +import java.io.Serializable; import java.util.Map; import java.util.Set; @@ -12,9 +13,10 @@ /** * A base class for configuration sources which delegate to other configuration sources. */ -public abstract class AbstractDelegatingConfigSource implements ConfigSource { +public abstract class AbstractDelegatingConfigSource implements ConfigSource, Serializable { + private static final long serialVersionUID = -6636734120743034580L; protected final ConfigSource delegate; - private Map propertiesMap; + protected Map propertiesMap; /** * Construct a new instance. diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractRawDefaultConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractRawDefaultConfigSource.java index 3e2f4915cd5ff..3b1d418c9a72b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractRawDefaultConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractRawDefaultConfigSource.java @@ -1,5 +1,6 @@ package io.quarkus.runtime.configuration; +import java.io.Serializable; import java.util.Collections; import java.util.Map; @@ -8,7 +9,9 @@ /** * The base class for the config source that yields the 'raw' default values. */ -public abstract class AbstractRawDefaultConfigSource implements ConfigSource { +public abstract class AbstractRawDefaultConfigSource implements ConfigSource, Serializable { + private static final long serialVersionUID = 2524612253582530249L; + protected AbstractRawDefaultConfigSource() { } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java index 708693b26cccb..2443f5cec5e42 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/CidrAddressConverter.java @@ -2,6 +2,8 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; + import javax.annotation.Priority; import org.eclipse.microprofile.config.spi.Converter; @@ -12,7 +14,9 @@ * A converter which converts a CIDR address into an instance of {@link CidrAddress}. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class CidrAddressConverter implements Converter { +public class CidrAddressConverter implements Converter, Serializable { + + private static final long serialVersionUID = 2023552088048952902L; @Override public CidrAddress convert(String value) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java index 236286f6a4533..c7da037b9d170 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java @@ -1,5 +1,7 @@ package io.quarkus.runtime.configuration; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.HashSet; import java.util.Set; import java.util.function.UnaryOperator; @@ -11,6 +13,7 @@ * A configuration source which supports deployment profiles. */ public class DeploymentProfileConfigSource extends AbstractDelegatingConfigSource { + private static final long serialVersionUID = -8001338475089294128L; private final String profilePrefix; @@ -35,6 +38,26 @@ public DeploymentProfileConfigSource(final ConfigSource delegate, final String p profilePrefix = "%" + profileName + "."; } + Object writeReplace() throws ObjectStreamException { + return new Ser(delegate, profilePrefix); + } + + static final class Ser implements Serializable { + private static final long serialVersionUID = -4618790131794331510L; + + final ConfigSource d; + final String p; + + Ser(final ConfigSource d, String p) { + this.d = d; + this.p = p; + } + + Object readResolve() { + return new DeploymentProfileConfigSource(d, p); + } + } + public Set getPropertyNames() { Set propertyNames = delegate.getPropertyNames(); //if a key is only present in a profile we still want the unprofiled key name to show up diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java index 6e1406aedc601..8c04d55c5e4aa 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.time.Duration; import java.time.format.DateTimeParseException; import java.util.regex.Pattern; @@ -14,7 +15,8 @@ * A converter for a {@link Duration} interface. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class DurationConverter implements Converter { +public class DurationConverter implements Converter, Serializable { + private static final long serialVersionUID = 7499347081928776532L; private static final String PERIOD_OF_TIME = "PT"; private static final Pattern DIGITS = Pattern.compile("^[-+]?\\d+$"); private static final Pattern START_WITH_DIGITS = Pattern.compile("^[-+]?\\d+.*"); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java index 86c28d94a8a22..b5aeb7e9c8460 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java @@ -1,5 +1,7 @@ package io.quarkus.runtime.configuration; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.UnaryOperator; @@ -13,6 +15,7 @@ */ public class ExpandingConfigSource extends AbstractDelegatingConfigSource { + private static final long serialVersionUID = 1075000015425893741L; private static final ThreadLocal NO_EXPAND = new ThreadLocal<>(); public static UnaryOperator wrapper(Cache cache) { @@ -44,6 +47,24 @@ public String getValue(final String propertyName) { return isExpanding() ? expand(delegateValue) : delegateValue; } + Object writeReplace() throws ObjectStreamException { + return new Ser(delegate); + } + + static final class Ser implements Serializable { + private static final long serialVersionUID = 3633535720479375279L; + + final ConfigSource d; + + Ser(final ConfigSource d) { + this.d = d; + } + + Object readResolve() { + return new ExpandingConfigSource(d, new Cache()); + } + } + String expand(final String value) { return expandValue(value, cache); } @@ -83,7 +104,9 @@ public static String expandValue(String value, Cache cache) { /** * An expression cache to use with {@link ExpandingConfigSource}. */ - public static final class Cache { + public static final class Cache implements Serializable { + private static final long serialVersionUID = 6111143168103886992L; + // this is a cache of compiled expressions, NOT a cache of expanded values final ConcurrentHashMap exprCache = new ConcurrentHashMap<>(); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java index 20032f9b714f6..81e7edcb9c174 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java @@ -1,5 +1,6 @@ package io.quarkus.runtime.configuration; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -12,9 +13,10 @@ /** * A converter for hyphenated enums. */ -public final class HyphenateEnumConverter> implements Converter { +public final class HyphenateEnumConverter> implements Converter, Serializable { private static final String HYPHEN = "-"; private static final Pattern PATTERN = Pattern.compile("([-_]+)"); + private static final long serialVersionUID = 5675903245398498741L; private final Class enumType; private final Map values = new HashMap<>(); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java index b7adf02c8ad6e..a1bbbcd1575cf 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetAddressConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.net.InetAddress; import java.net.UnknownHostException; @@ -14,7 +15,9 @@ * A converter which produces values of type {@link InetAddress}. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class InetAddressConverter implements Converter { +public class InetAddressConverter implements Converter, Serializable { + + private static final long serialVersionUID = 4539214213710330204L; @Override public InetAddress convert(String value) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java index ed3a3785e3512..d6dad37ef5e7e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/InetSocketAddressConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -16,7 +17,9 @@ * an unresolved instance is returned. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class InetSocketAddressConverter implements Converter { +public class InetSocketAddressConverter implements Converter, Serializable { + + private static final long serialVersionUID = 1928336763333858343L; @Override public InetSocketAddress convert(String value) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java index e4361653cfc75..f88cc13f87798 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; @@ -16,10 +17,11 @@ * A converter to support data sizes. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class MemorySizeConverter implements Converter { +public class MemorySizeConverter implements Converter, Serializable { private static final Pattern MEMORY_SIZE_PATTERN = Pattern.compile("^(\\d+)([BbKkMmGgTtPpEeZzYy]?)$"); static final BigInteger KILO_BYTES = BigInteger.valueOf(1024); private static final Map MEMORY_SIZE_MULTIPLIERS; + private static final long serialVersionUID = -1988485929047973068L; static { MEMORY_SIZE_MULTIPLIERS = new HashMap<>(); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java index a783840069ad8..e113a49813308 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,7 +14,9 @@ * A converter for a {@link Path} interface. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class PathConverter implements Converter { +public class PathConverter implements Converter, Serializable { + + private static final long serialVersionUID = 4452863383998867844L; @Override public Path convert(String value) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java index ad6e72093c48b..0f8babbc4b906 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.util.regex.Pattern; import javax.annotation.Priority; @@ -12,7 +13,9 @@ * A converter to support regular expressions. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public class RegexConverter implements Converter { +public class RegexConverter implements Converter, Serializable { + + private static final long serialVersionUID = -2627801624423530576L; /** * Construct a new instance. diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LevelConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LevelConverter.java index b87e84404739c..8669ff5e50a86 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LevelConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LevelConverter.java @@ -2,6 +2,7 @@ import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY; +import java.io.Serializable; import java.util.logging.Level; import javax.annotation.Priority; @@ -13,7 +14,9 @@ * A simple converter for logging levels. */ @Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY) -public final class LevelConverter implements Converter { +public final class LevelConverter implements Converter, Serializable { + + private static final long serialVersionUID = 704275577610445233L; public Level convert(final String value) { if (value == null || value.isEmpty()) { diff --git a/tcks/microprofile-config/pom.xml b/tcks/microprofile-config/pom.xml index d97f5b4c70982..c8be3cfc68e74 100644 --- a/tcks/microprofile-config/pom.xml +++ b/tcks/microprofile-config/pom.xml @@ -40,8 +40,7 @@ org.eclipse.microprofile.config.tck.EmptyValuesTest - - + org.eclipse.microprofile.config.tck.ConfigProviderTest diff --git a/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java index d08a201848e53..d5f07b821e05d 100644 --- a/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java +++ b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomConfigProviderTest.java @@ -1,11 +1,27 @@ package io.quarkus.tck.config; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.tck.ConfigProviderTest; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.testng.Assert; import org.testng.annotations.Test; import io.quarkus.runtime.configuration.ExpandingConfigSource; public class CustomConfigProviderTest extends ConfigProviderTest { + + @Inject + private Config config; + @Test public void testEnvironmentConfigSource() { // this test fails when there is a expression-like thing in an env prop @@ -17,8 +33,25 @@ public void testEnvironmentConfigSource() { } } - @Test(enabled = false) + @Test public void testInjectedConfigSerializable() { + // Needed a custom ObjectInputStream.resolveClass() to use a ClassLoader with Quarkus generated classes in it + // Everything else is identical to the test class it overwrites + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream)) { + out.writeObject(config); + } catch (IOException ex) { + Assert.fail("Injected config should be serializable, but could not serialize it", ex); + } + Object readObject = null; + try (ObjectInputStream in = new CustomObjectInputStream( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))) { + readObject = in.readObject(); + } catch (IOException | ClassNotFoundException ex) { + Assert.fail("Injected config should be serializable, but could not deserialize a previously serialized instance", + ex); + } + MatcherAssert.assertThat("Deserialized object", readObject, CoreMatchers.instanceOf(Config.class)); } @Test diff --git a/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomObjectInputStream.java b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomObjectInputStream.java new file mode 100644 index 0000000000000..090dc586661af --- /dev/null +++ b/tcks/microprofile-config/src/test/java/io/quarkus/tck/config/CustomObjectInputStream.java @@ -0,0 +1,25 @@ +package io.quarkus.tck.config; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +public class CustomObjectInputStream extends ObjectInputStream { + + public CustomObjectInputStream(InputStream in) throws IOException { + super(in); + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + String name = desc.getName(); + try { + return Class.forName(name, false, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException ex) { + // Do nothing + } + // Fallback to parent implementation if we can't find the class + return super.resolveClass(desc); + } +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java index 887e880605cc4..d56e3ce725c7d 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPConfigSourceProvider.java @@ -1,5 +1,6 @@ package io.quarkus.test.common.http; +import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -28,22 +29,26 @@ public class TestHTTPConfigSourceProvider implements ConfigSourceProvider { } public Iterable getConfigSources(final ClassLoader forClassLoader) { - return Collections.singletonList(new ConfigSource() { - public Map getProperties() { - return entries; - } - - public String getValue(final String propertyName) { - return entries.get(propertyName); - } - - public String getName() { - return "test URL provider"; - } - - public int getOrdinal() { - return Integer.MIN_VALUE + 1000; - } - }); + return Collections.singletonList(new TestURLConfigSource()); + } + + static class TestURLConfigSource implements ConfigSource, Serializable { + private static final long serialVersionUID = 4841094273900625000L; + + public Map getProperties() { + return entries; + } + + public String getValue(final String propertyName) { + return entries.get(propertyName); + } + + public String getName() { + return "test URL provider"; + } + + public int getOrdinal() { + return Integer.MIN_VALUE + 1000; + } } } From 82b43f33bbf2036aa1d2f4b806fc83f230c8b41a Mon Sep 17 00:00:00 2001 From: Ken Finnigan Date: Tue, 26 Nov 2019 09:28:06 -0500 Subject: [PATCH 092/602] Switch field back to private as it was made protected by mistake --- .../runtime/configuration/AbstractDelegatingConfigSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java index bf7672e3eb2ee..95d3feddfef55 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractDelegatingConfigSource.java @@ -16,7 +16,7 @@ public abstract class AbstractDelegatingConfigSource implements ConfigSource, Serializable { private static final long serialVersionUID = -6636734120743034580L; protected final ConfigSource delegate; - protected Map propertiesMap; + private Map propertiesMap; /** * Construct a new instance. From d2c8a005f0e54c95697994671d3a41a37dbb8be3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2019 17:55:41 +0000 Subject: [PATCH 093/602] Bump flyway-core from 6.0.8 to 6.1.0 Bumps [flyway-core](https://github.com/flyway/flyway) from 6.0.8 to 6.1.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-6.0.8...flyway-6.1.0) Signed-off-by: dependabot-preview[bot] Using updated org.flywaydb.core.internal.scanner.Scanner constructor --- bom/runtime/pom.xml | 2 +- .../io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 16d633d53a868..886df06ef2fc5 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -148,7 +148,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.0.8 + 6.1.0 1.0.5 4.0.0-beta03 3.10.2 diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java index f952abf41158b..46ddd977c8b0b 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java @@ -7,6 +7,7 @@ import org.flywaydb.core.api.Location; import org.flywaydb.core.internal.resource.LoadableResource; +import org.flywaydb.core.internal.scanner.ResourceNameCache; import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; import com.oracle.svm.core.annotate.Alias; @@ -21,6 +22,7 @@ public final class ScannerSubstitutions { @Alias private List resources = new ArrayList<>(); + @Alias private List> classes = new ArrayList<>(); @@ -33,7 +35,7 @@ public final class ScannerSubstitutions { */ @Substitute public ScannerSubstitutions(Class implementedInterface, Collection locations, ClassLoader classLoader, - Charset encoding) { + Charset encoding, ResourceNameCache resourceNameCache) { ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(); resources.addAll(quarkusScanner.scanForResources()); classes.addAll(quarkusScanner.scanForClasses()); From 251f1f92a095b60c1745eefacab5ccdef56c90f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danilo=20Magalh=C3=A3es?= Date: Tue, 26 Nov 2019 13:46:42 +0000 Subject: [PATCH 094/602] Fixes #5765 - Remove the "preview" note from the guide - Update the quarkus-extension.yaml file --- docs/src/main/asciidoc/scheduler.adoc | 7 ------- .../src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/src/main/asciidoc/scheduler.adoc b/docs/src/main/asciidoc/scheduler.adoc index 14c6380fa626d..6981be9a3e1a6 100644 --- a/docs/src/main/asciidoc/scheduler.adoc +++ b/docs/src/main/asciidoc/scheduler.adoc @@ -10,13 +10,6 @@ include::./attributes.adoc[] Modern applications often need to run specific tasks periodically. In this guide, you learn how to schedule periodic tasks. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== - == Prerequisites To complete this guide, you need: diff --git a/extensions/scheduler/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/scheduler/runtime/src/main/resources/META-INF/quarkus-extension.yaml index a518bc3dfed0f..d8bdef06be207 100644 --- a/extensions/scheduler/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/scheduler/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,4 +8,4 @@ metadata: guide: "https://quarkus.io/guides/scheduler" categories: - "miscellaneous" - status: "preview" + status: "stable" From 3adabba771771e04c50f5a817e8be09582933c57 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 27 Nov 2019 09:12:57 +1100 Subject: [PATCH 095/602] Upgrade jandex-maven-plugin to 1.0.7 --- build-parent/pom.xml | 2 +- docs/src/main/asciidoc/cdi-reference.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index f08feddd138d9..d19a36b1f1304 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -299,7 +299,7 @@ org.jboss.jandex jandex-maven-plugin - 1.0.6 + 1.0.7 net.revelc.code.formatter diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index bce767ca7c3d8..fb870debc97a4 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -55,7 +55,7 @@ To generate the index just add the following to your `pom.xml`: org.jboss.jandex jandex-maven-plugin - 1.0.6 + 1.0.7 make-index From 7063224350e52e10d3fbe9bc87370a8c47f933db Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2019 14:36:13 +0000 Subject: [PATCH 096/602] Bump docker-maven-plugin from 0.29.0 to 0.31.0 Bumps [docker-maven-plugin](https://github.com/fabric8io/docker-maven-plugin) from 0.29.0 to 0.31.0. - [Release notes](https://github.com/fabric8io/docker-maven-plugin/releases) - [Changelog](https://github.com/fabric8io/docker-maven-plugin/blob/master/doc/changelog.md) - [Commits](https://github.com/fabric8io/docker-maven-plugin/compare/v0.29.0...v0.31.0) Signed-off-by: dependabot-preview[bot] --- build-parent/pom.xml | 2 +- integration-tests/keycloak-authorization/pom.xml | 1 - integration-tests/oidc-code-flow/pom.xml | 1 - integration-tests/oidc/pom.xml | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index d19a36b1f1304..3d7b975b63571 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -286,7 +286,7 @@ io.fabric8 docker-maven-plugin - 0.29.0 + 0.31.0 maven-javadoc-plugin diff --git a/integration-tests/keycloak-authorization/pom.xml b/integration-tests/keycloak-authorization/pom.xml index 76014dfccd2ac..041078e677612 100644 --- a/integration-tests/keycloak-authorization/pom.xml +++ b/integration-tests/keycloak-authorization/pom.xml @@ -189,7 +189,6 @@ io.fabric8 docker-maven-plugin - 0.28.0 diff --git a/integration-tests/oidc-code-flow/pom.xml b/integration-tests/oidc-code-flow/pom.xml index 06bf52a0dfb67..6189bf349dc63 100644 --- a/integration-tests/oidc-code-flow/pom.xml +++ b/integration-tests/oidc-code-flow/pom.xml @@ -214,7 +214,6 @@ io.fabric8 docker-maven-plugin - 0.28.0 diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index b27ce1870a00a..0570ebfe19344 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -195,7 +195,6 @@ io.fabric8 docker-maven-plugin - 0.28.0 From b400582fe6e98298381c856c2b706c36214c8060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferraz=20Campos=20Florentino?= Date: Tue, 26 Nov 2019 12:39:06 -0300 Subject: [PATCH 097/602] Fix configure parameter Class Fix configure parameter Classs. Changing ElasticsearchAnalysisDefinitionContainerContext to ElasticsearchAnalysisConfigurationContext --- docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc index bbeb67b5209aa..688a7c7619379 100644 --- a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc +++ b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc @@ -431,13 +431,13 @@ To fulfill our requirements, let's create the following implementation: ---- package org.acme.hibernate.search.elasticsearch.config; +import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurationContext; import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer; -import org.hibernate.search.backend.elasticsearch.analysis.model.dsl.ElasticsearchAnalysisDefinitionContainerContext; public class AnalysisConfigurer implements ElasticsearchAnalysisConfigurer { @Override - public void configure(ElasticsearchAnalysisDefinitionContainerContext context) { + public void configure(ElasticsearchAnalysisConfigurationContext context) { context.analyzer("name").custom() // <1> .tokenizer("standard") .tokenFilters("asciifolding", "lowercase"); From e138b2c7286f192be5961b088f8145dd014250f7 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 26 Nov 2019 23:45:34 +0000 Subject: [PATCH 098/602] Remove the nimbus test dependency --- extensions/smallrye-jwt/deployment/pom.xml | 6 -- .../java/io/quarkus/jwt/test/TokenUtils.java | 60 +++++++------------ 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/extensions/smallrye-jwt/deployment/pom.xml b/extensions/smallrye-jwt/deployment/pom.xml index e1e822600674c..53e66713f62f8 100644 --- a/extensions/smallrye-jwt/deployment/pom.xml +++ b/extensions/smallrye-jwt/deployment/pom.xml @@ -52,12 +52,6 @@ rest-assured test - - com.nimbusds - nimbus-jose-jwt - 6.7 - test - diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java index 38f7d876020ec..52a96c7be2bb2 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java @@ -1,13 +1,10 @@ package io.quarkus.jwt.test; -import static net.minidev.json.parser.JSONParser.DEFAULT_PERMISSIVE_MODE; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyPair; @@ -15,7 +12,6 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; @@ -23,19 +19,13 @@ import java.util.Map; import java.util.Set; -import org.eclipse.microprofile.jwt.Claims; - -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.crypto.MACSigner; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; +import javax.crypto.KeyGenerator; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; +import org.eclipse.microprofile.jwt.Claims; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; /** * Utilities for generating a JWT for testing @@ -127,11 +117,11 @@ public static String generateTokenString(PrivateKey pk, String kid, String jsonR byte[] content = new byte[length]; System.arraycopy(tmp, 0, content, 0, length); - JSONParser parser = new JSONParser(DEFAULT_PERMISSIVE_MODE); - JSONObject jwtContent = parser.parse(content, JSONObject.class); + JwtClaims claims = JwtClaims.parse(new String(content)); + // Change the issuer to INVALID_ISSUER for failure testing if requested if (invalidClaims.contains(InvalidClaims.ISSUER)) { - jwtContent.put(Claims.iss.name(), "INVALID_ISSUER"); + claims.setIssuer("INVALID_ISSUER"); } long currentTimeInSecs = currentTimeInSecs(); long exp = currentTimeInSecs + 300; @@ -148,11 +138,11 @@ public static String generateTokenString(PrivateKey pk, String kid, String jsonR iat = exp - 5; authTime = exp - 5; } - jwtContent.put(Claims.iat.name(), iat); - jwtContent.put(Claims.auth_time.name(), authTime); + claims.setIssuedAt(NumericDate.fromSeconds(iat)); + claims.setClaim(Claims.auth_time.name(), NumericDate.fromSeconds(authTime)); // If the exp claim is not updated, it will be an old value that should be seen as expired if (!invalidClaims.contains(InvalidClaims.EXP)) { - jwtContent.put(Claims.exp.name(), exp); + claims.setExpirationTime(NumericDate.fromSeconds(exp)); } // Return the token time values if requested if (timeClaims != null) { @@ -167,23 +157,19 @@ public static String generateTokenString(PrivateKey pk, String kid, String jsonR pk = keyPair.getPrivate(); } - // Create RSA-signer with the private key - JWSSigner signer = new RSASSASigner(pk); - JWTClaimsSet claimsSet = JWTClaimsSet.parse(jwtContent); - JWSAlgorithm alg = JWSAlgorithm.RS256; + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(claims.toJson()); + jws.setKeyIdHeaderValue(kid); + jws.setHeader("typ", "JWT"); if (invalidClaims.contains(InvalidClaims.ALG)) { - alg = JWSAlgorithm.HS256; - SecureRandom random = new SecureRandom(); - BigInteger secret = BigInteger.probablePrime(256, random); - signer = new MACSigner(secret.toByteArray()); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + KeyGenerator kgen = KeyGenerator.getInstance("HMACSHA256"); + jws.setKey(kgen.generateKey()); + } else { + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); + jws.setKey(pk); } - JWSHeader jwtHeader = new JWSHeader.Builder(alg) - .keyID(kid) - .type(JOSEObjectType.JWT) - .build(); - SignedJWT signedJWT = new SignedJWT(jwtHeader, claimsSet); - signedJWT.sign(signer); - return signedJWT.serialize(); + return jws.getCompactSerialization(); } /** From 6d165cf2fad2600ebae2be080388498ba962bf2d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 27 Nov 2019 11:13:41 +0100 Subject: [PATCH 099/602] Hibernate Search Elasticsearch hosts list should be non-optional Fixes #5698 --- .../runtime/HibernateSearchElasticsearchRuntimeConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java index 6628067575d9c..04aee3fccd968 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java @@ -63,7 +63,7 @@ public static class ElasticsearchBackendRuntimeConfig { * The list of hosts of the Elasticsearch servers. */ @ConfigItem(defaultValue = "http://localhost:9200") - Optional> hosts; + List hosts; /** * The username used for authentication. From 4d93b718a210ff5fc791a9bc393fdb98bee03117 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 27 Nov 2019 11:17:37 +0100 Subject: [PATCH 100/602] Define an explicit default value for MongoDB hosts Fixes #5699 --- .../io/quarkus/mongodb/runtime/MongoClientConfig.java | 8 ++++---- .../io/quarkus/mongodb/runtime/MongoClientRecorder.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java index 87fedc898e970..1ef09c8208c77 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientConfig.java @@ -63,11 +63,11 @@ public class MongoClientConfig { public Optional connectionString; /** - * Configures the Mongo server addressed (one if single mode). - * The addressed are passed as {@code host:port}. + * Configures the MongoDB server addressed (one if single mode). + * The addresses are passed as {@code host:port}. */ - @ConfigItem - public Optional> hosts; + @ConfigItem(defaultValue = "127.0.0.1:27017") + public List hosts; /** * Configure the database name. diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java index 2fb6b72445303..823595b12f18e 100644 --- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClientRecorder.java @@ -139,7 +139,7 @@ void initialize(MongoClientConfig config, List codecProviders) { settings.applyToClusterSettings(builder -> { if (!maybeConnectionString.isPresent()) { // Parse hosts - List hosts = parseHosts(config.hosts.orElse(Collections.emptyList())); + List hosts = parseHosts(config.hosts); builder.hosts(hosts); if (hosts.size() == 1 && !config.replicaSetName.isPresent()) { From 3eb4ec79dd70305da7ae271628c59b7754635e21 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 13:39:21 +0200 Subject: [PATCH 101/602] Fix header conversion when converting from Spring ResponseEntity to JAX-RS Response --- .../io/quarkus/spring/web/runtime/ResponseEntityConverter.java | 2 +- .../src/main/java/io/quarkus/it/spring/web/CustomAdvice.java | 2 +- .../java/io/quarkus/it/spring/web/SpringControllerTest.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java index 7cb84ad174f32..d052bcfaa5789 100644 --- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java +++ b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java @@ -22,7 +22,7 @@ public static Response toResponse(ResponseEntity responseEntity) { private static Headers toHeaders(HttpHeaders springHeaders) { Headers jaxRsHeaders = new Headers<>(); for (Map.Entry> entry : springHeaders.entrySet()) { - jaxRsHeaders.addAll(entry.getKey(), entry.getValue()); + jaxRsHeaders.addAll(entry.getKey(), entry.getValue().toArray(new Object[0])); } return jaxRsHeaders; } diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java index 93f502e88f0b1..342e44abc6e14 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java @@ -26,7 +26,7 @@ public void unannotatedException() { @ExceptionHandler(IllegalStateException.class) public ResponseEntity handleIllegalStateException(IllegalStateException e, HttpServletRequest request, HttpServletResponse response) { - return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED) + return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).header("custom-header", "custom-value") .body(new Error(request.getRequestURI() + ":" + e.getMessage())); } diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java index 7e284182480b6..d541424e461fe 100644 --- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java +++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java @@ -133,7 +133,8 @@ public void testExceptionHandlerResponseEntityType() { RestAssured.when().get("/exception/responseEntity").then() .contentType("application/json") .body(containsString("bad state"), containsString("responseEntity")) - .statusCode(402); + .statusCode(402) + .header("custom-header", "custom-value"); } @Test From bd1a6cd25ae791c524465cd799a926bc695e4142 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 13:50:48 +0200 Subject: [PATCH 102/602] Ensure that generated exceptions mappers don't collide This could happen if multiple exceptions with the same simple name but different package names were used --- .../web/deployment/AbstractExceptionMapperGenerator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java index 7499d97780251..50d0e7a50ed1b 100644 --- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java +++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java @@ -11,6 +11,7 @@ import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; +import io.quarkus.deployment.util.HashUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.MethodCreator; @@ -33,9 +34,10 @@ abstract class AbstractExceptionMapperGenerator { abstract void generateMethodBody(MethodCreator toResponse); String generate() { - String generatedClassName = "io.quarkus.spring.web.mappers." + exceptionDotName.withoutPackagePrefix() + "Mapper"; + String generatedClassName = "io.quarkus.spring.web.mappers." + exceptionDotName.withoutPackagePrefix() + "_Mapper_" + + HashUtil.sha1(exceptionDotName.toString()); String generatedSubtypeClassName = "io.quarkus.spring.web.mappers.Subtype" + exceptionDotName.withoutPackagePrefix() - + "Mapper"; + + "Mapper_" + HashUtil.sha1(exceptionDotName.toString()); String exceptionClassName = exceptionDotName.toString(); try (ClassCreator cc = ClassCreator.builder() From e910e0ec427bbcbe406c763cf7f5bf353e601fb1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 14:18:41 +0200 Subject: [PATCH 103/602] Ensure that response from @RestControllerAdvice defaults to JSON content type unless void or String Fixes: #5796 --- ...dviceAbstractExceptionMapperGenerator.java | 28 +++++++++++++++++-- .../web/runtime/ResponseEntityConverter.java | 18 ++++++++++-- .../web/ExceptionThrowingController.java | 10 +++++++ .../it/spring/web/SpringControllerTest.java | 17 +++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java index 2ef58e2276e2c..5b35f0035830b 100644 --- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java +++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java @@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.jboss.jandex.AnnotationInstance; @@ -31,6 +32,7 @@ class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractExceptionMapperGenerator { private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity"); + private static final DotName STRING = DotName.createSimple(String.class.getName()); private final MethodInfo controllerAdviceMethod; private final TypesUtil typesUtil; @@ -135,17 +137,39 @@ void generateMethodBody(MethodCreator toResponse) { ResultHandle response; if (RESPONSE_ENTITY.equals(returnType.name())) { + /* + * By default we will send JSON back unless the ResponseEntity has a String body + */ + boolean addDefaultJsonContentType = true; + if (returnType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + if (returnType.asParameterizedType().arguments().size() == 1) { + Type responseEntityParameterType = returnType.asParameterizedType().arguments().get(0); + if (STRING.equals(responseEntityParameterType.name())) { + addDefaultJsonContentType = false; + } + } + } + // convert Spring's ResponseEntity to JAX-RS Response response = toResponse.invokeStaticMethod( MethodDescriptor.ofMethod(ResponseEntityConverter.class.getName(), "toResponse", - Response.class.getName(), RESPONSE_ENTITY.toString()), - exceptionHandlerMethodResponse); + Response.class.getName(), RESPONSE_ENTITY.toString(), boolean.class.getName()), + exceptionHandlerMethodResponse, toResponse.load(addDefaultJsonContentType)); } else { ResultHandle status = toResponse.load(getStatus(controllerAdviceMethod.annotation(RESPONSE_STATUS))); ResultHandle responseBuilder = toResponse.invokeStaticMethod( MethodDescriptor.ofMethod(Response.class, "status", Response.ResponseBuilder.class, int.class), status); + /* + * By default we will send JSON back unless the ResponseEntity has a String body + */ + if (!STRING.equals(returnType.name())) { + toResponse.invokeVirtualMethod( + MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "type", Response.ResponseBuilder.class, + String.class), + responseBuilder, toResponse.load(MediaType.APPLICATION_JSON)); + } toResponse.invokeVirtualMethod( MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "entity", Response.ResponseBuilder.class, diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java index d052bcfaa5789..8735c6282f2f3 100644 --- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java +++ b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.jboss.resteasy.core.Headers; @@ -11,19 +12,30 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; +/** + * This is only used in the generated ExceptionMappers when the Spring @RestControllerAdvice method returns a ResponseEntity + */ public class ResponseEntityConverter { - public static Response toResponse(ResponseEntity responseEntity) { + public static Response toResponse(ResponseEntity responseEntity, boolean addJsonContentTypeIfNotSet) { return new BuiltResponse( - responseEntity.getStatusCodeValue(), toHeaders(responseEntity.getHeaders()), responseEntity.getBody(), + responseEntity.getStatusCodeValue(), toHeaders(responseEntity.getHeaders(), addJsonContentTypeIfNotSet), + responseEntity.getBody(), new Annotation[0]); } - private static Headers toHeaders(HttpHeaders springHeaders) { + private static Headers toHeaders(HttpHeaders springHeaders, boolean addJsonContentTypeIfNotSet) { Headers jaxRsHeaders = new Headers<>(); for (Map.Entry> entry : springHeaders.entrySet()) { jaxRsHeaders.addAll(entry.getKey(), entry.getValue().toArray(new Object[0])); } + /* + * We add the default application/json content type if no content type is specified + * since this is the default value when returning an object from a Spring RestController + */ + if (addJsonContentTypeIfNotSet && !jaxRsHeaders.containsKey(javax.ws.rs.core.HttpHeaders.CONTENT_TYPE)) { + jaxRsHeaders.add(javax.ws.rs.core.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } return jaxRsHeaders; } diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java index d6865bb9e1445..f7d08ad68096b 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java @@ -35,11 +35,21 @@ public Greeting handledByResponseEntity() { throw new IllegalStateException("bad state"); } + @GetMapping("/responseEntityFromVoidReturningMethod") + public void handledByResponseEntityFromVoidReturningMethod() { + throw new IllegalStateException("bad state"); + } + @GetMapping("/pojo") public Greeting greetingWithIllegalArgumentException() { throw new IllegalArgumentException("hello from error"); } + @GetMapping("/pojoWithVoidReturnType") + public void greetingWithIllegalArgumentExceptionAndVoidReturnType() { + throw new IllegalArgumentException("hello from error"); + } + @GetMapping("/re") public ResponseEntity responseEntityWithIllegalArgumentException() { throw new IllegalStateException("hello from error"); diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java index d541424e461fe..81f7d829a918b 100644 --- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java +++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java @@ -137,6 +137,15 @@ public void testExceptionHandlerResponseEntityType() { .header("custom-header", "custom-value"); } + @Test + public void testExceptionHandlerResponseEntityTypeFromVoidReturningMethod() { + RestAssured.when().get("/exception/responseEntityFromVoidReturningMethod").then() + .contentType("application/json") + .body(containsString("bad state"), containsString("responseEntity")) + .statusCode(402) + .header("custom-header", "custom-value"); + } + @Test public void testExceptionHandlerPojoEntityType() { RestAssured.when().get("/exception/pojo").then() @@ -145,6 +154,14 @@ public void testExceptionHandlerPojoEntityType() { .statusCode(417); } + @Test + public void testControllerMethodWithVoidReturnTypeAndExceptionHandlerPojoEntityType() { + RestAssured.when().get("/exception/pojoWithVoidReturnType").then() + .contentType("application/json") + .body(containsString("hello from error")) + .statusCode(417); + } + @Test public void testRestControllerWithoutRequestMapping() { RestAssured.when().get("/hello").then() From 3dff28877ac17fb8bb1598286dcc73e30b620d54 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 15:25:54 +0200 Subject: [PATCH 104/602] Fix broken dev-mode It broke in 6d0025c192c9fd48609ec364e8e9bd10e27bdf20 --- .../io/quarkus/spring/web/deployment/SpringWebProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java index 7457e9d8ef817..61b68f5e6167e 100644 --- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java +++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java @@ -338,7 +338,7 @@ public void generateExceptionMapperProviders(BeanArchiveIndexBuildItem beanArchi // Look for all exception classes that are annotated with @ResponseStatus IndexView index = beanArchiveIndexBuildItem.getIndex(); - ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedExceptionMappers, false); + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedExceptionMappers, true); generateMappersForResponseStatusOnException(providersProducer, index, classOutput, typesUtil); generateMappersForExceptionHandlerInControllerAdvice(providersProducer, reflectiveClassProducer, index, classOutput, typesUtil); From 1ce018be6f16f881934f9e75ba861252e7adcc3b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 15:27:12 +0200 Subject: [PATCH 105/602] Replace redundant Class.forName with loadClass --- .../ControllerAdviceAbstractExceptionMapperGenerator.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java index 5b35f0035830b..d24544fdc0ac6 100644 --- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java +++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java @@ -214,9 +214,7 @@ private ResultHandle exceptionHandlerMethodResponse(MethodCreator toResponse) { } private ResultHandle controllerAdviceInstance(MethodCreator toResponse) { - ResultHandle controllerAdviceClass = toResponse.invokeStaticMethod( - MethodDescriptor.ofMethod(Class.class, "forName", Class.class, String.class), - toResponse.load(declaringClassName)); + ResultHandle controllerAdviceClass = toResponse.loadClass(declaringClassName); ResultHandle container = toResponse .invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class)); From 0b1fb5771b971b21c3e599f85fb364b42b8302a3 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 27 Nov 2019 10:04:30 -0300 Subject: [PATCH 106/602] Launching command line should be copy/paste friendly It also performs a minor refactor on how ProcessBuilder is used --- .../src/main/java/io/quarkus/maven/DevMojo.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index afafff887fe9d..0fe778d55b60d 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -1,5 +1,7 @@ package io.quarkus.maven; +import static java.util.stream.Collectors.joining; + import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; @@ -617,14 +619,11 @@ public Set getPomFiles() { public void run() throws Exception { // Display the launch command line in dev mode - getLog().info("Launching JVM with command line: " + args.toString()); - ProcessBuilder pb = new ProcessBuilder(args.toArray(new String[0])); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); - pb.redirectInput(ProcessBuilder.Redirect.INHERIT); - pb.directory(workingDir); - process = pb.start(); - + getLog().info("Launching JVM with command line: " + args.stream().collect(joining(" "))); + process = new ProcessBuilder(args) + .inheritIO() + .directory(workingDir) + .start(); //https://github.com/quarkusio/quarkus/issues/232 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override From 554b16c9dff61f1a483526a4fb47a91ad74c0e71 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 27 Nov 2019 18:54:44 +0200 Subject: [PATCH 107/602] Ensure that only the method parameter types (without annotations) are stored in SecurityCheckStorage Fixes: #5763 --- .../security/deployment/SecurityProcessor.java | 2 +- .../java/io/quarkus/it/rest/RBACSecuredResource.java | 12 ++++++++++++ .../test/java/io/quarkus/it/main/RBACAccessTest.java | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index 0b04ffdcffcad..cb16d7a104001 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -199,7 +199,7 @@ private ResultHandle paramTypes(MethodCreator ctor, List parameters) { ResultHandle result = ctor.newArray(String.class, ctor.load(parameters.size())); for (int i = 0; i < parameters.size(); i++) { - ctor.writeArrayValue(result, i, ctor.load(parameters.get(i).toString())); + ctor.writeArrayValue(result, i, ctor.load(parameters.get(i).name().toString())); } return result; diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/RBACSecuredResource.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/RBACSecuredResource.java index 7234d40d2f6f1..9352665271f15 100644 --- a/integration-tests/main/src/main/java/io/quarkus/it/rest/RBACSecuredResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/RBACSecuredResource.java @@ -4,8 +4,12 @@ import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; +import javax.validation.Valid; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; import io.quarkus.security.Authenticated; @@ -25,6 +29,14 @@ public String forTesterOnly() { return "forTesterOnly"; } + @GET + @RolesAllowed("tester") + @Path("forTesterOnlyWithMethodParamAnnotations") + public String forTesterOnlyWithMethodParamAnnotations(@Context SecurityContext ctx, @Context UriInfo uriInfo, + @Valid String message) { + return "forTesterOnlyWithMethodParamAnnotations"; + } + @GET @DenyAll @Path("denied") diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/RBACAccessTest.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RBACAccessTest.java index df8426145dba5..96b0ae4b58dd9 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/RBACAccessTest.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RBACAccessTest.java @@ -25,6 +25,15 @@ public void shouldRestrictAccessToSpecificRole() { Optional.of("forTesterOnly")); } + @Test + public void shouldRestrictAccessToSpecificRoleAndMethodParameterAnnotationsShouldntAffectAnything() { + String path = "/rbac-secured/forTesterOnlyWithMethodParamAnnotations"; + assertForAnonymous(path, 401, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("stuart", "test"), path, 403, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("scott", "jb0ss"), path, 200, + Optional.of("forTesterOnlyWithMethodParamAnnotations")); + } + @Test public void shouldFailToAccessForbidden() { assertForAnonymous("/rbac-secured/denied", 401, Optional.empty()); From 4ae7bee00f2a8a0e3c3cc4f4664cc4d8060fabef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 27 Nov 2019 14:55:54 +0100 Subject: [PATCH 108/602] Fixes the extension author guide WRT the new quarkus-extension.yaml --- .../src/main/asciidoc/writing-extensions.adoc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) mode change 100644 => 100755 docs/src/main/asciidoc/writing-extensions.adoc diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc old mode 100644 new mode 100755 index 21696b7061218..5b7a68781a322 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -378,7 +378,24 @@ The above sequence of commands does the following: A Maven build performed immediately after generating the modules should fail due to a `fail()` assertion in one of the test classes. There is one step (specific to the Quarkus source tree) that you should do manually when creating a new extension: -Add the extension coordinates to `devtools/common/src/main/filtered/extensions.json`. +create a `quarkus-extension.yaml` file that describe your extension inside the runtime module `src/main/resources/META-INF` folder. + +This is the `quarkus-extension.yaml` of the `quarkus-agroal` extension, you can use it as an example: + +[source,yaml] +---- +name: "Agroal - Database connection pool" +metadata: + keywords: + - "agroal" + - "database-connection-pool" + - "datasource" + - "jdbc" + guide: "https://quarkus.io/guides/datasource" + categories: + - "data" + status: "stable" +---- Note that the parameters of the mojo that will be constant for all the extensions added to this source tree are configured in `extensions/pom.xml` so that they do not need to be passed on the command line each time a new extension is added: From 1dc63d2f55a152b6b05babd21f3884e592d7396e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 15 Nov 2019 18:22:06 +0100 Subject: [PATCH 109/602] Avoid caching the io.quarkus artifacts in the Azure cache --- ci-templates/prepare-cache.yaml | 4 ++++ ci-templates/stages.yml | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 ci-templates/prepare-cache.yaml diff --git a/ci-templates/prepare-cache.yaml b/ci-templates/prepare-cache.yaml new file mode 100644 index 0000000000000..38cdfc7e04248 --- /dev/null +++ b/ci-templates/prepare-cache.yaml @@ -0,0 +1,4 @@ +steps: +- script: find $(MAVEN_CACHE_FOLDER)io/quarkus/ -mindepth 1 -maxdepth 1 \( ! -name http -a ! -name gizmo -a ! -name security \) -exec rm -rf {} \; + displayName: 'Remove Quarkus artifacts before caching' + diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 31fe1ac3153c3..1b47954195d51 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -46,6 +46,8 @@ stages: mavenOptions: $(MAVEN_OPTS) options: '-B --settings azure-mvn-settings.xml -DskipTests=true -Dno-format -DskipDocs' + - template: prepare-cache.yaml + - job: Cache_Windows_Maven_Repo #windows has different line endings so the cache key is different displayName: 'Windows Maven Repo' condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) @@ -92,6 +94,7 @@ stages: - template: jvm-build-steps.yaml - publish: $(MAVEN_CACHE_FOLDER) artifact: $(Build.SourceVersion)-BuiltMavenRepo + - template: prepare-cache.yaml - stage: run_jvm_tests_stage${{parameters.expectUseVMs}} displayName: '${{parameters.displayPrefix}} Run JVM Tests' @@ -133,6 +136,7 @@ stages: - template: jvm-build-steps.yaml parameters: jdk: '1.11' + - template: prepare-cache.yaml - job: Build_JDK12_Linux timeoutInMinutes: 60 @@ -147,6 +151,7 @@ stages: - template: jvm-build-steps.yaml parameters: jdk: '1.12' + - template: prepare-cache.yaml - job: Run_TCKs condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) @@ -175,8 +180,7 @@ stages: mavenOptions: $(MAVEN_OPTS) options: '-B --settings azure-mvn-settings.xml' mavenPomFile: 'tcks/pom.xml' - - + - template: prepare-cache.yaml - stage: run_native_tests_stage${{parameters.expectUseVMs}} displayName: '${{parameters.displayPrefix}} Native Tests' From b148d645546761b9bfd66b0c9e6dc9c0896a62f1 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 15 Nov 2019 18:39:17 +0100 Subject: [PATCH 110/602] Force a cache refresh with a version I haven't found a proper way to reset the caches from the UI. --- ci-templates/stages.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 1b47954195d51..637b2ce1db450 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -32,7 +32,8 @@ stages: - task: CacheBeta@0 inputs: - key: maven | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect + # the number below is a cache version when we want to force a cache refresh + key: maven | "1" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect path: $(MAVEN_CACHE_FOLDER) securityNamespace: cache cacheHitVar: CACHE_RESTORED @@ -61,7 +62,8 @@ stages: steps: - task: CacheBeta@0 inputs: - key: mavenWindows | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect + # the number below is a cache version when we want to force a cache refresh + key: mavenWindows | "1" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect path: $(MAVEN_CACHE_FOLDER) securityNamespace: cache cacheHitVar: CACHE_RESTORED From 7b77ad07a2220e4629fd0fc6f9e828207a2b0caa Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 25 Nov 2019 13:30:55 +0100 Subject: [PATCH 111/602] Bump cache version for Azure pipeline --- ci-templates/stages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 637b2ce1db450..e2f47e4bb51f5 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -33,7 +33,7 @@ stages: - task: CacheBeta@0 inputs: # the number below is a cache version when we want to force a cache refresh - key: maven | "1" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect + key: maven | "2" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect path: $(MAVEN_CACHE_FOLDER) securityNamespace: cache cacheHitVar: CACHE_RESTORED @@ -63,7 +63,7 @@ stages: - task: CacheBeta@0 inputs: # the number below is a cache version when we want to force a cache refresh - key: mavenWindows | "1" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect + key: mavenWindows | "2" | bom/runtime/pom.xml #if we attempt to use all poms then when they get copied to target everything breaks. This should be good enough, it does not need to be perfect path: $(MAVEN_CACHE_FOLDER) securityNamespace: cache cacheHitVar: CACHE_RESTORED From 7d733cc59644220ea1960eb94457971e4a3fb4ee Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 26 Nov 2019 11:48:07 +0100 Subject: [PATCH 112/602] Add a comment explaining what we do in the prepare cache step --- ci-templates/prepare-cache.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci-templates/prepare-cache.yaml b/ci-templates/prepare-cache.yaml index 38cdfc7e04248..ff3b6ac8b6572 100644 --- a/ci-templates/prepare-cache.yaml +++ b/ci-templates/prepare-cache.yaml @@ -1,4 +1,6 @@ steps: +# We remove all the Quarkus artifacts before pushing the $(MAVEN_CACHE_FOLDER) to the cache. +# We don't remove Quarkus HTTP, Gizmo and Quarkus Security as they are external to this build. - script: find $(MAVEN_CACHE_FOLDER)io/quarkus/ -mindepth 1 -maxdepth 1 \( ! -name http -a ! -name gizmo -a ! -name security \) -exec rm -rf {} \; displayName: 'Remove Quarkus artifacts before caching' From 4bb59b74fb269d225fc9e4355b2966573ec80635 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 26 Nov 2019 11:48:48 +0100 Subject: [PATCH 113/602] Display the cached directory names before removing them --- ci-templates/prepare-cache.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/prepare-cache.yaml b/ci-templates/prepare-cache.yaml index ff3b6ac8b6572..90557f8c0fc08 100644 --- a/ci-templates/prepare-cache.yaml +++ b/ci-templates/prepare-cache.yaml @@ -1,6 +1,6 @@ steps: # We remove all the Quarkus artifacts before pushing the $(MAVEN_CACHE_FOLDER) to the cache. # We don't remove Quarkus HTTP, Gizmo and Quarkus Security as they are external to this build. -- script: find $(MAVEN_CACHE_FOLDER)io/quarkus/ -mindepth 1 -maxdepth 1 \( ! -name http -a ! -name gizmo -a ! -name security \) -exec rm -rf {} \; +- script: find $(MAVEN_CACHE_FOLDER)io/quarkus/ -mindepth 1 -maxdepth 1 \( ! -name http -a ! -name gizmo -a ! -name security \) -exec echo {} \; -exec rm -rf {} \; displayName: 'Remove Quarkus artifacts before caching' From c71adf00c826957ef216e964eac2f60226795cb6 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 28 Nov 2019 11:02:06 -0300 Subject: [PATCH 114/602] Add JDBC drivers to dependabot --- .dependabot/config.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index e9720acc479ad..eaae965749217 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -13,3 +13,14 @@ update_configs: dependency_name: "org.eclipse.jgit:org.eclipse.jgit" - match: dependency_name: "io.fabric8:kubernetes-client-bom" + # JDBC Drivers + - match: + dependency_name: "org.postgresql:postgresql" + - match: + dependency_name: "org.mariadb.jdbc:mariadb-java-client" + - match: + dependency_name: "mysql:mysql-connector-java" + - match: + dependency_name: "org.apache.derby:derbyclient" + - match: + dependency_name: "com.microsoft.sqlserver:mssql-jdbc" From 681c70f7b7d98952f93490a155a16c949a1f8b92 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 28 Nov 2019 16:08:49 +0100 Subject: [PATCH 115/602] Add freemarker and artemis to dependabot --- .dependabot/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index eaae965749217..8ef1b58d40567 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -7,8 +7,14 @@ update_configs: allowed_updates: - match: update_type: "security" + - match: + dependency_name: "org.apache.activemq:artemis-core-client" + - match: + dependency_name: "org.apache.activemq:artemis-jms-client" - match: dependency_name: "org.flywaydb:flyway-core" + - match: + dependency_name: "org.freemarker:freemarker" - match: dependency_name: "org.eclipse.jgit:org.eclipse.jgit" - match: From 530f843878801bc02fb4280dcc5919b2595de65f Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 28 Nov 2019 12:44:10 -0300 Subject: [PATCH 116/602] Add org.apache.activemq:artemis-server to Dependabot --- .dependabot/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 8ef1b58d40567..c8fb357d0ec57 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -11,6 +11,8 @@ update_configs: dependency_name: "org.apache.activemq:artemis-core-client" - match: dependency_name: "org.apache.activemq:artemis-jms-client" + - match: + dependency_name: "org.apache.activemq:artemis-server" - match: dependency_name: "org.flywaydb:flyway-core" - match: From d56bbca5f0bde76169e4c5c297d867bbf9a7128f Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 28 Nov 2019 13:17:15 +0100 Subject: [PATCH 117/602] Micro versions bumps --- bom/runtime/pom.xml | 6 +++--- build-parent/pom.xml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 886df06ef2fc5..ae2bd9269a1b2 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -90,7 +90,7 @@ 7.6.0.Final 7.4.0 1.3.8 - 2.2.13 + 2.2.15 1.0.6.Final 2.1.0.Final 1.7.29 @@ -111,7 +111,7 @@ 1.4.197 42.2.8 2.4.4 - 8.0.17 + 8.0.18 7.2.1.jre8 10.14.2.0 1.2.6 @@ -153,7 +153,7 @@ 4.0.0-beta03 3.10.2 1.11.0 - 2.10.0 + 2.10.1 3.12.6 3.1.0 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 3d7b975b63571..fafd4adb624c9 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -36,10 +36,10 @@ 4.1.1 0.0.9 3.8.3 - 2.10.0 + 2.10.1 - 2.3.28 + 2.3.29 1.5.0.Final From a41d88ddaa8b5a13ee8354a6bd744db7e8d39817 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 28 Nov 2019 14:10:17 +0100 Subject: [PATCH 118/602] Set current/explicit Keycloak version in docs examples --- docs/pom.xml | 1 + docs/src/main/asciidoc/security-keycloak-authorization.adoc | 4 ++-- .../asciidoc/security-openid-connect-web-authentication.adoc | 4 ++-- docs/src/main/asciidoc/security-openid-connect.adoc | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index cc265d4af1ef0..7411780b0b739 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -97,6 +97,7 @@ ${version.surefire.plugin} ${graal-sdk.version-for-documentation} ${rest-assured.version} + ${keycloak.docker.image} ${quarkus-home-url} diff --git a/docs/src/main/asciidoc/security-keycloak-authorization.adoc b/docs/src/main/asciidoc/security-keycloak-authorization.adoc index 4961b34659e8a..3fc9aa0f78b52 100644 --- a/docs/src/main/asciidoc/security-keycloak-authorization.adoc +++ b/docs/src/main/asciidoc/security-keycloak-authorization.adoc @@ -173,9 +173,9 @@ NOTE: By default, applications using the `quarkus-oidc` extension are marked as To start a Keycloak Server you can use Docker and just run the following command: -[source,bash] +[source,bash,subs=attributes+] ---- -docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak +docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 {keycloak-docker-image} ---- You should be able to access your Keycloak Server at http://localhost:8180/auth[localhost:8180/auth]. diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index 94aa09c318592..2edd3432a6d41 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -83,9 +83,9 @@ all paths are being protected by a policy that ensures that only `authenticated` To start a Keycloak Server you can use Docker and just run the following command: -[source,bash] +[source,bash,subs=attributes+] ---- -docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:7.0.0 +docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 {keycloak-docker-image} ---- You should be able to access your Keycloak Server at http://localhost:8180/auth[localhost:8180/auth]. diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 0012cccdaf90e..3876d6d12e905 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -167,9 +167,9 @@ If you plan to consume this application from another application running on a di To start a Keycloak Server you can use Docker and just run the following command: -[source,bash] +[source,bash,subs=attributes+] ---- -docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:7.0.0 +docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 {keycloak-docker-image} ---- You should be able to access your Keycloak Server at http://localhost:8180/auth[localhost:8180/auth]. From dc6fe3f495d6a2c915bef6cf8c4a8ddef2d1c940 Mon Sep 17 00:00:00 2001 From: Filippe Spolti Date: Thu, 21 Nov 2019 20:57:57 -0300 Subject: [PATCH 119/602] [KOGITO-625] - Kogito quickstart Person example fails with list of objects Signed-off-by: Filippe Spolti --- .../io/quarkus/kogito/deployment/JandexProtoGenerator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java index db3b5db43680e..d5f95d91e849a 100644 --- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java +++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java @@ -74,6 +74,7 @@ protected ProtoMessage messageFromClass(Proto proto, ClassInfo clazz, IndexView String fieldTypeString = pd.type().name().toString(); DotName fieldType = pd.type().name(); + String protoType; if (pd.type().kind() == Kind.PARAMETERIZED_TYPE) { fieldTypeString = "Collection"; @@ -83,8 +84,10 @@ protected ProtoMessage messageFromClass(Proto proto, ClassInfo clazz, IndexView + " uses collection without type information"); } fieldType = typeParameters.get(0).name(); + protoType = protoType(fieldType.toString()); + } else { + protoType = protoType(fieldTypeString); } - String protoType = protoType(fieldTypeString); if (protoType == null) { ProtoMessage another = messageFromClass(proto, index.getClassByName(fieldType), index, packageName, From 471cf43b4389ee5d0513790b2fd55bc5f7f0883c Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 18 Nov 2019 09:48:10 -0300 Subject: [PATCH 120/602] Add Cache and NoCache tests Closes #5551 --- .../test/CacheControlFeatureTest.java | 30 +++++++++++++++++++ .../quarkus/resteasy/test/CacheResource.java | 26 ++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheControlFeatureTest.java create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheResource.java diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheControlFeatureTest.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheControlFeatureTest.java new file mode 100644 index 0000000000000..b9ce2fec70075 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheControlFeatureTest.java @@ -0,0 +1,30 @@ +package io.quarkus.resteasy.test; + +import static io.restassured.RestAssured.when; + +import javax.ws.rs.core.HttpHeaders; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class CacheControlFeatureTest { + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(CacheResource.class)); + + @Test + public void testNoCacheAnnotation() { + when().get("/nocache").then().header(HttpHeaders.CACHE_CONTROL, "no-cache=\"foo\""); + } + + @Test + public void testCacheAnnotation() { + when().get("/cache").then().header(HttpHeaders.CACHE_CONTROL, "max-age=123"); + } + +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheResource.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheResource.java new file mode 100644 index 0000000000000..fef3e180dba98 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CacheResource.java @@ -0,0 +1,26 @@ +package io.quarkus.resteasy.test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.jboss.resteasy.annotations.cache.Cache; +import org.jboss.resteasy.annotations.cache.NoCache; + +@Path("/") +public class CacheResource { + + @GET + @Path("/nocache") + @NoCache(fields = { "foo" }) + public String noCache() { + return "No Cache"; + } + + @GET + @Path("/cache") + @Cache(maxAge = 123) + public String cache() { + return "Cache"; + } + +} From f4eb46db374faccac824afbc354f151d79f5ba50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 13 Nov 2019 15:23:03 +0100 Subject: [PATCH 121/602] Add missing backticks on @BsonId on the mongodb-panache guide --- docs/src/main/asciidoc/mongodb-panache.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 376525f0617af..8aca7ac46f222 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -448,7 +448,7 @@ In MongoDB with Panache the ID are defined by a field named `id` of the `org.bso but if you want ot customize them, once again we have you covered. You can specify your own ID strategy by extending `PanacheMongoEntityBase` instead of `PanacheMongoEntity`. Then -you just declare whatever ID you want as a public field by annotating it by @BsonId: +you just declare whatever ID you want as a public field by annotating it by `@BsonId`: [source,java] ---- From 36ba66daf099a28086ee7ee124f9655b9ffa2281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Thu, 28 Nov 2019 15:59:29 +0100 Subject: [PATCH 122/602] fix the double locked idiom It misses a volatile modifier to the synchronized field --- .../streams/deployment/KafkaStreamsHotReplacementSetup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsHotReplacementSetup.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsHotReplacementSetup.java index 96ea504dfc856..892f0c0ceb23c 100644 --- a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsHotReplacementSetup.java +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsHotReplacementSetup.java @@ -12,7 +12,7 @@ public class KafkaStreamsHotReplacementSetup implements HotReplacementSetup { private static final long TWO_SECONDS = 2000; private HotReplacementContext context; - private long nextUpdate; + private volatile long nextUpdate; private final Executor executor = Executors.newSingleThreadExecutor(); @Override From a1bf2c790dc23e1849b1ab5431ae467f8b676610 Mon Sep 17 00:00:00 2001 From: Logan HAUSPIE Date: Wed, 13 Nov 2019 11:52:28 +0100 Subject: [PATCH 123/602] Generate a README.md with instructions on how to build the project fixes #4608 --- .../resources/templates/README.gradle.ftl | 35 +++++++++++++++++++ .../main/resources/templates/README.maven.ftl | 30 ++++++++++++++++ .../rest/BasicRestProjectGenerator.java | 16 +++++++++ .../cli/commands/CreateProjectTest.java | 14 +++++++- .../rest/BasicRestProjectGeneratorTest.java | 6 ++-- .../resources/templates/README.gradle.ftl | 5 +++ .../test/resources/templates/README.maven.ftl | 5 +++ 7 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 devtools/platform-descriptor-json/src/main/resources/templates/README.gradle.ftl create mode 100644 devtools/platform-descriptor-json/src/main/resources/templates/README.maven.ftl create mode 100644 independent-projects/tools/common/src/test/resources/templates/README.gradle.ftl create mode 100644 independent-projects/tools/common/src/test/resources/templates/README.maven.ftl diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/README.gradle.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/README.gradle.ftl new file mode 100644 index 0000000000000..3a846deb25056 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/templates/README.gradle.ftl @@ -0,0 +1,35 @@ +# ${project_artifactId} project + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: +``` +./gradlew quarkusDev +``` + +## Packaging and running the application + +The application is packageable using `./gradlew quarkusBuild`. +It produces the executable `${project_artifactId}-${project_version}-runner.jar` file in `build` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/lib` directory. + +The application is now runnable using `java -jar build/${project_artifactId}-${project_version}-runner.jar`. + +If you want to build an _über-jar_, just add the `--uber-jar` option to the command line: +``` +./gradlew quarkusBuild --uber-jar +``` + +## Creating a native executable + +You can create a native executable using: `./gradlew buildNative`. + +Or you can use Docker to build the native executable using: `./gradlew buildNative --docker-build=true`. + +You can then execute your binary: `./build/${project_artifactId}-${project_version}-runner` + +If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling#building-a-native-executable . \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/README.maven.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/README.maven.ftl new file mode 100644 index 0000000000000..e751e91dd2b4b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/templates/README.maven.ftl @@ -0,0 +1,30 @@ +# ${project_artifactId} project + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: +``` +./mvnw quarkus:dev +``` + +## Packaging and running the application + +The application is packageable using `./mvnw package`. +It produces the executable `${project_artifactId}-${project_version}-runner.jar` file in `/target` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. + +The application is now runnable using `java -jar target/${project_artifactId}-${project_version}-runner.jar`. + +## Creating a native executable + +You can create a native executable using: `./mvnw package -Pnative`. + +Or you can use Docker to build the native executable using: `./mvnw package -Pnative -Dquarkus.native.container-build=true`. + +You can then execute your binary: `./target/${project_artifactId}-${project_version}-runner` + +If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image-guide . \ No newline at end of file diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java index c364b98982900..09433aea15981 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java @@ -38,6 +38,7 @@ public void generate(final ProjectWriter writer, QuarkusCommandInvocation invoca project.createDockerFiles(); project.createDockerIgnore(); project.createApplicationConfig(); + project.createReadme(); project.createGitIgnore(); } @@ -165,6 +166,21 @@ private void createDockerFiles() throws IOException { generate("templates/dockerfile-jvm.ftl", invocation, dockerRootDir + "/Dockerfile.jvm", "jvm docker file"); } + private void createReadme() throws IOException { + String readme = writer.mkdirs("") + "README.md"; + BuildTool buildTool = getBuildTool(); + switch (buildTool) { + case MAVEN: + generate("templates/README.maven.ftl", context, readme, "read me"); + break; + case GRADLE: + generate("templates/README.gradle.ftl", context, readme, "read me"); + break; + default: + throw new IllegalStateException("buildTool is none of Maven or Gradle"); + } + } + private void createDockerIgnore() throws IOException { String docker = writer.mkdirs("") + ".dockerignore"; generate("templates/dockerignore.ftl", invocation, docker, "docker ignore"); diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java index e09c94274020f..8fa89db5affc8 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java @@ -74,6 +74,9 @@ public void createGradle() throws IOException { Assertions.assertFalse(gitignoreContent.contains("\ntarget/\n")); Assertions.assertTrue(gitignoreContent.contains("\nbuild/")); Assertions.assertTrue(gitignoreContent.contains("\n.gradle/\n")); + + assertThat(new File(file, "README.md")).exists(); + assertThat(contentOf(new File(file, "README.md"), "UTF-8")).contains("./gradlew"); } @Test @@ -115,7 +118,10 @@ public void createGradleOnExisting() throws IOException { Assertions.assertEquals(getBomGroupId(), props.get("quarkusPlatformGroupId")); Assertions.assertEquals(getBomArtifactId(), props.get("quarkusPlatformArtifactId")); Assertions.assertEquals(getBomVersion(), props.get("quarkusPlatformVersion")); - } + + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./gradlew"); + } @Test public void createOnTopPomWithoutResource() throws IOException { @@ -142,6 +148,8 @@ public void createOnTopPomWithoutResource() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).isFile(); assertThat(new File(testDir, "src/main/java")).isDirectory().matches(f -> { @@ -184,6 +192,8 @@ public void createOnTopPomWithResource() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).exists(); assertThat(new File(testDir, "src/main/java")).isDirectory(); @@ -227,6 +237,8 @@ public void createOnTopPomWithSpringController() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).exists(); assertThat(new File(testDir, "src/main/java")).isDirectory(); diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java index 846cebeb545e3..d0d21a4428d00 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/generators/rest/BasicRestProjectGeneratorTest.java @@ -85,8 +85,8 @@ void generateFilesWithJaxRsResource() throws Exception { basicRestProjectGenerator.generate(mockWriter, BASIC_PROJECT_CONTEXT); - verify(mockWriter, times(9)).mkdirs(anyString()); - verify(mockWriter, times(2)).mkdirs(""); + verify(mockWriter, times(10)).mkdirs(anyString()); + verify(mockWriter, times(3)).mkdirs(""); verify(mockWriter, times(1)).mkdirs("src/main/java"); verify(mockWriter, times(1)).mkdirs("src/main/java/org/example"); verify(mockWriter, times(1)).mkdirs("src/test/java"); @@ -95,7 +95,7 @@ void generateFilesWithJaxRsResource() throws Exception { verify(mockWriter, times(1)).mkdirs("src/main/resources/META-INF/resources"); verify(mockWriter, times(1)).mkdirs("src/main/docker"); - verify(mockWriter, times(10)).write(anyString(), anyString()); + verify(mockWriter, times(11)).write(anyString(), anyString()); verify(mockWriter, times(1)).write(eq("pom.xml"), argThat(argument -> argument.contains("org.example") && argument.contains("quarkus-app Date: Sun, 24 Nov 2019 13:35:44 +0100 Subject: [PATCH 124/602] Implements proposal 5728, introduces quarkus.http.auth.form.redirect-after-login If there is no page to redirect back to, Quarkus redirects to ```quarkus.http.auth.form.landing-page``` after a successful login. This PR makes this behaviour optional, i.e. with ```quarkus.http.auth.form.redirect-after-login=false``` no such redirect takes place if there is no stored location to go back to, i.e. ```quarkus.http.auth.form.landing-page``` remains unused. See https://github.com/quarkusio/quarkus/issues/5728 for details on the proposal. --- .../security/FormAuthNoRedirectTestCase.java | 139 ++++++++++++++++++ .../vertx/http/runtime/FormAuthConfig.java | 7 + .../security/FormAuthenticationMechanism.java | 15 +- 3 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthNoRedirectTestCase.java diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthNoRedirectTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthNoRedirectTestCase.java new file mode 100644 index 0000000000000..d3c6407b7587a --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/FormAuthNoRedirectTestCase.java @@ -0,0 +1,139 @@ +package io.quarkus.vertx.http.security; + +import static org.hamcrest.Matchers.*; + +import java.util.function.Supplier; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.filter.cookie.CookieFilter; + +public class FormAuthNoRedirectTestCase { + + private static final String APP_PROPS = "" + + "quarkus.http.auth.form.enabled=true\n" + + "quarkus.http.auth.form.login-page=login\n" + + "quarkus.http.auth.form.error-page=error\n" + + "quarkus.http.auth.form.landing-page=landing\n" + + "quarkus.http.auth.form.redirect-after-login=false\n" + + "quarkus.http.auth.policy.r1.roles-allowed=a d m i n\n" + + "quarkus.http.auth.permission.roles1.paths=/admin\n" + + "quarkus.http.auth.permission.roles1.policy=r1\n"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestIdentityProvider.class, TestTrustedIdentityProvider.class, PathHandler.class) + .addAsResource(new StringAsset(APP_PROPS), "application.properties"); + } + }); + + @BeforeAll + public static void setup() { + TestIdentityController.resetRoles() + .add("a d m i n", "a d m i n", "a d m i n"); + } + + /** + * First, protected /admin resource is accessed. No quarkus-credential cookie + * is presented by the client, so server should redirect to /login page. + * + * Next, let's assume there was a login form on the /login page, + * we do POST with valid credentials. + * Server should provide a response with quarkus-credential cookie + * and a redirect to the previously attempted /admin page. + * Note the redirect takes place despite having quarkus.http.auth.form.redirect-after-login=false + * because there is some previous location to redirect to. + * + * Last but not least, client accesses the protected /admin resource again, + * this time providing server with stored quarkus-credential cookie. + * Access is granted and landing page displayed. + */ + @Test + public void testFormBasedAuthSuccess() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + CookieFilter cookies = new CookieFilter(); + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .get("/admin") + .then() + .assertThat() + .statusCode(302) + .header("location", containsString("/login")) + .cookie("quarkus-redirect-location", containsString("/admin")); + + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .formParam("j_username", "a d m i n") + .formParam("j_password", "a d m i n") + .post("/j_security_check") + .then() + .assertThat() + .statusCode(302) + .header("location", containsString("/admin")) + .cookie("quarkus-credential", notNullValue()); + + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .get("/admin") + .then() + .assertThat() + .statusCode(200) + .body(equalTo("a d m i n:/admin")); + } + + @Test + public void testFormBasedAuthSuccessLandingPage() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + CookieFilter cookies = new CookieFilter(); + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .formParam("j_username", "a d m i n") + .formParam("j_password", "a d m i n") + .post("/j_security_check") + .then() + .assertThat() + .statusCode(200) + .cookie("quarkus-credential", notNullValue()); + } + + @Test + public void testFormAuthFailure() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + CookieFilter cookies = new CookieFilter(); + RestAssured + .given() + .filter(cookies) + .redirects().follow(false) + .when() + .formParam("j_username", "a d m i n") + .formParam("j_password", "wrongpassword") + .post("/j_security_check") + .then() + .assertThat() + .statusCode(302) + .header("location", containsString("/error")) + .header("quarkus-credential", nullValue()); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java index 0eb5eb09798a5..7b1699e8e1124 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/FormAuthConfig.java @@ -34,6 +34,13 @@ public class FormAuthConfig { @ConfigItem(defaultValue = "/index.html") public String landingPage; + /** + * Option to disable redirect to landingPage if there is no saved page to redirect back to. Form Auth POST is followed + * by redirect to landingPage by default. + */ + @ConfigItem(defaultValue = "true") + public boolean redirectAfterLogin; + /** * The inactivity timeout */ diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java index 8bfc37b827712..dd0c5e8dfefa9 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/FormAuthenticationMechanism.java @@ -37,6 +37,7 @@ public class FormAuthenticationMechanism implements HttpAuthenticationMechanism private volatile String postLocation = DEFAULT_POST_LOCATION; private volatile String locationCookie = "quarkus-redirect-location"; private volatile String landingPage = "/index.html"; + private volatile boolean redirectAfterLogin; private volatile PersistentLoginManager loginManager; @@ -66,6 +67,7 @@ public void init(HttpConfiguration httpConfiguration, HttpBuildTimeConfig buildT loginPage = form.loginPage.startsWith("/") ? form.loginPage : "/" + form.loginPage; errorPage = form.errorPage.startsWith("/") ? form.errorPage : "/" + form.errorPage; landingPage = form.landingPage.startsWith("/") ? form.landingPage : "/" + form.landingPage; + redirectAfterLogin = form.redirectAfterLogin; } public CompletionStage runFormAuth(final RoutingContext exchange, @@ -97,10 +99,15 @@ public Object apply(SecurityIdentity identity, Throwable throwable) { result.completeExceptionally(throwable); } else { loginManager.save(identity, exchange, null); - handleRedirectBack(exchange); - //we have authenticated, but we want to just redirect back to the original page - //so we don't actually authenticate the current request - //instead we have just set a cookie so the redirected request will be authenticated + if (redirectAfterLogin || exchange.getCookie(locationCookie) != null) { + handleRedirectBack(exchange); + //we have authenticated, but we want to just redirect back to the original page + //so we don't actually authenticate the current request + //instead we have just set a cookie so the redirected request will be authenticated + } else { + exchange.response().setStatusCode(200); + exchange.response().end(); + } result.complete(null); } return null; From 65e99b55e8c71bd1e3fef3b8569f76a1f1711a61 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 28 Nov 2019 17:51:30 -0300 Subject: [PATCH 125/602] Fix compilation error This fixes the error introduced by https://github.com/quarkusio/quarkus/commit/8f439467bb504112a0b34f3f377d690d9ecb872c --- .../io/quarkus/generators/rest/BasicRestProjectGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java index 09433aea15981..eac0b929e1691 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/generators/rest/BasicRestProjectGenerator.java @@ -171,10 +171,10 @@ private void createReadme() throws IOException { BuildTool buildTool = getBuildTool(); switch (buildTool) { case MAVEN: - generate("templates/README.maven.ftl", context, readme, "read me"); + generate("templates/README.maven.ftl", invocation, readme,"read me"); break; case GRADLE: - generate("templates/README.gradle.ftl", context, readme, "read me"); + generate("templates/README.gradle.ftl", invocation, readme, "read me"); break; default: throw new IllegalStateException("buildTool is none of Maven or Gradle"); From aafbe053ca04b78039d262cd0a6199df82faa88b Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 28 Nov 2019 17:32:37 -0300 Subject: [PATCH 126/602] Fixes Idea modules issue while importing the project. In IDEA, when importing `devtools/gradle` as a Gradle project, in Project Structure, if you click to remove the `quarkus-gradle-plugin` project, and apply, the following message is displayed: ``` Content root "/quarkus/bom/runtime" is defined for modules "quarkus-bom" and "quarkus-platform-descriptor-json". Two modules in a project cannot share the same content root ``` This is because the `` entries in `platform-descriptor-json` are set to other projects, which may produce unpredictable results in IDEs. --- devtools/platform-descriptor-json/pom.xml | 59 +++++++++++++++++------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/devtools/platform-descriptor-json/pom.xml b/devtools/platform-descriptor-json/pom.xml index 66d65a028a558..56f22a32510a0 100644 --- a/devtools/platform-descriptor-json/pom.xml +++ b/devtools/platform-descriptor-json/pom.xml @@ -22,23 +22,50 @@ src/main/filtered true - - ${project.basedir}/../../bom/runtime - quarkus-bom - true - - pom.xml - - - - ${project.basedir}/../bom-descriptor-json/target - quarkus-bom-descriptor - false - - extensions.json - - + + + maven-dependency-plugin + + + copy-bom + + copy + + + + + io.quarkus + quarkus-bom + ${project.version} + pom + ${project.build.outputDirectory}/quarkus-bom + pom.xml + + + + + + copy-bom-descriptor-json + + copy + + + + + io.quarkus + quarkus-bom-descriptor-json + ${project.version} + json + ${project.build.outputDirectory}/quarkus-bom-descriptor + extensions.json + + + + + + + From 67b3f78dd0e2298c78ae30a7db579cdd92da1824 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 28 Nov 2019 22:51:37 -0300 Subject: [PATCH 127/602] Arc should use Jandex 2.1.2.Final --- independent-projects/arc/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 4636cb682a0c1..e0f2a4db6b629 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -36,7 +36,7 @@ 1.8 2.0.2 - 2.1.1.Final + 2.1.2.Final 5.5.2 3.5.4 3.12.2 From e7784e7afca61762c1fb85256941ad074e9a07c1 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sun, 24 Nov 2019 11:55:50 +0530 Subject: [PATCH 128/602] issue-5647 Filter WARN message from Netty when it can't parse the /etc/hosts file --- .../quarkus/vertx/core/deployment/VertxCoreProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java index 6de2d0c881909..65a5e2ef78c76 100644 --- a/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java +++ b/extensions/vertx-core/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java @@ -13,6 +13,7 @@ import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.netty.deployment.EventLoopSupplierBuildItem; import io.quarkus.runtime.RuntimeValue; import io.quarkus.vertx.core.runtime.VertxCoreProducer; @@ -79,4 +80,9 @@ InternalWebVertxBuildItem buildWeb(VertxCoreRecorder recorder, VertxConfiguratio RuntimeValue vertx = recorder.initializeWeb(config, context, launchModeBuildItem.getLaunchMode()); return new InternalWebVertxBuildItem(vertx); } + + @BuildStep + LogCleanupFilterBuildItem filterNettyHostsFileParsingWarn() { + return new LogCleanupFilterBuildItem("io.netty.resolver.HostsFileParser", "Failed to load and parse hosts file"); + } } From 1758547a521219417bb82c82e8875119c903a239 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 29 Nov 2019 08:38:44 +1100 Subject: [PATCH 129/602] Adjust swagger-ui path if HTTP root is set Fixes #5818 --- .../io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java index 405ad1a3f4699..a066920608573 100644 --- a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java +++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java @@ -118,7 +118,8 @@ public void registerSwaggerUiServletExtension(SwaggerUiRecorder recorder, throw new RuntimeException(e); } } - Handler handler = recorder.handler(cached.cachedDirectory, swaggerUiConfig.path); + Handler handler = recorder.handler(cached.cachedDirectory, + httpRootPathBuildItem.adjustPath(swaggerUiConfig.path)); routes.produce(new RouteBuildItem(swaggerUiConfig.path, handler)); routes.produce(new RouteBuildItem(swaggerUiConfig.path + "/*", handler)); displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(swaggerUiConfig.path + "/")); From bbcea672e6ef2c4954a2e58760d9c66e05f2bb35 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 21 Nov 2019 15:29:05 +0100 Subject: [PATCH 130/602] ArC - introduce BeanStream - convenient Stream wrapper that can be used to filter beans - allow for inspecting removed beans in BeanDeploymentValidator - also make it possible to mark a synthetic bean as unremovable - resolves #5653 --- docs/src/main/asciidoc/cdi-reference.adoc | 20 +- .../deployment/SchedulerProcessor.java | 10 +- .../SmallRyeFaultToleranceProcessor.java | 7 +- .../deployment/SmallRyeMetricsProcessor.java | 64 ++-- .../SmallRyeReactiveMessagingProcessor.java | 38 ++- .../web/deployment/VertxWebProcessor.java | 2 +- .../vertx/deployment/VertxProcessor.java | 29 +- .../arc/processor/BeanConfigurator.java | 14 +- .../quarkus/arc/processor/BeanDeployment.java | 22 ++ .../processor/BeanDeploymentValidator.java | 14 +- .../io/quarkus/arc/processor/BeanInfo.java | 35 ++- .../quarkus/arc/processor/BeanRegistrar.java | 12 +- .../io/quarkus/arc/processor/BeanStream.java | 283 ++++++++++++++++++ .../quarkus/arc/processor/BuildExtension.java | 11 +- .../extension/beans/BeanRegistrarTest.java | 41 ++- .../BeanDeploymentValidatorTest.java | 47 ++- 16 files changed, 518 insertions(+), 131 deletions(-) create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index fb870debc97a4..393ddcd55340f 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -379,8 +379,8 @@ NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is re === Synthetic Beans Sometimes it is very useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class. -In CDI this could be achieved using `AfterBeanDiscovery.addBean()` methods. -In Quarkus we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. +In CDI, this could be achieved using `AfterBeanDiscovery.addBean()` methods. +In Quarkus, we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. [source,java] ---- @@ -398,6 +398,8 @@ BeanRegistrarBuildItem syntheticBean() { NOTE: The output of a `BeanConfigurator` is recorded as bytecode. Therefore there are some limitations in how a synthetic bean instance is created. See also `BeanConfigurator.creator()` methods. +TIP: You can easily filter all class-based beans via the convenient `BeanStream` returned from the `RegistrationContext.beans()` method. + If an extension needs to produce other build items during the "bean registration" phase it should use the `BeanRegistrationPhaseBuildItem` instead. The reason is that injected objects are only valid during a `@BuildStep` method invocation. @@ -511,7 +513,7 @@ BeanDeploymentValidatorBuildItem beanDeploymentValidator() { } ---- -NOTE: See also `io.quarkus.arc.processor.BuildExtension.Key` to discover the available metadata. +TIP: You can easily filter all registered beans via the convenient `BeanStream` returned from the `ValidationContext.beans()` method. If an extension needs to produce other build items during the "validation" phase it should use the `ValidationPhaseBuildItem` instead. The reason is that injected objects are only valid during a `@BuildStep` method invocation. @@ -574,13 +576,15 @@ The built-in keys located in `io.quarkus.arc.processor.BuildExtension.Key` are: * `ANNOTATION_STORE` ** Contains an `AnnotationStore` that keeps information about all `AnnotationTarget` annotations after application of annotation transformers * `INJECTION_POINTS` -** `List` containing all injection points +** `Collection` containing all injection points * `BEANS` -** `List` containing all beans +** `Collection` containing all beans +* `REMOVED_BEANS` +** `Collection` containing all the removed beans; see <> for more information * `OBSERVERS` -** `List` containing all observers +** `Collection` containing all observers * `SCOPES` -** `List` containing all scopes, including custom ones +** `Collection` containing all scopes, including custom ones * `QUALIFIERS` ** `Map` containing all qualifiers * `INTERCEPTOR_BINDINGS` @@ -600,7 +604,7 @@ Here is a summary of which extensions can access which metadata: * `InjectionPointsTransformer` ** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES` * `BeanRegistrar` -** Has access to all build metadata +** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES`, `BEANS` * `BeanDeploymentValidator` ** Has access to all build metadata 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 c41bc5ef6ca7c..4e4b90791b130 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 @@ -127,12 +127,10 @@ void collectScheduledMethods( AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); // We need to collect all business methods annotated with @Scheduled first - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - collectScheduledMethods(config, beanArchives.getIndex(), annotationStore, bean, - bean.getTarget().get().asClass(), - scheduledBusinessMethods, validationPhase.getContext()); - } + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + collectScheduledMethods(config, beanArchives.getIndex(), annotationStore, bean, + bean.getTarget().get().asClass(), + scheduledBusinessMethods, validationPhase.getContext()); } } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index afb2570201829..d5216ecf64fed 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -34,7 +34,6 @@ import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; -import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.QuarkusConfig; @@ -192,10 +191,8 @@ public void transform(TransformationContext ctx) { void validateFaultToleranceAnnotations( ValidationPhaseBuildItem validationPhase, SmallryeFaultToleranceRecorder recorder) { List beanNames = new ArrayList<>(); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - beanNames.add(bean.getBeanClass().toString()); - } + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + beanNames.add(bean.getBeanClass().toString()); } recorder.validate(beanNames); } diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java index d38e136d64711..784fe29504477 100644 --- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java +++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java @@ -338,39 +338,37 @@ void registerMetricsFromProducers( ValidationPhaseBuildItem validationPhase, BeanArchiveIndexBuildItem beanArchiveIndex) { IndexView index = beanArchiveIndex.getIndex(); - for (io.quarkus.arc.processor.BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isProducerField() || bean.isProducerMethod()) { - MetricType metricType = getMetricType(bean.getImplClazz()); - if (metricType != null) { - AnnotationTarget target = bean.getTarget().get(); - AnnotationInstance metricAnnotation = null; - String memberName = null; - if (bean.isProducerField()) { - FieldInfo field = target.asField(); - metricAnnotation = field.annotation(METRIC); - memberName = field.name(); - } - if (bean.isProducerMethod()) { - MethodInfo method = target.asMethod(); - metricAnnotation = method.annotation(METRIC); - memberName = method.name(); - } - if (metricAnnotation != null) { - String nameValue = metricAnnotation.valueWithDefault(index, "name").asString(); - boolean absolute = metricAnnotation.valueWithDefault(index, "absolute").asBoolean(); - String metricSimpleName = !nameValue.isEmpty() ? nameValue : memberName; - String declaringClassName = bean.getDeclaringBean().getImplClazz().name().toString(); - String metricsFinalName = absolute ? metricSimpleName - : MetricRegistry.name(declaringClassName, metricSimpleName); - recorder.registerMetricFromProducer( - bean.getIdentifier(), - metricType, - metricsFinalName, - metricAnnotation.valueWithDefault(index, "tags").asStringArray(), - metricAnnotation.valueWithDefault(index, "description").asString(), - metricAnnotation.valueWithDefault(index, "displayName").asString(), - metricAnnotation.valueWithDefault(index, "unit").asString()); - } + for (io.quarkus.arc.processor.BeanInfo bean : validationPhase.getContext().beans().producers()) { + MetricType metricType = getMetricType(bean.getImplClazz()); + if (metricType != null) { + AnnotationTarget target = bean.getTarget().get(); + AnnotationInstance metricAnnotation = null; + String memberName = null; + if (bean.isProducerField()) { + FieldInfo field = target.asField(); + metricAnnotation = field.annotation(METRIC); + memberName = field.name(); + } + if (bean.isProducerMethod()) { + MethodInfo method = target.asMethod(); + metricAnnotation = method.annotation(METRIC); + memberName = method.name(); + } + if (metricAnnotation != null) { + String nameValue = metricAnnotation.valueWithDefault(index, "name").asString(); + boolean absolute = metricAnnotation.valueWithDefault(index, "absolute").asBoolean(); + String metricSimpleName = !nameValue.isEmpty() ? nameValue : memberName; + String declaringClassName = bean.getDeclaringBean().getImplClazz().name().toString(); + String metricsFinalName = absolute ? metricSimpleName + : MetricRegistry.name(declaringClassName, metricSimpleName); + recorder.registerMetricFromProducer( + bean.getIdentifier(), + metricType, + metricsFinalName, + metricAnnotation.valueWithDefault(index, "tags").asStringArray(), + metricAnnotation.valueWithDefault(index, "description").asString(), + metricAnnotation.valueWithDefault(index, "displayName").asString(), + metricAnnotation.valueWithDefault(index, "unit").asString()); } } } diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java index 7eebdbc360786..7daedd19fee18 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/SmallRyeReactiveMessagingProcessor.java @@ -118,27 +118,25 @@ void validateBeanDeployment( AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); // We need to collect all business methods annotated with @Incoming/@Outgoing first - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - // TODO: add support for inherited business methods - for (MethodInfo method : bean.getTarget().get().asClass().methods()) { - AnnotationInstance incoming = annotationStore.getAnnotation(method, - io.quarkus.smallrye.reactivemessaging.deployment.DotNames.INCOMING); - AnnotationInstance outgoing = annotationStore.getAnnotation(method, - io.quarkus.smallrye.reactivemessaging.deployment.DotNames.OUTGOING); - if (incoming != null || outgoing != null) { - if (incoming != null && incoming.value().asString().isEmpty()) { - validationPhase.getContext().addDeploymentProblem( - new DeploymentException("Empty @Incoming annotation on method " + method)); - } - if (outgoing != null && outgoing.value().asString().isEmpty()) { - validationPhase.getContext().addDeploymentProblem( - new DeploymentException("Empty @Outgoing annotation on method " + method)); - } - // TODO: validate method params and return type? - mediatorMethods.produce(new MediatorBuildItem(bean, method)); - LOGGER.debugf("Found mediator business method %s declared on %s", method, bean); + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + // TODO: add support for inherited business methods + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + AnnotationInstance incoming = annotationStore.getAnnotation(method, + io.quarkus.smallrye.reactivemessaging.deployment.DotNames.INCOMING); + AnnotationInstance outgoing = annotationStore.getAnnotation(method, + io.quarkus.smallrye.reactivemessaging.deployment.DotNames.OUTGOING); + if (incoming != null || outgoing != null) { + if (incoming != null && incoming.value().asString().isEmpty()) { + validationPhase.getContext().addDeploymentProblem( + new DeploymentException("Empty @Incoming annotation on method " + method)); } + if (outgoing != null && outgoing.value().asString().isEmpty()) { + validationPhase.getContext().addDeploymentProblem( + new DeploymentException("Empty @Outgoing annotation on method " + method)); + } + // TODO: validate method params and return type? + mediatorMethods.produce(new MediatorBuildItem(bean, method)); + LOGGER.debugf("Found mediator business method %s declared on %s", method, bean); } } } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index d102597adb9f2..8b98af3df949c 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -104,7 +104,7 @@ void validateBeanDeployment( // Collect all business methods annotated with @Route and @RouteFilter AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { if (bean.isClassBean()) { // NOTE: inherited business methods are not taken into account ClassInfo beanClass = bean.getTarget().get().asClass(); diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index be4f0059373a8..b76021f524029 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -108,23 +108,20 @@ void validateBeanDeployment( // We need to collect all business methods annotated with @MessageConsumer first AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); - for (BeanInfo bean : validationPhase.getContext().get(BuildExtension.Key.BEANS)) { - if (bean.isClassBean()) { - // TODO: inherited business methods? - for (MethodInfo method : bean.getTarget().get().asClass().methods()) { - AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); - if (consumeEvent != null) { - // Validate method params and return type - List params = method.parameters(); - if (params.size() != 1) { - throw new IllegalStateException(String.format( - "Event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s", - params, method, bean)); - } - messageConsumerBusinessMethods - .produce(new EventConsumerBusinessMethodItem(bean, method, consumeEvent)); - LOGGER.debugf("Found event consumer business method %s declared on %s", method, bean); + for (BeanInfo bean : validationPhase.getContext().beans().classBeans()) { + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); + if (consumeEvent != null) { + // Validate method params and return type + List params = method.parameters(); + if (params.size() != 1) { + throw new IllegalStateException(String.format( + "Event consumer business method must accept exactly one parameter: %s [method: %s, bean:%s", + params, method, bean)); } + messageConsumerBusinessMethods + .produce(new EventConsumerBusinessMethodItem(bean, method, consumeEvent)); + LOGGER.debugf("Found event consumer business method %s declared on %s", method, bean); } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java index c78bbe1f34b6c..0ae85aede3a13 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java @@ -59,7 +59,9 @@ public final class BeanConfigurator { private final Map params; - private boolean isDefaultBean; + private boolean defaultBean; + + private boolean removable; /** * @@ -77,6 +79,7 @@ public final class BeanConfigurator { this.scope = BuiltinScope.DEPENDENT.getInfo(); this.params = new HashMap<>(); this.name = null; + this.removable = true; } public BeanConfigurator param(String name, Class value) { @@ -152,7 +155,12 @@ public BeanConfigurator name(String name) { } public BeanConfigurator defaultBean() { - this.isDefaultBean = true; + this.defaultBean = true; + return this; + } + + public BeanConfigurator unremovable() { + this.removable = false; return this; } @@ -216,7 +224,7 @@ public void done() { .beanDeployment(beanDeployment).scope(scope).types(types) .qualifiers(qualifiers) .alternativePriority(alternativePriority).name(name).creator(creatorConsumer).destroyer(destroyerConsumer) - .params(params).defaultBean(isDefaultBean).build()); + .params(params).defaultBean(defaultBean).removable(removable).build()); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index af993182d193a..ac4d0e5475d70 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -241,6 +241,10 @@ void init(Consumer bytecodeTransformerConsumer) { if (bean.getName() != null) { continue test; } + // Unremovable synthetic beans + if (!bean.isRemovable()) { + continue test; + } // Custom exclusions for (Predicate exclusion : unusedExclusions) { if (exclusion.test(bean)) { @@ -294,6 +298,9 @@ void init(Consumer bytecodeTransformerConsumer) { } LOGGER.debugf("Removed %s unused beans in %s ms", removable.size(), System.currentTimeMillis() - removalStart); } + + buildContext.putInternal(BuildExtension.Key.REMOVED_BEANS.asString(), Collections.unmodifiableSet(removedBeans)); + LOGGER.debugf("Bean deployment initialized in %s ms", System.currentTimeMillis() - start); } @@ -836,6 +843,11 @@ public V put(Key key, V value) { return buildContext.put(key, value); } + @Override + public BeanStream beans() { + return new BeanStream(get(BuildExtension.Key.BEANS)); + } + }; for (BeanRegistrar registrar : beanRegistrars) { registrar.register(registrationContext); @@ -956,6 +968,16 @@ public List getDeploymentProblems() { return Collections.unmodifiableList(errors); } + @Override + public BeanStream beans() { + return new BeanStream(get(BuildExtension.Key.BEANS)); + } + + @Override + public BeanStream removedBeans() { + return new BeanStream(get(BuildExtension.Key.REMOVED_BEANS)); + } + } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java index 8b413eb53a77b..4bc3b34aaf69a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeploymentValidator.java @@ -19,7 +19,7 @@ public interface BeanDeploymentValidator extends BuildExtension { * @see Key#OBSERVERS * @see DeploymentException */ - default void validate(ValidationContext validationContext) { + default void validate(ValidationContext context) { } /** @@ -38,6 +38,18 @@ interface ValidationContext extends BuildContext { List getDeploymentProblems(); + /** + * + * @return a new stream of beans that form the deployment + */ + BeanStream beans(); + + /** + * + * @return a new stream of beans that are considered {@code unused} and were removed from the deployment + */ + BeanStream removedBeans(); + } public enum ValidationRule { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 8806d4c65fe6c..90f7263aa70f9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -69,9 +69,11 @@ public class BeanInfo implements InjectionTargetInfo { private final String name; - private final boolean isDefaultBean; + private final boolean defaultBean; - // Gizmo consumers are only used by synthetic beans + // Following fields are only used by synthetic beans + + private final boolean removable; private final Consumer creatorConsumer; @@ -87,7 +89,7 @@ public class BeanInfo implements InjectionTargetInfo { this(null, null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, null, null, - Collections.emptyMap()); + Collections.emptyMap(), true); } BeanInfo(ClassInfo implClazz, Type providerType, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, @@ -97,7 +99,7 @@ public class BeanInfo implements InjectionTargetInfo { List stereotypes, String name, boolean isDefaultBean, Consumer creatorConsumer, Consumer destroyerConsumer, - Map params) { + Map params, boolean isRemovable) { this.target = Optional.ofNullable(target); if (implClazz == null && target != null) { implClazz = initImplClazz(target, beanDeployment); @@ -126,9 +128,10 @@ public class BeanInfo implements InjectionTargetInfo { this.alternativePriority = alternativePriority; this.stereotypes = stereotypes; this.name = name; - this.isDefaultBean = isDefaultBean; + this.defaultBean = isDefaultBean; this.creatorConsumer = creatorConsumer; this.destroyerConsumer = destroyerConsumer; + this.removable = isRemovable; this.params = params; // Identifier must be unique for a specific deployment this.identifier = Hashes.sha1(toString()); @@ -178,6 +181,10 @@ public boolean isSynthetic() { return !target.isPresent(); } + public boolean isRemovable() { + return removable; + } + public DotName getBeanClass() { if (declaringBean != null) { return declaringBean.implClazz.name(); @@ -259,6 +266,10 @@ public boolean hasLifecycleInterceptors() { return !lifecycleInterceptors.isEmpty(); } + public boolean hasAroundInvokeInterceptors() { + return !interceptedMethods.isEmpty(); + } + boolean isSubclassRequired() { return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); } @@ -323,7 +334,7 @@ public String getName() { } public boolean isDefaultBean() { - return isDefaultBean; + return defaultBean; } Consumer getCreatorConsumer() { @@ -653,6 +664,8 @@ static class Builder { private Map params; + private boolean removable = true; + Builder() { injections = Collections.emptyList(); stereotypes = Collections.emptyList(); @@ -743,11 +756,15 @@ Builder params(Map params) { return this; } + Builder removable(boolean val) { + this.removable = val; + return this; + } + BeanInfo build() { return new BeanInfo(implClazz, providerType, target, beanDeployment, scope, types, qualifiers, injections, - declaringBean, - disposer, alternativePriority, - stereotypes, name, isDefaultBean, creatorConsumer, destroyerConsumer, params); + declaringBean, disposer, alternativePriority, stereotypes, name, isDefaultBean, creatorConsumer, + destroyerConsumer, params, removable); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java index 51fb9733806a3..209da36ac063a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanRegistrar.java @@ -12,9 +12,9 @@ public interface BeanRegistrar extends BuildExtension { /** * - * @param registrationContext + * @param context */ - void register(RegistrationContext registrationContext); + void register(RegistrationContext context); interface RegistrationContext extends BuildContext { @@ -34,6 +34,14 @@ default BeanConfigurator configure(Class beanClass) { return configure(DotName.createSimple(beanClass.getName())); } + /** + * The returned stream contains all non-synthetic beans (beans derived from classes) and beans + * registered by other {@link BeanRegistrar}s before the stream is created. + * + * @return a new stream of beans + */ + BeanStream beans(); + } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java new file mode 100644 index 0000000000000..ce82fd43d6048 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanStream.java @@ -0,0 +1,283 @@ +package io.quarkus.arc.processor; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +/** + * Convenient {@link Stream} wrapper that can be used to filter a set of beans. + *

+ * This object is stateful and cannot be reused. After a terminal opration is performed, the underlying stream is considered + * consumed, and can no longer be used. + *

+ * This construct is not threadsafe. + */ +public final class BeanStream implements Iterable { + + private Stream stream; + + public BeanStream(Collection beans) { + this.stream = Objects.requireNonNull(beans, "Beans collection is null").stream(); + } + + /** + * + * @param scopeName + * @return the new stream of beans + * @see BeanInfo#getScope() + */ + public BeanStream withScope(Class scope) { + return withScope(DotName.createSimple(scope.getName())); + } + + /** + * + * @param scopeName + * @return the new stream of beans + * @see BeanInfo#getScope() + */ + public BeanStream withScope(DotName scopeName) { + stream = stream.filter(bean -> bean.getScope().getDotName().equals(scopeName)); + return this; + } + + /** + * + * @param beanType + * @return the new stream of beans + * @see BeanInfo#getTypes() + */ + public BeanStream withBeanType(Class beanType) { + return withBeanType(DotName.createSimple(beanType.getName())); + } + + /** + * + * @param beanType + * @return the new stream of beans + * @see BeanInfo#getTypes() + */ + public BeanStream withBeanType(DotName beanTypeName) { + stream = stream.filter(bean -> bean.getTypes().stream().anyMatch(t -> t.name().equals(beanTypeName))); + return this; + } + + /** + * + * @param beanType + * @return the new stream of beans + * @see BeanInfo#getTypes() + */ + public BeanStream withBeanType(Type beanType) { + stream = stream.filter(bean -> bean.getTypes().stream().anyMatch(t -> t.equals(beanType))); + return this; + } + + /** + * + * @param beanClass + * @return the new stream of beans + * @see BeanInfo#getBeanClass() + */ + public BeanStream withBeanClass(Class beanClass) { + return withBeanClass(DotName.createSimple(beanClass.getName())); + } + + /** + * + * @param beanClass + * @return the new stream of beans + * @see BeanInfo#getBeanClass() + */ + public BeanStream withBeanClass(DotName beanClass) { + stream = stream.filter(bean -> bean.getBeanClass().equals(beanClass)); + return this; + } + + /** + * + * @param qualifier + * @return the new stream of beans + * @see BeanInfo#getQualifiers() + */ + @SafeVarargs + public final BeanStream withQualifier(Class... qualifiers) { + if (qualifiers.length == 1) { + return withQualifier(DotName.createSimple(qualifiers[0].getName())); + } else { + return withQualifier(Arrays.stream(qualifiers).map(q -> DotName.createSimple(q.getName())).toArray(DotName[]::new)); + } + } + + /** + * + * @param qualifierNames + * @return the new stream of beans + * @see BeanInfo#getQualifiers() + */ + public BeanStream withQualifier(DotName... qualifierNames) { + if (qualifierNames.length == 1) { + stream = stream.filter(bean -> bean.getQualifiers().stream().anyMatch(q -> q.name().equals(qualifierNames[0]))); + } else { + stream = stream.filter(bean -> bean.getQualifiers().stream().anyMatch(q -> { + for (DotName qualifierName : qualifierNames) { + if (q.name().equals(qualifierName)) { + return true; + } + } + return false; + })); + } + return this; + } + + /** + * + * @param name + * @return the new stream of beans + * @see BeanInfo#getName() + */ + public BeanStream withName(String name) { + stream = stream.filter(bean -> name.equals(bean.getName())); + return this; + } + + /** + * + * @param id + * @return an {@link Optional} with the matching bean, or an empty {@link Optional} if no such bean is found + * @see BeanInfo#getIdentifier() + */ + public Optional findByIdentifier(String id) { + return stream.filter(bean -> id.equals(bean.getIdentifier())).findFirst(); + } + + /** + * + * @return the new stream of producer beans + */ + public BeanStream producers() { + stream = stream.filter(bean -> bean.isProducerField() || bean.isProducerMethod()); + return this; + } + + /** + * + * @return the new stream of producer method beans + */ + public BeanStream producerMethods() { + stream = stream.filter(BeanInfo::isProducerMethod); + return this; + } + + /** + * + * @return the new stream of producer field beans + */ + public BeanStream producerFields() { + stream = stream.filter(BeanInfo::isProducerField); + return this; + } + + /** + * + * @return the new stream of class beans + */ + public BeanStream classBeans() { + stream = stream.filter(BeanInfo::isClassBean); + return this; + } + + /** + * + * @return the new stream of synthetic beans + */ + public BeanStream syntheticBeans() { + stream = stream.filter(BeanInfo::isSynthetic); + return this; + } + + /** + * + * @return the new stream of named beans + * @see BeanInfo#getName() + */ + public BeanStream namedBeans() { + stream = stream.filter(bean -> bean.getName() != null); + return this; + } + + /** + * + * @return the new stream of default beans + * @see BeanInfo#isDefaultBean() + */ + public BeanStream defaultBeans() { + stream = stream.filter(BeanInfo::isDefaultBean); + return this; + } + + /** + * + * @return the new stream of default beans + * @see BeanInfo#isAlternative() + */ + public BeanStream alternativeBeans() { + stream = stream.filter(BeanInfo::isAlternative); + return this; + } + + /** + * Terminal operation. + * + * @return the list of beans + */ + public List collect() { + return stream.collect(Collectors.toList()); + } + + /** + * + * @return the underlying stream instance + */ + public Stream stream() { + return stream; + } + + /** + * Terminal operation. + * + * @return true if the stream contains no elements + */ + public boolean isEmpty() { + return stream.count() == 0; + } + + /** + * Terminal operation. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return stream.iterator(); + } + + /** + * Terminal operation. + * + * @return an {@link Optional} with the first matching bean, or an empty {@link Optional} if no bean is matching + */ + public Optional firstResult() { + return stream.findFirst(); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java index 9eb2cf21a84f8..0c24459d0b379 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuildExtension.java @@ -1,6 +1,6 @@ package io.quarkus.arc.processor; -import java.util.List; +import java.util.Collection; import java.util.Map; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -51,11 +51,12 @@ interface Key { // Built-in keys static String BUILT_IN_PREFIX = BuildExtension.class.getPackage().getName() + "."; static Key INDEX = new SimpleKey<>(BUILT_IN_PREFIX + "index"); - static Key> INJECTION_POINTS = new SimpleKey<>(BUILT_IN_PREFIX + "injectionPoints"); - static Key> BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "beans"); - static Key> OBSERVERS = new SimpleKey<>(BUILT_IN_PREFIX + "observers"); + static Key> INJECTION_POINTS = new SimpleKey<>(BUILT_IN_PREFIX + "injectionPoints"); + static Key> BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "beans"); + static Key> REMOVED_BEANS = new SimpleKey<>(BUILT_IN_PREFIX + "removedBeans"); + static Key> OBSERVERS = new SimpleKey<>(BUILT_IN_PREFIX + "observers"); static Key ANNOTATION_STORE = new SimpleKey<>(BUILT_IN_PREFIX + "annotationStore"); - static Key> SCOPES = new SimpleKey<>(BUILT_IN_PREFIX + "scopes"); + static Key> SCOPES = new SimpleKey<>(BUILT_IN_PREFIX + "scopes"); static Key> QUALIFIERS = new SimpleKey<>(BUILT_IN_PREFIX + "qualifiers"); static Key> INTERCEPTOR_BINDINGS = new SimpleKey<>(BUILT_IN_PREFIX + "interceptorBindings"); static Key> STEREOTYPES = new SimpleKey<>(BUILT_IN_PREFIX + "stereotypes"); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java index 7e1f4bd4486eb..5fb3637576379 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/beans/BeanRegistrarTest.java @@ -1,25 +1,37 @@ package io.quarkus.arc.test.build.extension.beans; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import io.quarkus.arc.Arc; import io.quarkus.arc.BeanCreator; import io.quarkus.arc.processor.BeanConfigurator; +import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BeanRegistrar; import io.quarkus.arc.test.ArcTestContainer; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import java.util.Map; +import java.util.Optional; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; +import javax.inject.Qualifier; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public class BeanRegistrarTest { @RegisterExtension - public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class) + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class, MyQualifier.class) + .removeUnusedBeans(true) .beanRegistrars(new TestRegistrar()).build(); @Test @@ -38,19 +50,21 @@ public boolean initialize(BuildContext buildContext) { } @Override - public void register(RegistrationContext registrationContext) { - // Verify that the class bean was registered - assertTrue(registrationContext.get(Key.BEANS).stream() - .anyMatch(b -> b.isClassBean() && b.getBeanClass().toString().equals(UselessBean.class.getName()))); - - BeanConfigurator integerConfigurator = registrationContext.configure(Integer.class); - integerConfigurator.types(Integer.class).creator(mc -> { + public void register(RegistrationContext context) { + Optional uselessBean = context.beans().withBeanClass(UselessBean.class).firstResult(); + assertTrue(uselessBean.isPresent()); + assertTrue(context.beans().findByIdentifier(uselessBean.get().getIdentifier()).isPresent()); + assertEquals(uselessBean.get().getIdentifier(), + context.beans().withQualifier(MyQualifier.class).firstResult().get().getIdentifier()); + + BeanConfigurator integerConfigurator = context.configure(Integer.class); + integerConfigurator.unremovable().types(Integer.class).creator(mc -> { ResultHandle ret = mc.newInstance(MethodDescriptor.ofConstructor(Integer.class, int.class), mc.load(152)); mc.returnValue(ret); }); integerConfigurator.done(); - registrationContext.configure(String.class).types(String.class).param("name", "Frantisek") + context.configure(String.class).unremovable().types(String.class).param("name", "Frantisek") .creator(StringCreator.class).done(); } @@ -65,6 +79,15 @@ public String create(CreationalContext creationalContext, Map b.isClassBean() && b.getBeanClass() - .toString() - .equals(Alpha.class.getName()))); - assertTrue(validationContext.get(Key.BEANS) - .stream() - .anyMatch(b -> b.isSynthetic() && b.getTypes() - .contains(EmptyStringListCreator.listStringType()))); - assertTrue(validationContext.get(Key.OBSERVERS) + public void validate(ValidationContext context) { + assertFalse(context.removedBeans().withBeanClass(UselessBean.class).isEmpty()); + + assertFalse(context.beans().classBeans().withBeanClass(Alpha.class).isEmpty()); + assertFalse(context.beans().syntheticBeans().withBeanType(EmptyStringListCreator.listStringType()) + .isEmpty()); + List namedAlpha = context.beans().withName("alpha").collect(); + assertEquals(1, namedAlpha.size()); + assertEquals(Alpha.class.getName(), namedAlpha.get(0).getBeanClass().toString()); + + Collection observers = context.get(Key.OBSERVERS); + + List namedClassWithObservers = context.beans().classBeans().namedBeans().stream() + .filter(b -> observers.stream().anyMatch(o -> o.getDeclaringBean().equals(b))).collect(Collectors.toList()); + assertEquals(1, namedClassWithObservers.size()); + assertEquals(Alpha.class.getName(), namedClassWithObservers.get(0).getBeanClass().toString()); + + assertTrue(observers .stream() .anyMatch(o -> o.getObservedType() .equals(Type.create(DotName.createSimple(Object.class.getName()), Kind.CLASS)))); @@ -66,11 +81,12 @@ public void validate(ValidationContext validationContext) { } + @Named @ApplicationScoped static class Alpha { @Inject - private List strings; + List strings; void observeAppContextInit(@Observes @Initialized(ApplicationScoped.class) Object event) { } @@ -96,4 +112,9 @@ public List create(CreationalContext> creationalContext, Ma } + @ApplicationScoped + static class UselessBean { + + } + } From 7d1a0bae87dafecdfd9237c0a8ee414737b67e06 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 29 Nov 2019 10:25:17 +0100 Subject: [PATCH 131/602] Update Vert.x version to 3.8.4 Also remove the patched Vert.x web version, it's now included in the 3.8.4. --- bom/runtime/pom.xml | 8 +++----- build-parent/pom.xml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index ae2bd9269a1b2..32662d90bf29d 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -100,9 +100,7 @@ 2.0.0.Alpha4 1.8.7.Final 3.0.0.Final - 3.8.3 - - 3.8.3-01 + 3.8.4 4.5.10 4.4.12 4.1.4 @@ -1951,12 +1949,12 @@ io.vertx vertx-web - ${vertx-web.version} + ${vertx.version} io.vertx vertx-web-common - ${vertx-web.version} + ${vertx.version} io.smallrye.reactive diff --git a/build-parent/pom.xml b/build-parent/pom.xml index fafd4adb624c9..ea0cc715df9d4 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -35,7 +35,7 @@ 19.2.1 4.1.1 0.0.9 - 3.8.3 + 3.8.4 2.10.1 From 94d09ac9379fac84c8b172ca8963550c046c3485 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 29 Nov 2019 11:23:59 +0100 Subject: [PATCH 132/602] Disable DEBUG logging in elytron-security-properties-file tests - also remove some useless properties files --- .../resources/application-form-auth.properties | 18 +++++++++--------- .../src/test/resources/application.properties | 18 +++++++++--------- ...application-custom-auth-embedded.properties | 14 -------------- .../application-custom-auth.properties | 8 -------- .../src/test/resources/application.properties | 15 --------------- .../src/test/resources/logging.properties | 18 ------------------ .../src/test/resources/test-roles.properties | 4 ---- .../src/test/resources/test-users.properties | 5 ----- 8 files changed, 18 insertions(+), 82 deletions(-) delete mode 100644 extensions/elytron-security/deployment/src/test/resources/application-custom-auth-embedded.properties delete mode 100644 extensions/elytron-security/deployment/src/test/resources/application-custom-auth.properties delete mode 100644 extensions/elytron-security/deployment/src/test/resources/application.properties delete mode 100644 extensions/elytron-security/deployment/src/test/resources/logging.properties delete mode 100644 extensions/elytron-security/deployment/src/test/resources/test-roles.properties delete mode 100644 extensions/elytron-security/deployment/src/test/resources/test-users.properties diff --git a/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties b/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties index bb91fd666d238..5b1d3aa17e61e 100644 --- a/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties +++ b/extensions/elytron-security-properties-file/deployment/src/test/resources/application-form-auth.properties @@ -1,13 +1,13 @@ # Logging -quarkus.log.level=DEBUG -quarkus.log.console.enable=true -quarkus.log.console.level=DEBUG -quarkus.log.file.enable=true -quarkus.log.file.path=/tmp/trace.log -quarkus.log.file.level=TRACE -quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n -quarkus.log.category."io.quarkus.arc".level=TRACE -quarkus.log.category."io.undertow.request.security".level=TRACE +#quarkus.log.level=DEBUG +#quarkus.log.console.enable=true +#quarkus.log.console.level=DEBUG +#quarkus.log.file.enable=true +#quarkus.log.file.path=/tmp/trace.log +#quarkus.log.file.level=TRACE +#quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +#quarkus.log.category."io.quarkus.arc".level=TRACE +#quarkus.log.category."io.undertow.request.security".level=TRACE # Identities quarkus.security.users.file.enabled=true diff --git a/extensions/elytron-security-properties-file/deployment/src/test/resources/application.properties b/extensions/elytron-security-properties-file/deployment/src/test/resources/application.properties index cec6f162c6e3d..7548cdc43904e 100644 --- a/extensions/elytron-security-properties-file/deployment/src/test/resources/application.properties +++ b/extensions/elytron-security-properties-file/deployment/src/test/resources/application.properties @@ -3,14 +3,14 @@ quarkus.security.users.file.users=test-users.properties quarkus.security.users.file.roles=test-roles.properties quarkus.security.users.file.plain-text=true -quarkus.log.level=DEBUG -quarkus.log.console.enable=true -quarkus.log.console.level=DEBUG -quarkus.log.file.enable=true +#quarkus.log.level=DEBUG +#quarkus.log.console.enable=true +#quarkus.log.console.level=DEBUG +#quarkus.log.file.enable=true # Send output to a trace.log file under the /tmp directory -quarkus.log.file.path=/tmp/trace.log -quarkus.log.file.level=TRACE -quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +#quarkus.log.file.path=/tmp/trace.log +#quarkus.log.file.level=TRACE +#quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n # Set 2 categories (io.quarkus.smallrye.jwt, io.undertow.request.security) to TRACE level -quarkus.log.category."io.quarkus.arc".level=TRACE -quarkus.log.category."io.undertow.request.security".level=TRACE +#quarkus.log.category."io.quarkus.arc".level=TRACE +#quarkus.log.category."io.undertow.request.security".level=TRACE diff --git a/extensions/elytron-security/deployment/src/test/resources/application-custom-auth-embedded.properties b/extensions/elytron-security/deployment/src/test/resources/application-custom-auth-embedded.properties deleted file mode 100644 index 6373e5e075748..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/application-custom-auth-embedded.properties +++ /dev/null @@ -1,14 +0,0 @@ -quarkus.security.embedded.enabled=true -quarkus.security.embedded.users.scott=jb0ss -quarkus.security.embedded.users.stuart=test -quarkus.security.embedded.users.jdoe=p4ssw0rd -quarkus.security.embedded.users.noadmin=n0Adm1n -quarkus.security.embedded.roles.scott=Admin,admin,Tester,user -quarkus.security.embedded.roles.stuart=admin,user -quarkus.security.embedded.roles.jdoe=NoRolesUser -quarkus.security.embedded.roles.noadmin=user -quarkus.security.embedded.auth-mechanism=CUSTOM - -#quarkus.log.min-level=DEBUG -#quarkus.log.level=DEBUG -#quarkus.log.console.level=DEBUG \ No newline at end of file diff --git a/extensions/elytron-security/deployment/src/test/resources/application-custom-auth.properties b/extensions/elytron-security/deployment/src/test/resources/application-custom-auth.properties deleted file mode 100644 index 5a771caf555a7..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/application-custom-auth.properties +++ /dev/null @@ -1,8 +0,0 @@ -quarkus.security.file.enabled=true -quarkus.security.file.users=test-users.properties -quarkus.security.file.roles=test-roles.properties -quarkus.security.file.auth-mechanism=CUSTOM - -#quarkus.log.min-level=DEBUG -#quarkus.log.level=DEBUG -#quarkus.log.console.level=DEBUG \ No newline at end of file diff --git a/extensions/elytron-security/deployment/src/test/resources/application.properties b/extensions/elytron-security/deployment/src/test/resources/application.properties deleted file mode 100644 index 94c2eed37843d..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/application.properties +++ /dev/null @@ -1,15 +0,0 @@ -quarkus.security.file.enabled=true -quarkus.security.file.users=test-users.properties -quarkus.security.file.roles=test-roles.properties - -quarkus.log.level=DEBUG -quarkus.log.console.enable=true -quarkus.log.console.level=DEBUG -quarkus.log.file.enable=true -# Send output to a trace.log file under the /tmp directory -quarkus.log.file.path=/tmp/trace.log -quarkus.log.file.level=TRACE -quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n -# Set 2 categories (io.quarkus.smallrye.jwt, io.undertow.request.security) to TRACE level -quarkus.log.category."io.quarkus.arc".level=TRACE -quarkus.log.category."io.undertow.request.security".level=TRACE diff --git a/extensions/elytron-security/deployment/src/test/resources/logging.properties b/extensions/elytron-security/deployment/src/test/resources/logging.properties deleted file mode 100644 index cb93578ab5345..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/logging.properties +++ /dev/null @@ -1,18 +0,0 @@ -loggers=org.jboss.logmanager - -# Root logger -logger.level=INFO -logger.handlers=CONSOLE - -logger.org.jboss.logmanager.useParentHandlers=true -logger.org.jboss.logmanager.level=INFO - -handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler -handler.CONSOLE.formatter=PATTERN -handler.CONSOLE.properties=autoFlush,target -handler.CONSOLE.autoFlush=true -handler.CONSOLE.target=SYSTEM_OUT - -formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter -formatter.PATTERN.properties=pattern -formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n \ No newline at end of file diff --git a/extensions/elytron-security/deployment/src/test/resources/test-roles.properties b/extensions/elytron-security/deployment/src/test/resources/test-roles.properties deleted file mode 100644 index 02e10e7bfba8d..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/test-roles.properties +++ /dev/null @@ -1,4 +0,0 @@ -scott=Admin,admin,Tester,user -jdoe=NoRolesUser -stuart=admin,user -noadmin=user \ No newline at end of file diff --git a/extensions/elytron-security/deployment/src/test/resources/test-users.properties b/extensions/elytron-security/deployment/src/test/resources/test-users.properties deleted file mode 100644 index 3a420f96c8e23..0000000000000 --- a/extensions/elytron-security/deployment/src/test/resources/test-users.properties +++ /dev/null @@ -1,5 +0,0 @@ -scott=jb0ss -jdoe=p4ssw0rd -stuart=test -noadmin=n0Adm1n -alice=alice \ No newline at end of file From 0aaf926e7c16d89ff58207ceb069b2b37b349768 Mon Sep 17 00:00:00 2001 From: aurea munoz Date: Wed, 27 Nov 2019 09:58:14 +0100 Subject: [PATCH 133/602] doc: add spring security guide --- docs/src/main/asciidoc/security.adoc | 3 +- docs/src/main/asciidoc/spring-data-jpa.adoc | 1 + docs/src/main/asciidoc/spring-di.adoc | 1 + docs/src/main/asciidoc/spring-security.adoc | 262 ++++++++++++++++++++ docs/src/main/asciidoc/spring-web.adoc | 2 +- 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 docs/src/main/asciidoc/spring-security.adoc diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 6f8f6056ba505..30ff0ff77c99a 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -68,7 +68,8 @@ The following properties can be used to configure form based auth: include::{generated-dir}/config/quarkus-vertx-http-config-group-form-auth-config.adoc[opts=optional, leveloffset=+1] -## Authorization in REST endpoints and CDI beans using annotations +[#standard-security-annotations] +== Authorization in REST endpoints and CDI beans using annotations Quarkus comes with built-in security to allow for Role-Based Access Control (link:https://en.wikipedia.org/wiki/Role-based_access_control[RBAC]) based on the common security annotations `@RolesAllowed`, `@DenyAll`, `@PermitAll` on REST endpoints and CDI beans. diff --git a/docs/src/main/asciidoc/spring-data-jpa.adoc b/docs/src/main/asciidoc/spring-data-jpa.adoc index eed8f33c26d7f..098517915538a 100644 --- a/docs/src/main/asciidoc/spring-data-jpa.adoc +++ b/docs/src/main/asciidoc/spring-data-jpa.adoc @@ -603,3 +603,4 @@ Quarkus support has more Spring compatibility features. See the following guides * link:spring-di[Quarkus - Extension for Spring DI] * link:spring-web[Quarkus - Extension for Spring Web] +* link:spring-security[Quarkus - Extension for Spring Security] diff --git a/docs/src/main/asciidoc/spring-di.adoc b/docs/src/main/asciidoc/spring-di.adoc index ec8bba87fcbf8..fc7b469f7f7ba 100644 --- a/docs/src/main/asciidoc/spring-di.adoc +++ b/docs/src/main/asciidoc/spring-di.adoc @@ -313,3 +313,4 @@ Quarkus supports additional Spring compatibility features. See the following gui * link:spring-web[Quarkus - Extension for Spring Web] * link:spring-data-jpa[Quarkus - Extension for Spring Data JPA] +* link:spring-security[Quarkus - Extension for Spring Security] diff --git a/docs/src/main/asciidoc/spring-security.adoc b/docs/src/main/asciidoc/spring-security.adoc new file mode 100644 index 0000000000000..1670fe4887f86 --- /dev/null +++ b/docs/src/main/asciidoc/spring-security.adoc @@ -0,0 +1,262 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Quarkus Extension for Spring Security API + +include::./attributes.adoc[] + +While users are encouraged to use <>, Quarkus provides a compatibility layer for Spring Security in the form of the `spring-security` extension. + +This guide explains how a Quarkus application can leverage the well known Spring Security annotations to define authorizations on RESTful services using roles. + +== Prerequisites + +To complete this guide, you need: + +* less than 15 minutes +* an IDE +* JDK 1.8+ installed with `JAVA_HOME` configured appropriately +* Apache Maven 3.5.3+ +* Some familiarity with the Spring Web extension + + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `spring-security-quickstart` {quickstarts-tree-url}/spring-security-quickstart[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +[source,shell,subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=spring-security-quickstart \ + -DclassName="org.acme.spring.security.GreetingController" \ + -Dpath="/greeting" \ + -Dextensions="spring-web, spring-security, quarkus-elytron-security-properties-file" +cd spring-security-quickstart +---- + +This command generates a Maven project with a REST endpoint and imports the `spring-web`, `spring-security` and `security-properties-file` extensions. + +For more information about `security-properties-file` you can check the guide of link:security-properties[quarkus-elytron-security-properties-file] extension. + +== GreetingController + +The Quarkus Maven plugin automatically generated a controller with the Spring Web annotations to define our REST endpoint (instead of the JAX-RS ones used by default). +The `src/main/java/org/acme/spring/web/GreetingController.java` file looks as follows: + +[source,java] +---- +package org.acme.spring.security; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; + +@RestController +@RequestMapping("/greeting") +public class GreetingController { + + @GetMapping + public String hello() { + return "hello"; + } +} +---- + +== GreetingControllerTest + +Note that a test for the controller has been created as well: + +[source, java] +---- +package org.acme.spring.security; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class GreetingControllerTest { + + @Test + public void testHelloEndpoint() { + given() + .when().get("/greeting") + .then() + .statusCode(200) + .body(is("hello")); + } + +} +---- + +== Package and run the application + +Run the application with: `./mvn quarkus:dev`. +Open your browser to http://localhost:8080/greeting. + +The result should be: `{"message": "hello"}`. + +== Modify the controller to secure the `hello` method + +In order to restrict access to the `hello` method to users with certain roles, the `@Secured` annotation will be utilized. +The updated controller will be: + +[source,java] +---- +package org.acme.spring.security; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; + +@RestController +@RequestMapping("/greeting") +public class GreetingController { + + @Secured("admin") + @GetMapping + public String hello() { + return "hello"; + } +} +---- + +The easiest way to setup users and roles for our example is to use the `security-properties-file` extension. This extension essentially allows users and roles to be defined in the main Quarkus configuration file - `application.properties`. +For more information about this extension check link:security-properties.adoc[the associated guide]. +An example configuration would be the following: + +[source,properties] +---- +quarkus.security.users.embedded.enabled=true +quarkus.security.users.embedded.plain-text=true +quarkus.security.users.embedded.users.scott=jb0ss +quarkus.security.users.embedded.roles.scott=admin,user +quarkus.security.users.embedded.users.stuart=test +quarkus.security.users.embedded.roles.stuart=user +---- + +Note that the test also needs to be updated. It could look like: + +== GreetingControllerTest + +[source, java] +---- +package org.acme.spring.security; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class GreetingControllerTest { + + @Test + public void testHelloEndpointForbidden() { + given().auth().preemptive().basic("stuart", "test") + .when().get("/greeting") + .then() + .statusCode(403); + } + + @Test + public void testHelloEndpoint() { + given().auth().preemptive().basic("scott", "jb0ss") + .when().get("/greeting") + .then() + .statusCode(200) + .body(is("hello")); + } + +} +---- + +== Test the changes + +- Access allowed + +Open your browser again to http://localhost:8080/greeting and introduce `scott` and `jb0ss` in the dialog displayed. + +The word `hello` should be displayed. + +- Access forbidden + +Open your browser again to http://localhost:8080/greeting and let empty the dialog displayed. + +The result should be: +---- +Access to localhost was denied +You don't have authorization to view this page. +HTTP ERROR 403 +---- + +== Run the application as a native executable + +You can of course create a native image using the instructions of the link:building-native-image[Building a native executable guide]. + + +== Supported Spring Security functionalities + +Quarkus currently only supports a small subset of the functionalities that Spring Security provides and more features will be added in the future. More specifically, Quarkus supports the security related features of role-based authorization semantics +(think of `@Secured` instead of `@RolesAllowed`). + +=== Annotations + +The table below summarizes the supported annotations: + +.Supported Spring Security annotations +|=== +|Name|Comments + +|@Secured +| + +|=== + + +== Important Technical Note + +Please note that the Spring support in Quarkus does not start a Spring Application Context nor are any Spring infrastructure classes run. +Spring classes and annotations are only used for reading metadata and / or are used as user code method return types or parameter types. +What that means for end users, is that adding arbitrary Spring libraries will not have any effect. Moreover Spring infrastructure +classes (like `org.springframework.beans.factory.config.BeanPostProcessor` for example) will not be executed. + +== Conversion Table + +The following table shows how Spring Security annotations can be converted to JAX-RS annotations. + +|=== +|Spring |JAX-RS |Comments + +|@Secured("admin") +|@RolesAllowed("admin") +| + +|=== + +== More Spring guides + +Quarkus support has more Spring compatibility features. See the following guides for more details: + +* link:spring-di[Quarkus - Extension for Spring DI] +* link:spring-web[Quarkus - Extension for Spring Web] +* link:spring-data-jpa[Quarkus - Extension for Spring Data JPA] + + diff --git a/docs/src/main/asciidoc/spring-web.adoc b/docs/src/main/asciidoc/spring-web.adoc index 176b8f61a112c..ae7b7c3be26c0 100644 --- a/docs/src/main/asciidoc/spring-web.adoc +++ b/docs/src/main/asciidoc/spring-web.adoc @@ -325,5 +325,5 @@ Quarkus support has more Spring compatibility features. See the following guides * link:spring-di[Quarkus - Extension for Spring DI] * link:spring-data-jpa[Quarkus - Extension for Spring Data JPA] - +* link:spring-security[Quarkus - Extension for Spring Security] From 25910a8c0bd482d1e57e0dd39cc31a64edb9eb6d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 21 Nov 2019 10:48:01 +0200 Subject: [PATCH 134/602] Ensure that Spring beans are properly named and that the can be consumed by other extensions --- .../SpringBeanNameToDotNameBuildItem.java | 24 +++ .../di/deployment/SpringDIProcessor.java | 148 +++++++++++++----- .../io/quarkus/spring/di/deployment/Bean.java | 7 + .../spring/di/deployment/BeanNameTest.java | 28 ++++ .../di/deployment/SpringDIProcessorTest.java | 12 +- 5 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringBeanNameToDotNameBuildItem.java create mode 100644 extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/Bean.java create mode 100644 extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/BeanNameTest.java diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringBeanNameToDotNameBuildItem.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringBeanNameToDotNameBuildItem.java new file mode 100644 index 0000000000000..d37a52950356d --- /dev/null +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringBeanNameToDotNameBuildItem.java @@ -0,0 +1,24 @@ +package io.quarkus.spring.di.deployment; + +import java.util.Map; + +import org.jboss.jandex.DotName; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * The purpose of this bean is to map the names of the Spring Beans to their associated DotName + * This info is needed when trying to convert SpEL expressions that reference beans by name, to bytecode + */ +public final class SpringBeanNameToDotNameBuildItem extends SimpleBuildItem { + + private final Map map; + + public SpringBeanNameToDotNameBuildItem(Map map) { + this.map = map; + } + + public Map getMap() { + return map; + } +} diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index 3eb82669a9f32..666a3f65704c3 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -88,6 +88,37 @@ FeatureBuildItem registerFeature() { return new FeatureBuildItem(FeatureBuildItem.SPRING_DI); } + /* + * This Build Item can't be generated in the beanTransformer method because the annotation transformer + * is generated lazily. + * However the logic is the same + */ + @BuildStep + SpringBeanNameToDotNameBuildItem createBeanNamesMap(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) { + final Map result = new HashMap<>(); + + final IndexView index = beanArchiveIndexBuildItem.getIndex(); + final Collection stereotypeInstances = new ArrayList<>(); + stereotypeInstances.addAll(index.getAnnotations(SPRING_COMPONENT)); + stereotypeInstances.addAll(index.getAnnotations(SPRING_REPOSITORY)); + stereotypeInstances.addAll(index.getAnnotations(SPRING_SERVICE)); + for (AnnotationInstance stereotypeInstance : stereotypeInstances) { + if (stereotypeInstance.target().kind() != AnnotationTarget.Kind.CLASS) { + continue; + } + result.put(getBeanNameFromStereotypeInstance(stereotypeInstance), stereotypeInstance.target().asClass().name()); + } + + for (AnnotationInstance beanInstance : index.getAnnotations(BEAN_ANNOTATION)) { + if (beanInstance.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + result.put(getBeanNameFromBeanInstance(beanInstance), beanInstance.target().asMethod().returnType().name()); + } + + return new SpringBeanNameToDotNameBuildItem(result); + } + @BuildStep AnnotationsTransformerBuildItem beanTransformer( final BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, @@ -276,17 +307,8 @@ Set getAnnotationsToAdd( if (scopeNames != null) { scopes.addAll(scopeNames); } - if (SPRING_STEREOTYPE_ANNOTATIONS.contains(clazzAnnotation)) { - //check if the spring annotation defines a name for the bean - final AnnotationValue value = classInfo.classAnnotation(clazzAnnotation).value(); - if (value == null) { - continue; - } - final String name = value.asString(); - if (name == null || name.isEmpty()) { - continue; - } - names.add(name); + if (SPRING_STEREOTYPE_ANNOTATIONS.contains(clazzAnnotation) && !isAnnotation(classInfo.flags())) { + names.add(getBeanNameFromStereotypeInstance(classInfo.classAnnotation(clazzAnnotation))); } } } @@ -377,12 +399,11 @@ Set getAnnotationsToAdd( Collections.emptyList())); } - //check if the spring annotation defines a name for the bean - final AnnotationValue beanNameAnnotationValue = methodInfo.annotation(BEAN_ANNOTATION).value("name"); - final AnnotationValue beanValueAnnotationValue = methodInfo.annotation(BEAN_ANNOTATION).value("value"); - if (!addCDINamedAnnotation(target, beanNameAnnotationValue, annotationsToAdd)) { - addCDINamedAnnotation(target, beanValueAnnotationValue, annotationsToAdd); - } + String beanName = getBeanNameFromBeanInstance(methodInfo.annotation(BEAN_ANNOTATION)); + annotationsToAdd.add(create( + CDI_NAMED_ANNOTATION, + target, + Collections.singletonList(AnnotationValue.createStringValue("value", beanName)))); } // add method parameter conversion annotations @@ -407,6 +428,79 @@ Set getAnnotationsToAdd( return annotationsToAdd; } + /** + * Meant to be called with instances of @Component, @Service, @Repository + */ + private String getBeanNameFromStereotypeInstance(AnnotationInstance annotationInstance) { + if (annotationInstance.target().kind() != AnnotationTarget.Kind.CLASS) { + throw new IllegalStateException( + "AnnotationInstance " + annotationInstance + " is an invalid target. Only Class targets are supported"); + } + final AnnotationValue value = annotationInstance.value(); + if ((value == null) || value.asString().isEmpty()) { + return getDefaultBeanNameFromClass(annotationInstance.target().asClass().name().toString()); + } else { + return value.asString(); + } + } + + /** + * Meant to be called with instances of @Bean + */ + private String getBeanNameFromBeanInstance(AnnotationInstance annotationInstance) { + if (annotationInstance.target().kind() != AnnotationTarget.Kind.METHOD) { + throw new IllegalStateException( + "AnnotationInstance " + annotationInstance + " is an invalid target. Only Method targets are supported"); + } + + String beanName = null; + final AnnotationValue beanNameAnnotationValue = annotationInstance.value("name"); + if (beanNameAnnotationValue != null) { + beanName = determineName(beanNameAnnotationValue); + } + if (beanName == null || beanName.isEmpty()) { + final AnnotationValue beanValueAnnotationValue = annotationInstance.value(); + if (beanNameAnnotationValue != null) { + beanName = determineName(beanValueAnnotationValue); + } + } + if (beanName == null || beanName.isEmpty()) { + beanName = annotationInstance.target().asMethod().name(); + } + + return beanName; + } + + // this does what Spring's AnnotationBeanNameGenerator does to generate a name + private String getDefaultBeanNameFromClass(String className) { + return decapitalize(getShortNameOfClass(className)); + } + + private String getShortNameOfClass(String className) { + int lastDotIndex = className.lastIndexOf('.'); + int nameEndIndex = className.indexOf("$$"); + if (nameEndIndex == -1) { + nameEndIndex = className.length(); + } + String shortName = className.substring(lastDotIndex + 1, nameEndIndex); + shortName = shortName.replace('$', '.'); + return shortName; + } + + private String decapitalize(String name) { + if (name != null && name.length() != 0) { + if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) { + return name; + } else { + char[] chars = name.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + return new String(chars); + } + } else { + return name; + } + } + private void addSpringValueAnnotations(AnnotationTarget target, AnnotationInstance annotation, boolean addInject, Set annotationsToAdd) { final AnnotationValue annotationValue = annotation.value(); @@ -440,26 +534,6 @@ private void addSpringValueAnnotations(AnnotationTarget target, AnnotationInstan } } - private static boolean addCDINamedAnnotation(AnnotationTarget target, - AnnotationValue annotationValue, - Set annotationsToAdd) { - if (annotationValue == null) { - return false; - } - - final String beanName = determineName(annotationValue); - if (beanName != null && !"".equals(beanName)) { - annotationsToAdd.add(create( - CDI_NAMED_ANNOTATION, - target, - Collections.singletonList(AnnotationValue.createStringValue("value", beanName)))); - - return true; - } - - return false; - } - private static String determineName(AnnotationValue annotationValue) { if (annotationValue.kind() == AnnotationValue.Kind.ARRAY) { return annotationValue.asStringArray()[0]; diff --git a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/Bean.java b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/Bean.java new file mode 100644 index 0000000000000..e87e80a66ea10 --- /dev/null +++ b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/Bean.java @@ -0,0 +1,7 @@ +package io.quarkus.spring.di.deployment; + +import org.springframework.stereotype.Component; + +@Component +public class Bean { +} diff --git a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/BeanNameTest.java b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/BeanNameTest.java new file mode 100644 index 0000000000000..7cc809aa9008c --- /dev/null +++ b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/BeanNameTest.java @@ -0,0 +1,28 @@ +package io.quarkus.spring.di.deployment; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; + +public class BeanNameTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Bean.class)); + + @Inject + Bean consumer; + + @Test + public void testBeanName() { + Assertions.assertSame(consumer, Arc.container().instance("bean").get()); + } +} diff --git a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java index 6386ed9d83f88..e26f7a5600919 100644 --- a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java +++ b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java @@ -161,7 +161,9 @@ public void getAnnotationsToAddBeanMethodDefaultsToSingleton() { final Set expected = setOf( AnnotationInstance.create(DotName.createSimple(Singleton.class.getName()), target, Collections.emptyList()), - AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList())); + AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList()), + AnnotationInstance.create(DotName.createSimple(Named.class.getName()), target, + Collections.singletonList(AnnotationValue.createStringValue("value", "singletonBean")))); assertEquals(expected, ret); } @@ -178,7 +180,9 @@ public void getAnnotationsToAddBeanMethodExplicitSingleton() { final Set ret = processor.getAnnotationsToAdd(target, scopes, null); final Set expected = setOf( - AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList())); + AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList()), + AnnotationInstance.create(DotName.createSimple(Named.class.getName()), target, + Collections.singletonList(AnnotationValue.createStringValue("value", "explicitSingletonBean")))); assertEquals(expected, ret); } @@ -193,7 +197,9 @@ public void getAnnotationsToAddBeanMethodWithScope() { final Set expected = setOf( AnnotationInstance.create(DotName.createSimple(RequestScoped.class.getName()), target, Collections.emptyList()), - AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList())); + AnnotationInstance.create(DotNames.PRODUCES, target, Collections.emptyList()), + AnnotationInstance.create(DotName.createSimple(Named.class.getName()), target, + Collections.singletonList(AnnotationValue.createStringValue("value", "requestBean")))); assertEquals(expected, ret); } From ff48d5b3abc56411b1cf62acde7788e45ebce2c9 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 22 Nov 2019 15:07:34 +0200 Subject: [PATCH 135/602] Make SecurityCheck more flexible by passing the method and parameters --- .../security/runtime/interceptor/SecurityConstrainer.java | 4 ++-- .../security/runtime/interceptor/SecurityHandler.java | 2 +- .../runtime/interceptor/check/AuthenticatedCheck.java | 4 +++- .../security/runtime/interceptor/check/DenyAllCheck.java | 4 +++- .../security/runtime/interceptor/check/PermitAllCheck.java | 4 +++- .../runtime/interceptor/check/RolesAllowedCheck.java | 3 ++- .../security/runtime/interceptor/check/SecurityCheck.java | 6 ++++-- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java index 61c38dbbcf0e8..380d804b55500 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityConstrainer.java @@ -20,11 +20,11 @@ public class SecurityConstrainer { @Inject SecurityCheckStorage storage; - public void checkRoles(Method method) { + public void check(Method method, Object[] parameters) { SecurityCheck securityCheck = storage.getSecurityCheck(method); if (securityCheck != null) { - securityCheck.apply(identity); + securityCheck.apply(identity, method, parameters); } } } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java index dd697ba297b0a..11d9bd6ebd4d4 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/SecurityHandler.java @@ -20,7 +20,7 @@ public Object handle(InvocationContext ic) throws Exception { if (alreadyHandled(ic)) { return ic.proceed(); } - constrainer.checkRoles(ic.getMethod()); + constrainer.check(ic.getMethod(), ic.getParameters()); return ic.proceed(); } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java index e669874ff47f9..7f05682ac1323 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/AuthenticatedCheck.java @@ -1,5 +1,7 @@ package io.quarkus.security.runtime.interceptor.check; +import java.lang.reflect.Method; + import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; @@ -11,7 +13,7 @@ private AuthenticatedCheck() { } @Override - public void apply(SecurityIdentity identity) { + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { if (identity.isAnonymous()) { throw new UnauthorizedException(); } diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java index 4aabfa26592af..70e1d0d832771 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/DenyAllCheck.java @@ -1,5 +1,7 @@ package io.quarkus.security.runtime.interceptor.check; +import java.lang.reflect.Method; + import io.quarkus.security.ForbiddenException; import io.quarkus.security.UnauthorizedException; import io.quarkus.security.identity.SecurityIdentity; @@ -12,7 +14,7 @@ private DenyAllCheck() { } @Override - public void apply(SecurityIdentity identity) { + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { if (identity.isAnonymous()) { throw new UnauthorizedException(); } else { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java index 2ceab71c5af96..c3be76e05f1d4 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/PermitAllCheck.java @@ -1,5 +1,7 @@ package io.quarkus.security.runtime.interceptor.check; +import java.lang.reflect.Method; + import io.quarkus.security.identity.SecurityIdentity; public class PermitAllCheck implements SecurityCheck { @@ -10,6 +12,6 @@ private PermitAllCheck() { } @Override - public void apply(SecurityIdentity identity) { + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { } } \ No newline at end of file diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java index eadad9da63016..8bf30ba6381f9 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/RolesAllowedCheck.java @@ -1,5 +1,6 @@ package io.quarkus.security.runtime.interceptor.check; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -49,7 +50,7 @@ private static Collection getCollectionForKey(String[] allowedRoles) { } @Override - public void apply(SecurityIdentity identity) { + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { Set roles = identity.getRoles(); if (roles != null) { for (String role : allowedRoles) { diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java index 31b36c98540b0..bee1e06417c61 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/interceptor/check/SecurityCheck.java @@ -1,7 +1,9 @@ package io.quarkus.security.runtime.interceptor.check; +import java.lang.reflect.Method; + import io.quarkus.security.identity.SecurityIdentity; public interface SecurityCheck { - void apply(SecurityIdentity identity); -} \ No newline at end of file + void apply(SecurityIdentity identity, Method method, Object[] parameters); +} From 4acf7b87896c4c812c984dd09f3202de7c349882 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 25 Nov 2019 19:25:44 +0200 Subject: [PATCH 136/602] Move security related classes of spring integration test to proper package --- .../it/spring/security/{security => }/SecuredService.java | 2 +- .../security/{security => }/ServiceWithSecuredMethods.java | 2 +- .../io/quarkus/it/spring/security/{security => }/Subclass.java | 2 +- .../it/spring/security/{security => }/TheController.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/{security => }/SecuredService.java (88%) rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/{security => }/ServiceWithSecuredMethods.java (88%) rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/{security => }/Subclass.java (81%) rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/{security => }/TheController.java (96%) diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/SecuredService.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/SecuredService.java similarity index 88% rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/SecuredService.java rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/SecuredService.java index 292518915be72..651b0e2d54d07 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/SecuredService.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/SecuredService.java @@ -1,4 +1,4 @@ -package io.quarkus.it.spring.security.security; +package io.quarkus.it.spring.security; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Service; diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/ServiceWithSecuredMethods.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithSecuredMethods.java similarity index 88% rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/ServiceWithSecuredMethods.java rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithSecuredMethods.java index 60202b51afd63..ff32cee313dbe 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/ServiceWithSecuredMethods.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithSecuredMethods.java @@ -1,4 +1,4 @@ -package io.quarkus.it.spring.security.security; +package io.quarkus.it.spring.security; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Service; diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/Subclass.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Subclass.java similarity index 81% rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/Subclass.java rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Subclass.java index 2f6be24283eed..9cb5bd24a7c07 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/Subclass.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Subclass.java @@ -1,4 +1,4 @@ -package io.quarkus.it.spring.security.security; +package io.quarkus.it.spring.security; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Service; diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/TheController.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java similarity index 96% rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/TheController.java rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java index 4e6c367212ba5..06915ac951c2a 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/security/TheController.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java @@ -1,4 +1,4 @@ -package io.quarkus.it.spring.security.security; +package io.quarkus.it.spring.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; From eeadec73d5fbd67a550b1ec8c6db7a6987545212 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 21 Nov 2019 10:48:12 +0200 Subject: [PATCH 137/602] Introduce support for the most common features of Spring Security's @PreAuthorize --- .../AdditionalSecurityCheckBuildItem.java | 1 + .../SecurityCheckInstantiationUtil.java | 2 +- .../quarkus/security/test/utils/AuthData.java | 4 +- .../security/test/utils/IdentityMock.java | 15 +- extensions/spring-security/deployment/pom.xml | 2 +- .../BeanMethodInvocationGenerator.java | 252 +++++++++++ .../spring/security/deployment/DotNames.java | 12 + .../security/deployment/HasRoleValueUtil.java | 60 +++ .../deployment/ParameterNameAndIndex.java | 19 + .../PreAuthorizeSecurityCheckUtil.java | 132 ++++++ ...gPreAuthorizeAnnotatedMethodBuildItem.java | 25 ++ .../SpringSecurityAnnotationsRegistrar.java | 2 + .../deployment/SpringSecurityProcessor.java | 406 +++++++++++++++++- .../SpringSecurityProcessorUtil.java | 64 +++ .../StringPropertyAccessorData.java | 58 +++ .../StringPropertyAccessorGenerator.java | 87 ++++ .../roles/FromBeanHasRoleValueProducer.java | 35 ++ .../roles/HasRoleValueProducer.java | 9 + .../roles/StaticHasRoleValueProducer.java | 18 + .../deployment/BeanMethodCheckTest.java | 104 +++++ .../deployment/MetaAnnotationsTest.java | 78 ++++ .../SpringPreAuthorizeClassAnnotatedTest.java | 87 ++++ .../deployment/SpringPreAuthorizeTest.java | 134 ++++++ ...SpringPreAuthorizeWithExpressionsTest.java | 58 +++ .../springapp/BeanWithAndOrExpressions.java | 23 + .../springapp/BeanWithBeanMethodChecks.java | 33 ++ .../springapp/BeanWithMetaAnnotations.java | 27 ++ .../ComponentWithClassAnnotation.java | 24 ++ .../deployment/springapp/DenyAllOnClass.java | 24 ++ .../security/deployment/springapp/IsUser.java | 14 + .../deployment/springapp/IsUserOrAdmin.java | 14 + .../security/deployment/springapp/Person.java | 14 + .../deployment/springapp/PersonChecker.java | 10 + .../springapp/PersonCheckerImpl.java | 19 + .../springapp/PrincipalChecker.java | 11 + .../security/deployment/springapp/Roles.java | 10 + .../deployment/springapp/SomeInterface.java | 6 + .../springapp/SomeInterfaceImpl.java | 14 + .../deployment/springapp/SpringComponent.java | 48 +++ .../springapp/SpringConfiguration.java | 13 + .../deployment/springapp/SpringService.java | 28 ++ .../SpringPreauthorizeInterceptor.java | 25 ++ .../accessor/StringPropertyAccessor.java | 9 + .../AbstractBeanMethodSecurityCheck.java | 30 ++ .../check/AllDelegatingSecurityCheck.java | 26 ++ .../interceptor/check/AnonymousCheck.java | 22 + .../check/AnyDelegatingSecurityCheck.java | 37 ++ ...lNameFromParameterObjectSecurityCheck.java | 72 ++++ ...incipalNameFromParameterSecurityCheck.java | 68 +++ .../spring/security/AlwaysFalseChecker.java | 11 + .../io/quarkus/it/spring/security/Roles.java | 9 + .../security/ServiceWithPreAuthorize.java | 18 + .../it/spring/security/TheController.java | 21 + .../src/main/resources/test-roles.properties | 4 +- .../src/main/resources/test-users.properties | 4 +- .../it/spring/web/SpringControllerTest.java | 28 ++ 56 files changed, 2370 insertions(+), 10 deletions(-) create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/BeanMethodInvocationGenerator.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/HasRoleValueUtil.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/ParameterNameAndIndex.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/PreAuthorizeSecurityCheckUtil.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeAnnotatedMethodBuildItem.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessorUtil.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorData.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorGenerator.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/FromBeanHasRoleValueProducer.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/HasRoleValueProducer.java create mode 100644 extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/StaticHasRoleValueProducer.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/BeanMethodCheckTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/MetaAnnotationsTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeClassAnnotatedTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeWithExpressionsTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithAndOrExpressions.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithBeanMethodChecks.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithMetaAnnotations.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/ComponentWithClassAnnotation.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/DenyAllOnClass.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUser.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUserOrAdmin.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Person.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonChecker.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonCheckerImpl.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PrincipalChecker.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Roles.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterface.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterfaceImpl.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringConfiguration.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringService.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringPreauthorizeInterceptor.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/accessor/StringPropertyAccessor.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java create mode 100644 extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/AlwaysFalseChecker.java create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Roles.java create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithPreAuthorize.java diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java index 31728ee802f10..f0c33d40a0217 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/AdditionalSecurityCheckBuildItem.java @@ -10,6 +10,7 @@ /** * Used as an integration point when extensions need to customize the security behavior of a bean + * The ResultHandle that is returned by function needs to be an instance of SecurityCheck */ public final class AdditionalSecurityCheckBuildItem extends MultiBuildItem { diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityCheckInstantiationUtil.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityCheckInstantiationUtil.java index f24acfeafb2e4..4320e5dec43fb 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityCheckInstantiationUtil.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityCheckInstantiationUtil.java @@ -17,7 +17,7 @@ public class SecurityCheckInstantiationUtil { private SecurityCheckInstantiationUtil() { } - public static Function rolesAllowedSecurityCheck(final String[] rolesAllowed) { + public static Function rolesAllowedSecurityCheck(String... rolesAllowed) { return new Function() { @Override public ResultHandle apply(BytecodeCreator creator) { diff --git a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/AuthData.java b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/AuthData.java index 1f056be0fb826..010d011fc0a0f 100644 --- a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/AuthData.java +++ b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/AuthData.java @@ -8,9 +8,11 @@ public class AuthData { public final Set roles; public final boolean anonymous; + public final String name; - public AuthData(Set roles, boolean anonymous) { + public AuthData(Set roles, boolean anonymous, String name) { this.roles = roles; this.anonymous = anonymous; + this.name = name; } } diff --git a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/IdentityMock.java b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/IdentityMock.java index f8ffdd698132d..766b9c1436bff 100644 --- a/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/IdentityMock.java +++ b/extensions/security/test-utils/src/main/java/io/quarkus/security/test/utils/IdentityMock.java @@ -22,21 +22,28 @@ @Priority(1) public class IdentityMock implements SecurityIdentity { - public static final AuthData ANONYMOUS = new AuthData(null, true); - public static final AuthData USER = new AuthData(Collections.singleton("user"), false); - public static final AuthData ADMIN = new AuthData(Collections.singleton("admin"), false); + public static final AuthData ANONYMOUS = new AuthData(null, true, null); + public static final AuthData USER = new AuthData(Collections.singleton("user"), false, "user"); + public static final AuthData ADMIN = new AuthData(Collections.singleton("admin"), false, "admin"); private static volatile boolean anonymous; private static volatile Set roles; + private static volatile String name; public static void setUpAuth(AuthData auth) { IdentityMock.anonymous = auth.anonymous; IdentityMock.roles = auth.roles; + IdentityMock.name = auth.name; } @Override public Principal getPrincipal() { - return () -> "whatever"; + return new Principal() { + @Override + public String getName() { + return name; + } + }; } @Override diff --git a/extensions/spring-security/deployment/pom.xml b/extensions/spring-security/deployment/pom.xml index be3c46aa77289..6eb0d967b2882 100644 --- a/extensions/spring-security/deployment/pom.xml +++ b/extensions/spring-security/deployment/pom.xml @@ -16,7 +16,7 @@ io.quarkus - quarkus-core-deployment + quarkus-spring-di-deployment io.quarkus diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/BeanMethodInvocationGenerator.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/BeanMethodInvocationGenerator.java new file mode 100644 index 0000000000000..c6c79af6af1eb --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/BeanMethodInvocationGenerator.java @@ -0,0 +1,252 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.gizmo.FieldDescriptor.of; +import static io.quarkus.gizmo.MethodDescriptor.ofConstructor; +import static io.quarkus.gizmo.MethodDescriptor.ofMethod; +import static io.quarkus.spring.security.deployment.SpringSecurityProcessorUtil.BASIC_BEAN_METHOD_INVOCATION_PATTERN; +import static io.quarkus.spring.security.deployment.SpringSecurityProcessorUtil.createGenericMalformedException; +import static io.quarkus.spring.security.deployment.SpringSecurityProcessorUtil.getClassInfoFromBeanName; +import static io.quarkus.spring.security.deployment.SpringSecurityProcessorUtil.getParameterIndex; + +import java.lang.reflect.Modifier; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.deployment.util.HashUtil; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.spring.security.runtime.interceptor.check.AbstractBeanMethodSecurityCheck; + +class BeanMethodInvocationGenerator { + + private static final String METHOD_PARAMETER_REGEX = "#(\\w+)"; + private static final Pattern METHOD_PARAMETER_PATTERN = Pattern.compile(METHOD_PARAMETER_REGEX); + + private final IndexView index; + private final Map springBeansNameToDotName; + private final Map springBeansNameToClassInfo; + private final Set beansReferencedInPreAuthorized; + private final ClassOutput classOutput; + + private final Map alreadyGeneratedClasses = new HashMap<>(); + + public BeanMethodInvocationGenerator(IndexView index, Map springBeansNameToDotName, + Map springBeansNameToClassInfo, Set beansReferencedInPreAuthorized, + ClassOutput classOutput) { + this.index = index; + this.springBeansNameToDotName = springBeansNameToDotName; + this.springBeansNameToClassInfo = springBeansNameToClassInfo; + this.beansReferencedInPreAuthorized = beansReferencedInPreAuthorized; + this.classOutput = classOutput; + } + + /** + * Returns the name of the generated class that implements the security check + * The generated class is an implementation of {@link AbstractBeanMethodSecurityCheck} + * that simply calls the proper bean method with the correct arguments + */ + final String generateSecurityCheck(String expression, MethodInfo securedMethodInfo) { + String paramTypesDescriptor = getParamTypesDescriptor(securedMethodInfo); + int parametersStartIndex = expression.indexOf('('); + int parametersEndIndex = expression.indexOf(')'); + String[] beanMethodArgumentExpressions = {}; + if (parametersEndIndex > parametersStartIndex + 1) { + beanMethodArgumentExpressions = expression.substring(parametersStartIndex + 1, parametersEndIndex).split(","); + } + /* + * We need to make sure the cache key contains the both the expression and the parameter types of the method + * on which the SecurityCheck will apply + * This is because the generated security check takes into account the parameter types in order to create the + * proper calls to the bean method. + * If however the expressions indicated that no parameters are passed to the bean method, we can just use the + * expression as the cache key + */ + String cacheKey = beanMethodArgumentExpressions.length > 0 ? expression + "-" + paramTypesDescriptor : expression; + + String cachedGeneratedClassName = alreadyGeneratedClasses.get(cacheKey); + if (cachedGeneratedClassName != null) { + return cachedGeneratedClassName; + } + + Matcher matcher = BASIC_BEAN_METHOD_INVOCATION_PATTERN.matcher(expression); + if (!matcher.find()) { // should never happen + throw createGenericMalformedException(securedMethodInfo, expression); + } + + String beanName = matcher.group(1); + ClassInfo beanClassInfo = getClassInfoFromBeanName(beanName, index, + springBeansNameToDotName, springBeansNameToClassInfo, expression, securedMethodInfo); + + String beanMethodName = matcher.group(2); + MethodInfo matchingBeanMethod = determineMatchingBeanMethod(beanMethodName, beanMethodArgumentExpressions.length, + beanClassInfo, securedMethodInfo, expression, beanName); + + String generatedClassName = "io.quarkus.spring.security.check." + beanClassInfo.name().withoutPackagePrefix() + "_" + + HashUtil.sha1(cacheKey) + "_CheckFor_" + beanMethodName; + try (ClassCreator cc = ClassCreator.builder() + .classOutput(classOutput).className(generatedClassName) + .superClass(AbstractBeanMethodSecurityCheck.class) + .build()) { + + /* + * The generated classes will have a static getInstance method that will allow the creation of a single instance + * This is done to avoid creating multiple objects for the same expression + */ + + FieldDescriptor instanceField = of(generatedClassName, "INSTANCE", generatedClassName); + cc.getFieldCreator(instanceField).setModifiers(Modifier.STATIC | Modifier.PRIVATE); + + try (MethodCreator getInstance = cc.getMethodCreator("getInstance", generatedClassName) + .setModifiers(Modifier.STATIC | Modifier.PUBLIC)) { + ResultHandle instance = getInstance.readStaticField(instanceField); + BranchResult instanceNullBranch = getInstance.ifNull(instance); + instanceNullBranch.falseBranch().returnValue(instance); + BytecodeCreator instanceNullTrue = instanceNullBranch.trueBranch(); + ResultHandle newInstance = instanceNullTrue.newInstance(ofConstructor(generatedClassName)); + instanceNullTrue.writeStaticField(instanceField, newInstance); + instanceNullTrue.returnValue(newInstance); + } + + try (MethodCreator check = cc.getMethodCreator("check", boolean.class, SecurityIdentity.class, Object[].class) + .setModifiers(Modifier.PROTECTED)) { + ResultHandle arcContainer = check + .invokeStaticMethod(ofMethod(Arc.class, "container", ArcContainer.class)); + ResultHandle instanceHandle = check.invokeInterfaceMethod( + ofMethod(ArcContainer.class, "instance", InstanceHandle.class, String.class), + arcContainer, check.load(beanName)); + ResultHandle bean = check + .invokeInterfaceMethod(ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle); + ResultHandle castedBean = check.checkCast(bean, beanClassInfo.name().toString()); + ResultHandle[] argHandles = new ResultHandle[beanMethodArgumentExpressions.length]; + + for (int i = 0; i < beanMethodArgumentExpressions.length; i++) { + String argumentExpression = beanMethodArgumentExpressions[i]; + String trimmedArgumentExpression = argumentExpression.trim(); + if (argumentExpression.startsWith("'") && argumentExpression.endsWith("'")) { // hard coded string case + if (!DotNames.STRING.equals(matchingBeanMethod.parameters().get(i).name())) { + throw new IllegalArgumentException("Parameter with index " + i + " of method '" + beanMethodName + + "' found in expression '" + trimmedArgumentExpression + + "' in the @PreAuthorize annotation on method " + securedMethodInfo.name() + " of class " + + securedMethodInfo.declaringClass() + " is not of type String"); + } + + argHandles[i] = check.load(argumentExpression.replace("'", "")); + } else if (trimmedArgumentExpression.matches(METHOD_PARAMETER_REGEX)) { // secured method's parameter case + Matcher parameterMatcher = METHOD_PARAMETER_PATTERN.matcher(trimmedArgumentExpression); + if (!parameterMatcher.find()) { // should never happen + throw createGenericMalformedException(securedMethodInfo, expression); + } + + // this is the index index of the parameter we care about + int parameterIndex = getParameterIndex(securedMethodInfo, parameterMatcher.group(1), expression); + + DotName expectedType = securedMethodInfo.parameters().get(parameterIndex).name(); + if (!matchingBeanMethod.parameters().get(i).name().equals(expectedType)) { + throw new IllegalArgumentException("Parameter with index " + i + " of method '" + beanMethodName + + "' found in expression '" + trimmedArgumentExpression + + "' in the @PreAuthorize annotation on method " + securedMethodInfo.name() + " of class " + + securedMethodInfo.declaringClass() + " is not of type " + expectedType); + } + + /* + * the check method from AbstractBeanMethodSecurityCheck contains all parameters in an object array + * so we need to use that to read the value at runtime + */ + ResultHandle methodArgsArrays = check.getMethodParam(1); + argHandles[i] = check.readArrayValue(methodArgsArrays, parameterIndex); + } else if (trimmedArgumentExpression.matches("(authentication.)?principal.username")) { // username use case + ResultHandle securityIdentity = check.getMethodParam(0); + ResultHandle principal = check.invokeInterfaceMethod( + ofMethod(SecurityIdentity.class, "getPrincipal", Principal.class), securityIdentity); + + if (!DotNames.STRING.equals(matchingBeanMethod.parameters().get(i).name())) { + throw new IllegalArgumentException("Parameter with index " + i + " of method '" + beanMethodName + + "' found in expression '" + trimmedArgumentExpression + + "' in the @PreAuthorize annotation on method " + securedMethodInfo.name() + " of class " + + securedMethodInfo.declaringClass() + " is not of type String"); + } + + ResultHandle username = check + .invokeInterfaceMethod(ofMethod(Principal.class, "getName", String.class), principal); + argHandles[i] = username; + } else { + throw createGenericMalformedException(securedMethodInfo, expression); + } + } + + ResultHandle result; + if (Modifier.isInterface(matchingBeanMethod.declaringClass().flags())) { + result = check.invokeInterfaceMethod(MethodDescriptor.of(matchingBeanMethod), castedBean, argHandles); + } else { + result = check.invokeVirtualMethod(MethodDescriptor.of(matchingBeanMethod), castedBean, argHandles); + } + + check.returnValue(result); + } + } + + beansReferencedInPreAuthorized.add(beanClassInfo.name().toString()); + alreadyGeneratedClasses.put(cacheKey, generatedClassName); + return generatedClassName; + } + + private String getParamTypesDescriptor(MethodInfo securedMethodInfo) { + StringBuilder sb = new StringBuilder("("); + for (Type type : securedMethodInfo.parameters()) { + sb.append(DescriptorUtils.objectToDescriptor(type.name().toString())); + } + sb.append(")"); + return sb.toString(); + } + + private MethodInfo determineMatchingBeanMethod(String methodName, int methodParametersSize, ClassInfo beanClassInfo, + MethodInfo securedMethodInfo, String expression, String beanName) { + MethodInfo matchingBeanClassMethod = null; + for (MethodInfo candidateMethod : beanClassInfo.methods()) { + if (candidateMethod.name().equals(methodName) && + Modifier.isPublic(candidateMethod.flags()) && + DotNames.PRIMITIVE_BOOLEAN.equals(candidateMethod.returnType().name()) && + candidateMethod.parameters().size() == methodParametersSize) { + if (matchingBeanClassMethod == null) { + matchingBeanClassMethod = candidateMethod; + } else { + throw new IllegalArgumentException( + "Could not match a unique method name '" + methodName + "' for bean named " + beanName + + " with class " + beanClassInfo.name() + " Offending expression is " + + expression + " of @PreAuthorize on method '" + methodName + "' of class " + + securedMethodInfo.declaringClass()); + } + } + } + if (matchingBeanClassMethod == null) { + throw new IllegalArgumentException( + "Could not find a public, boolean returning method named '" + methodName + "' for bean named " + beanName + + " with class " + beanClassInfo.name() + " Offending expression is " + + expression + " of @PreAuthorize on method '" + methodName + "' of class " + + securedMethodInfo.declaringClass()); + } + return matchingBeanClassMethod; + } + +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/DotNames.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/DotNames.java index 1b1c896642e09..baa3ddec4bd45 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/DotNames.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/DotNames.java @@ -1,11 +1,23 @@ package io.quarkus.spring.security.deployment; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import org.jboss.jandex.DotName; import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; public final class DotNames { + static final DotName STRING = DotName.createSimple(String.class.getName()); + static final DotName PRIMITIVE_BOOLEAN = DotName.createSimple(boolean.class.getName()); + static final DotName SPRING_SECURED = DotName.createSimple(Secured.class.getName()); + static final DotName SPRING_PRE_AUTHORIZE = DotName.createSimple(PreAuthorize.class.getName()); + + static final Set SUPPORTED_SPRING_SECURITY_ANNOTATIONS = new HashSet<>( + Arrays.asList(SPRING_SECURED, SPRING_PRE_AUTHORIZE)); private DotNames() { } diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/HasRoleValueUtil.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/HasRoleValueUtil.java new file mode 100644 index 0000000000000..723cc88d7e4b1 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/HasRoleValueUtil.java @@ -0,0 +1,60 @@ +package io.quarkus.spring.security.deployment; + +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.spring.security.deployment.roles.FromBeanHasRoleValueProducer; +import io.quarkus.spring.security.deployment.roles.HasRoleValueProducer; +import io.quarkus.spring.security.deployment.roles.StaticHasRoleValueProducer; + +final class HasRoleValueUtil { + + private static final String BEAN_FIELD_REGEX = "@(\\w+)\\.(\\w+)"; + private static final Pattern BEAN_FIELD_PATTERN = Pattern.compile(BEAN_FIELD_REGEX); + + private HasRoleValueUtil() { + } + + static HasRoleValueProducer getHasRoleValueProducer(String hasRoleValue, MethodInfo methodInfo, IndexView index, + Map springBeansNameToDotName, + Map springBeansNameToClassInfo, Set beansReferencedInPreAuthorized) { + if (hasRoleValue.startsWith("'") && hasRoleValue.endsWith("'")) { + return new StaticHasRoleValueProducer(hasRoleValue.replace("'", "")); + } else if (hasRoleValue.startsWith("@")) { + Matcher beanFieldMatcher = BEAN_FIELD_PATTERN.matcher(hasRoleValue); + if (!beanFieldMatcher.find()) { + throw SpringSecurityProcessorUtil.createGenericMalformedException(methodInfo, hasRoleValue); + } + + String beanName = beanFieldMatcher.group(1); + ClassInfo beanClassInfo = SpringSecurityProcessorUtil.getClassInfoFromBeanName(beanName, index, + springBeansNameToDotName, springBeansNameToClassInfo, hasRoleValue, methodInfo); + + String fieldName = beanFieldMatcher.group(2); + FieldInfo fieldInfo = beanClassInfo.field(fieldName); + if ((fieldInfo == null) || !Modifier.isPublic(fieldInfo.flags()) + || !DotNames.STRING.equals(fieldInfo.type().name())) { + throw new IllegalArgumentException("Bean named '" + beanName + "' found in expression '" + hasRoleValue + + "' in the @PreAuthorize annotation on method " + methodInfo.name() + " of class " + + methodInfo.declaringClass() + " does not have a public field named '" + fieldName + + "' of type String"); + } + + beansReferencedInPreAuthorized.add(fieldInfo.declaringClass().name().toString()); + + return new FromBeanHasRoleValueProducer(beanName, fieldInfo); + } else { + throw SpringSecurityProcessorUtil.createGenericMalformedException(methodInfo, hasRoleValue); + } + } + +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/ParameterNameAndIndex.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/ParameterNameAndIndex.java new file mode 100644 index 0000000000000..2a89b375fdb37 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/ParameterNameAndIndex.java @@ -0,0 +1,19 @@ +package io.quarkus.spring.security.deployment; + +class ParameterNameAndIndex { + private final int index; + private final String name; + + public ParameterNameAndIndex(int index, String name) { + this.index = index; + this.name = name; + } + + public int getIndex() { + return index; + } + + public String getName() { + return name; + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/PreAuthorizeSecurityCheckUtil.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/PreAuthorizeSecurityCheckUtil.java new file mode 100644 index 0000000000000..f0fdd0395ab30 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/PreAuthorizeSecurityCheckUtil.java @@ -0,0 +1,132 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.gizmo.FieldDescriptor.of; +import static io.quarkus.gizmo.MethodDescriptor.ofConstructor; +import static io.quarkus.gizmo.MethodDescriptor.ofMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.security.runtime.interceptor.check.RolesAllowedCheck; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.spring.security.deployment.roles.HasRoleValueProducer; +import io.quarkus.spring.security.runtime.interceptor.check.AllDelegatingSecurityCheck; +import io.quarkus.spring.security.runtime.interceptor.check.AnonymousCheck; +import io.quarkus.spring.security.runtime.interceptor.check.AnyDelegatingSecurityCheck; +import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterObjectSecurityCheck; +import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterSecurityCheck; + +/** + * Utility used to provide access to instances of the appropriate SecurityCheck classes + */ +final class PreAuthorizeSecurityCheckUtil { + + private PreAuthorizeSecurityCheckUtil() { + } + + public static Function anonymousSecurityCheck() { + return new Function() { + @Override + public ResultHandle apply(BytecodeCreator creator) { + return creator.readStaticField(of(AnonymousCheck.class, "INSTANCE", AnonymousCheck.class)); + } + }; + } + + public static Function hasRoleSecurityCheck( + List hasRoleValueProducers) { + return new HasRoleSecurityCheck(hasRoleValueProducers); + } + + public static Function principalNameFromParameterSecurityCheck(int index, + PrincipalNameFromParameterSecurityCheck.CheckType checkType) { + return new Function() { + @Override + public ResultHandle apply(BytecodeCreator creator) { + return creator.invokeStaticMethod( + ofMethod(PrincipalNameFromParameterSecurityCheck.class, "of", + PrincipalNameFromParameterSecurityCheck.class, int.class, + PrincipalNameFromParameterSecurityCheck.CheckType.class), + creator.load(index), + creator.readStaticField(FieldDescriptor.of(PrincipalNameFromParameterSecurityCheck.CheckType.class, + checkType.toString(), PrincipalNameFromParameterSecurityCheck.CheckType.class))); + } + }; + } + + public static Function principalNameFromParameterObjectSecurityCheck(int index, + String expectedParameterClass, String stringPropertyAccessorClass, String propertyName) { + return new Function() { + @Override + public ResultHandle apply(BytecodeCreator creator) { + return creator.invokeStaticMethod( + ofMethod(PrincipalNameFromParameterObjectSecurityCheck.class, "of", + PrincipalNameFromParameterObjectSecurityCheck.class, int.class, String.class, String.class, + String.class), + creator.load(index), creator.load(expectedParameterClass), + creator.load(stringPropertyAccessorClass), creator.load(propertyName)); + } + }; + } + + public static Function generateAllDelegatingSecurityCheck( + List> delegatesList) { + return generateDelegatingSecurityCheck(delegatesList, AllDelegatingSecurityCheck.class); + } + + public static Function generateAnyDelegatingSecurityCheck( + List> delegatesList) { + return generateDelegatingSecurityCheck(delegatesList, AnyDelegatingSecurityCheck.class); + } + + private static Function generateDelegatingSecurityCheck( + List> delegatesList, Class securityCheckClass) { + return new Function() { + @Override + public ResultHandle apply(BytecodeCreator creator) { + ResultHandle delegates = creator.newInstance(ofConstructor(ArrayList.class, int.class), + creator.load(delegatesList.size())); + for (Function delegateFunction : delegatesList) { + ResultHandle delegate = delegateFunction.apply(creator); + creator.invokeVirtualMethod(ofMethod(ArrayList.class, "add", boolean.class, Object.class), delegates, + delegate); + } + return creator.newInstance(ofConstructor(securityCheckClass, List.class), delegates); + } + }; + } + + public static Function beanMethodGeneratedSecurityCheck(String generatedClassName) { + return new Function() { + @Override + public ResultHandle apply(BytecodeCreator creator) { + return creator.invokeStaticMethod(ofMethod(generatedClassName, "getInstance", generatedClassName)); + } + }; + } + + private static class HasRoleSecurityCheck implements Function { + private final List roleValueProducers; + + private HasRoleSecurityCheck(List roleValueProducers) { + this.roleValueProducers = roleValueProducers; + } + + @Override + public ResultHandle apply(BytecodeCreator creator) { + ResultHandle rolesAllowedArgs = creator.newArray(String.class, creator.load(roleValueProducers.size())); + int i = 0; + for (Function roleValueProducer : roleValueProducers) { + creator.writeArrayValue(rolesAllowedArgs, i++, roleValueProducer.apply(creator)); + } + + return creator.invokeStaticMethod( + ofMethod(RolesAllowedCheck.class, "of", RolesAllowedCheck.class, String[].class), rolesAllowedArgs); + } + } + +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeAnnotatedMethodBuildItem.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeAnnotatedMethodBuildItem.java new file mode 100644 index 0000000000000..5cf2805c455a7 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeAnnotatedMethodBuildItem.java @@ -0,0 +1,25 @@ +package io.quarkus.spring.security.deployment; + +import java.util.Map; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Build Item recording all the methods that have been effectively annotated with + * {@code @PreAuthorize} + */ +public final class SpringPreAuthorizeAnnotatedMethodBuildItem extends SimpleBuildItem { + + private final Map methodToInstanceMap; + + public SpringPreAuthorizeAnnotatedMethodBuildItem(Map methodToInstanceMap) { + this.methodToInstanceMap = methodToInstanceMap; + } + + public Map getMethodToInstanceMap() { + return methodToInstanceMap; + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityAnnotationsRegistrar.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityAnnotationsRegistrar.java index f5b6be72c94cc..243dbe439be46 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityAnnotationsRegistrar.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityAnnotationsRegistrar.java @@ -7,6 +7,7 @@ import org.jboss.jandex.DotName; import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; import io.quarkus.arc.processor.InterceptorBindingRegistrar; @@ -16,6 +17,7 @@ public class SpringSecurityAnnotationsRegistrar implements InterceptorBindingReg static { SECURITY_BINDINGS.put(DotName.createSimple(Secured.class.getName()), Collections.singleton("value")); + SECURITY_BINDINGS.put(DotName.createSimple(PreAuthorize.class.getName()), Collections.singleton("value")); } @Override diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java index 6b6b2540715a9..8f4b838aef591 100644 --- a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessor.java @@ -4,28 +4,65 @@ import static io.quarkus.security.deployment.SecurityTransformerUtils.hasStandardSecurityAnnotation; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.security.deployment.AdditionalSecurityCheckBuildItem; import io.quarkus.security.deployment.SecurityCheckInstantiationUtil; import io.quarkus.security.deployment.SecurityTransformerUtils; +import io.quarkus.spring.di.deployment.SpringBeanNameToDotNameBuildItem; +import io.quarkus.spring.security.deployment.roles.HasRoleValueProducer; +import io.quarkus.spring.security.runtime.interceptor.SpringPreauthorizeInterceptor; import io.quarkus.spring.security.runtime.interceptor.SpringSecuredInterceptor; +import io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterSecurityCheck; class SpringSecurityProcessor { + private static final String PARAMETER_EQ_PRINCIPAL_USERNAME_REGEX = "#(\\w+)(\\.(\\w+))?\\s+[=!]=\\s+(authentication.)?principal.username"; + private static final Pattern PARAMETER_EQ_PRINCIPAL_USERNAME_PATTERN = Pattern + .compile(PARAMETER_EQ_PRINCIPAL_USERNAME_REGEX); + + private static final int PARAMETER_EQ_PRINCIPAL_USERNAME_PARAMETER_NAME_GROUP = 1; + private static final int PARAMETER_EQ_PRINCIPAL_USERNAME_PROPERTY_ACCESSOR_MATCHER_GROUP = 3; + + static final int ANNOTATION = 0x00002000; + + static boolean isAnnotation(final int mod) { + return (mod & ANNOTATION) != 0; + } + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.SPRING_SECURITY); @@ -36,6 +73,7 @@ void registerSecurityInterceptors(BuildProducer beans) { registrars.produce(new InterceptorBindingRegistrarBuildItem(new SpringSecurityAnnotationsRegistrar())); beans.produce(new AdditionalBeanBuildItem(SpringSecuredInterceptor.class)); + beans.produce(new AdditionalBeanBuildItem(SpringPreauthorizeInterceptor.class)); } @BuildStep @@ -43,6 +81,8 @@ void addSpringSecuredSecurityCheck(ApplicationIndexBuildItem index, BuildProducer additionalSecurityCheckBuildItems) { Set methodsWithSecurityAnnotation = new HashSet<>(); + + // first first go through the list of annotated methods for (AnnotationInstance instance : index.getIndex().getAnnotations(DotNames.SPRING_SECURED)) { if (instance.value() == null) { continue; @@ -56,7 +96,17 @@ void addSpringSecuredSecurityCheck(ApplicationIndexBuildItem index, additionalSecurityCheckBuildItems.produce(new AdditionalSecurityCheckBuildItem(methodInfo, SecurityCheckInstantiationUtil.rolesAllowedSecurityCheck(rolesAllowed))); methodsWithSecurityAnnotation.add(methodInfo); - } else if (instance.target().kind() == AnnotationTarget.Kind.CLASS) { + } + } + + // now check for instances on classes and methods that aren't already annotated with a security annotation + for (AnnotationInstance instance : index.getIndex().getAnnotations(DotNames.SPRING_SECURED)) { + if (instance.value() == null) { + continue; + } + String[] rolesAllowed = instance.value().asStringArray(); + + if (instance.target().kind() == AnnotationTarget.Kind.CLASS) { ClassInfo classInfo = instance.target().asClass(); checksStandardSecurity(instance, classInfo); for (MethodInfo methodInfo : classInfo.methods()) { @@ -64,6 +114,9 @@ void addSpringSecuredSecurityCheck(ApplicationIndexBuildItem index, continue; } checksStandardSecurity(instance, methodInfo); + if (hasSpringSecurityAnnotationOtherThan(methodInfo, DotNames.SPRING_SECURED)) { + continue; + } if (!methodsWithSecurityAnnotation.contains(methodInfo)) { additionalSecurityCheckBuildItems.produce(new AdditionalSecurityCheckBuildItem(methodInfo, SecurityCheckInstantiationUtil.rolesAllowedSecurityCheck(rolesAllowed))); @@ -73,6 +126,18 @@ void addSpringSecuredSecurityCheck(ApplicationIndexBuildItem index, } } + private boolean hasSpringSecurityAnnotationOtherThan(MethodInfo methodInfo, DotName excluded) { + Set toCheck = new HashSet<>(DotNames.SUPPORTED_SPRING_SECURITY_ANNOTATIONS); + toCheck.remove(excluded); + List annotations = methodInfo.annotations(); + for (AnnotationInstance instance : annotations) { + if (toCheck.contains(instance.name())) { + return true; + } + } + return false; + } + //Validates that there is no @Secured with the standard security annotations at class level private void checksStandardSecurity(AnnotationInstance instance, ClassInfo classInfo) { if (hasStandardSecurityAnnotation(classInfo)) { @@ -107,4 +172,343 @@ private boolean isPublicNonStaticNonConstructor(MethodInfo methodInfo) { return Modifier.isPublic(methodInfo.flags()) && !Modifier.isStatic(methodInfo.flags()) && !"".equals(methodInfo.name()); } + + @BuildStep + void locatePreAuthorizedInstances( + ApplicationIndexBuildItem index, + BuildProducer springPreAuthorizeAnnotatedMethods, + BuildProducer annotationsTransformer) { + Map result = new HashMap<>(); + + // first first go through the list of annotated methods + for (AnnotationInstance instance : index.getIndex().getAnnotations(DotNames.SPRING_PRE_AUTHORIZE)) { + if (instance.value() == null) { + continue; + } + if (instance.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + MethodInfo methodInfo = instance.target().asMethod(); + checksStandardSecurity(instance, methodInfo); + result.put(methodInfo, instance); + } + + Set metaAnnotations = new HashSet<>(); + + // now check for instances on classes and methods that aren't already annotated with a security annotation + for (AnnotationInstance instance : index.getIndex().getAnnotations(DotNames.SPRING_PRE_AUTHORIZE)) { + if (instance.value() == null) { + continue; + } + if (instance.target().kind() != AnnotationTarget.Kind.CLASS) { + continue; + } + ClassInfo classInfo = instance.target().asClass(); + if (isAnnotation(classInfo.flags())) { + // if the instance is an annotation we need to record it and handle it later + metaAnnotations.add(classInfo); + continue; + } + checksStandardSecurity(instance, classInfo); + for (MethodInfo methodInfo : classInfo.methods()) { + if (!isPublicNonStaticNonConstructor(methodInfo)) { + continue; + } + checksStandardSecurity(instance, methodInfo); + if (hasSpringSecurityAnnotationOtherThan(methodInfo, DotNames.SPRING_PRE_AUTHORIZE)) { + continue; + } + if (!result.containsKey(methodInfo)) { + result.put(methodInfo, instance); + } + } + } + + /* + * For each meta-annotation add the value of @PreAuthorize to the method tracking map + */ + Set classesInNeedOfAnnotationTransformation = new HashSet<>(); + for (ClassInfo metaAnnotation : metaAnnotations) { + for (AnnotationInstance instance : index.getIndex().getAnnotations(metaAnnotation.name())) { + if (instance.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + MethodInfo methodInfo = instance.target().asMethod(); + checksStandardSecurity(instance, methodInfo); + result.put(methodInfo, metaAnnotation.classAnnotation(DotNames.SPRING_PRE_AUTHORIZE)); + classesInNeedOfAnnotationTransformation.add(methodInfo.declaringClass()); + } + } + + springPreAuthorizeAnnotatedMethods.produce(new SpringPreAuthorizeAnnotatedMethodBuildItem(result)); + + /* + * Go through each of the classes that have an instance of a meta-annotation and add the @PreAuthorize + * annotation to the class. + * This is done in order so Arc will ensure that an interceptor will be introduced + */ + annotationsTransformer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + @Override + public boolean appliesTo(AnnotationTarget.Kind kind) { + return kind == AnnotationTarget.Kind.CLASS; + } + + @Override + public void transform(TransformationContext transformationContext) { + ClassInfo classInfo = transformationContext.getTarget().asClass(); + if (classesInNeedOfAnnotationTransformation.contains(classInfo)) { + transformationContext.transform() + .add(DotNames.SPRING_PRE_AUTHORIZE, AnnotationValue.createStringValue("value", "")).done(); + } + } + })); + } + + /** + * The generation needs to be done in it's own build step otherwise we can end up with build cycle errors + */ + @BuildStep + void generateNecessarySupportClasses(ApplicationIndexBuildItem index, + SpringPreAuthorizeAnnotatedMethodBuildItem springPreAuthorizeAnnotatedMethods, + BuildProducer generatedBeans, + BuildProducer unremovableBeans) { + + Map> stringPropertiesInNeedOfGeneratedAccessors = new HashMap<>(); + + for (Map.Entry entry : springPreAuthorizeAnnotatedMethods.getMethodToInstanceMap() + .entrySet()) { + AnnotationInstance instance = entry.getValue(); + MethodInfo methodInfo = entry.getKey(); + String value = instance.value().asString().trim(); + + String[] parts = { value }; + if (value.toLowerCase().contains(" and ")) { + parts = value.split("(?i) and "); + } else if (value.toLowerCase().contains(" or ")) { + parts = value.split("(?i) or "); + } + + /* + * this is essentially the same loop as in addSpringPreAuthorizeSecurityCheck but only deals with cases where + * where beans need to be generated + */ + for (String part : parts) { + part = part.trim(); + if (part.matches(PARAMETER_EQ_PRINCIPAL_USERNAME_REGEX)) { + Matcher matcher = PARAMETER_EQ_PRINCIPAL_USERNAME_PATTERN.matcher(part); + if (!matcher.find()) { // should never happen + throw SpringSecurityProcessorUtil.createGenericMalformedException(methodInfo, part); + } + + ParameterNameAndIndex parameterNameAndIndex = getParameterNameAndIndexForPrincipalUserNameReference( + methodInfo, + matcher, part); + + String propertyName = matcher.group(PARAMETER_EQ_PRINCIPAL_USERNAME_PROPERTY_ACCESSOR_MATCHER_GROUP); + if (propertyName != null) { + /* + * In this we need to call a getter method on the parameter. In order to do that we need to generate + * an accessor for that method (which is ensured to return type String since that is the type of the + * username). + */ + StringPropertyAccessorData stringPropertyAccessorData = StringPropertyAccessorData.from( + methodInfo, parameterNameAndIndex.getIndex(), + propertyName, index.getIndex(), + part); + + Set fields = stringPropertiesInNeedOfGeneratedAccessors.getOrDefault( + stringPropertyAccessorData.getMatchingParameterClassInfo(), + new HashSet<>()); + fields.add(stringPropertyAccessorData.getMatchingParameterFieldInfo()); + stringPropertiesInNeedOfGeneratedAccessors.put( + stringPropertyAccessorData.getMatchingParameterClassInfo(), + fields); + } + } + } + } + + // actually generate the accessor classes as beans + if (!stringPropertiesInNeedOfGeneratedAccessors.isEmpty()) { + GeneratedBeanGizmoAdaptor classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); + Set generatedBeanClassNames = new HashSet<>(stringPropertiesInNeedOfGeneratedAccessors.keySet().size()); + for (Map.Entry> entry : stringPropertiesInNeedOfGeneratedAccessors.entrySet()) { + String generateClassName = StringPropertyAccessorGenerator.generate(entry.getKey(), entry.getValue(), + classOutput); + generatedBeanClassNames.add(generateClassName); + } + unremovableBeans.produce((new UnremovableBeanBuildItem( + new UnremovableBeanBuildItem.BeanClassNamesExclusion(generatedBeanClassNames)))); + } + } + + @BuildStep + void addSpringPreAuthorizeSecurityCheck(ApplicationIndexBuildItem index, + SpringPreAuthorizeAnnotatedMethodBuildItem springPreAuthorizeAnnotatedMethods, + SpringBeanNameToDotNameBuildItem springBeanNames, + BuildProducer additionalSecurityChecks, + BuildProducer unremovableBeans, + BuildProducer generatedClasses) { + + Map springBeansNameToDotName = springBeanNames.getMap(); + Map springBeansNameToClassInfo = new HashMap<>(); + Set beansReferencedInPreAuthorized = new HashSet<>(); + BeanMethodInvocationGenerator beanMethodInvocationGenerator = new BeanMethodInvocationGenerator(index.getIndex(), + springBeansNameToDotName, springBeansNameToClassInfo, beansReferencedInPreAuthorized, + new GeneratedClassGizmoAdaptor(generatedClasses, true)); + + for (Map.Entry entry : springPreAuthorizeAnnotatedMethods.getMethodToInstanceMap() + .entrySet()) { + AnnotationInstance instance = entry.getValue(); + + MethodInfo methodInfo = entry.getKey(); + String value = instance.value().asString().trim(); + + /* + * TODO: this serves fine for most purposes but a full blown solution will need a proper parser + */ + + boolean containsAnd = false; + boolean containsOr = false; + String lowercaseValue = value.toLowerCase(); + if (lowercaseValue.contains(" or ")) { + containsOr = true; + } + if (lowercaseValue.contains(" and ")) { + containsAnd = true; + } + + if (containsAnd && containsOr) { + throw new IllegalStateException( + "Currently expressions containing both logical 'and' / 'or' are not supported. Offending expression is " + + value + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass()); + } + + String[] parts = { value }; + if (containsAnd) { + parts = value.split("(?i) and "); + } else if (containsOr) { + parts = value.split("(?i) or "); + } + + List> securityChecks = new ArrayList<>(parts.length); + + for (String part : parts) { + part = part.trim(); + if (part.equals("permitAll()")) { + securityChecks.add(SecurityCheckInstantiationUtil.permitAllSecurityCheck()); + } else if (part.equals("denyAll()")) { + securityChecks.add(SecurityCheckInstantiationUtil.denyAllSecurityCheck()); + } else if (part.equals("isAnonymous()")) { + securityChecks.add(PreAuthorizeSecurityCheckUtil.anonymousSecurityCheck()); + } else if (part.replaceAll("\\s", "").equals("isAuthenticated()")) { + + securityChecks.add(SecurityCheckInstantiationUtil.authenticatedSecurityCheck()); + + } else if (part.startsWith("hasRole(")) { + + String hasRoleValue = part.replace("hasRole(", "").replace(")", ""); + HasRoleValueProducer hasRoleValueProducer = HasRoleValueUtil.getHasRoleValueProducer(hasRoleValue, + methodInfo, + index.getIndex(), springBeansNameToDotName, springBeansNameToClassInfo, + beansReferencedInPreAuthorized); + securityChecks.add( + PreAuthorizeSecurityCheckUtil + .hasRoleSecurityCheck(Collections.singletonList(hasRoleValueProducer))); + + } else if (part.startsWith("hasAnyRole(")) { + + String hasRoleValues = part.replace("hasAnyRole(", "").replace(")", ""); + String[] hasRoleParts = hasRoleValues.split(","); + List hasRoleValueProducers = new ArrayList<>(hasRoleParts.length); + for (String hasRolePart : hasRoleParts) { + hasRoleValueProducers + .add(HasRoleValueUtil.getHasRoleValueProducer(hasRolePart.trim(), methodInfo, index.getIndex(), + springBeansNameToDotName, + springBeansNameToClassInfo, beansReferencedInPreAuthorized)); + } + securityChecks.add(PreAuthorizeSecurityCheckUtil.hasRoleSecurityCheck(hasRoleValueProducers)); + + } else if (part.matches(PARAMETER_EQ_PRINCIPAL_USERNAME_REGEX)) { // TODO this section needs to be improved + + Matcher matcher = PARAMETER_EQ_PRINCIPAL_USERNAME_PATTERN.matcher(part); + if (!matcher.find()) { // should never happen + throw SpringSecurityProcessorUtil.createGenericMalformedException(methodInfo, part); + } + ParameterNameAndIndex parameterNameAndIndex = getParameterNameAndIndexForPrincipalUserNameReference( + methodInfo, + matcher, part); + + String propertyName = matcher.group(PARAMETER_EQ_PRINCIPAL_USERNAME_PROPERTY_ACCESSOR_MATCHER_GROUP); + if (propertyName == null) { // this is the case where the parameter is supposed to be a string + if (!DotNames.STRING.equals(methodInfo.parameters().get(parameterNameAndIndex.getIndex()).name())) { + throw new IllegalArgumentException( + "Expression: '" + part + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' references method parameter '" + + parameterNameAndIndex.getName() + "' which is not a string"); + } + + PrincipalNameFromParameterSecurityCheck.CheckType checkType = part.contains("==") + ? PrincipalNameFromParameterSecurityCheck.CheckType.EQ + : PrincipalNameFromParameterSecurityCheck.CheckType.NEQ; + securityChecks.add(PreAuthorizeSecurityCheckUtil + .principalNameFromParameterSecurityCheck(parameterNameAndIndex.getIndex(), checkType)); + + } else { + /* + * In this the security check needs to check against a property of the method parameter. + * We use a special SecurityCheck that leverages a generated accessor class + * (see the generateStringPropertyAccessors method) + */ + StringPropertyAccessorData stringPropertyAccessorData = StringPropertyAccessorData.from( + methodInfo, parameterNameAndIndex.getIndex(), + propertyName, index.getIndex(), + part); + + securityChecks.add(PreAuthorizeSecurityCheckUtil.principalNameFromParameterObjectSecurityCheck( + parameterNameAndIndex.getIndex(), + stringPropertyAccessorData.getMatchingParameterClassInfo().name().toString(), + StringPropertyAccessorGenerator + .getAccessorClassName(stringPropertyAccessorData.getMatchingParameterClassInfo()), + stringPropertyAccessorData.getMatchingParameterFieldInfo().name())); + + } + } else if (part.matches(SpringSecurityProcessorUtil.BASIC_BEAN_METHOD_INVOCATION_REGEX)) { + String generatedClassName = beanMethodInvocationGenerator.generateSecurityCheck(part, methodInfo); + securityChecks.add(PreAuthorizeSecurityCheckUtil.beanMethodGeneratedSecurityCheck( + generatedClassName)); + } else { + throw SpringSecurityProcessorUtil.createGenericMalformedException(methodInfo, part); + } + } + + // if there is only one security check we don't need to do any delegation + if (securityChecks.size() == 1) { + additionalSecurityChecks.produce(new AdditionalSecurityCheckBuildItem(methodInfo, securityChecks.get(0))); + } else { + if (containsAnd) { + additionalSecurityChecks.produce(new AdditionalSecurityCheckBuildItem(methodInfo, + PreAuthorizeSecurityCheckUtil.generateAllDelegatingSecurityCheck(securityChecks))); + } else if (containsOr) { + additionalSecurityChecks.produce(new AdditionalSecurityCheckBuildItem(methodInfo, + PreAuthorizeSecurityCheckUtil.generateAnyDelegatingSecurityCheck(securityChecks))); + } + } + } + + if (!beansReferencedInPreAuthorized.isEmpty()) { + unremovableBeans.produce((new UnremovableBeanBuildItem( + new UnremovableBeanBuildItem.BeanClassNamesExclusion(beansReferencedInPreAuthorized)))); + } + } + + private ParameterNameAndIndex getParameterNameAndIndexForPrincipalUserNameReference(MethodInfo methodInfo, Matcher matcher, + String expression) { + String parameterName = matcher.group(PARAMETER_EQ_PRINCIPAL_USERNAME_PARAMETER_NAME_GROUP); + + return new ParameterNameAndIndex(SpringSecurityProcessorUtil.getParameterIndex(methodInfo, parameterName, expression), + parameterName); + } + } diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessorUtil.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessorUtil.java new file mode 100644 index 0000000000000..46478f9a2ab98 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/SpringSecurityProcessorUtil.java @@ -0,0 +1,64 @@ +package io.quarkus.spring.security.deployment; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; + +final class SpringSecurityProcessorUtil { + + static final String BASIC_BEAN_METHOD_INVOCATION_REGEX = "@(\\w+)\\.(\\w+)\\(.*\\)"; + static final Pattern BASIC_BEAN_METHOD_INVOCATION_PATTERN = Pattern.compile(BASIC_BEAN_METHOD_INVOCATION_REGEX); + + private SpringSecurityProcessorUtil() { + } + + static IllegalArgumentException createGenericMalformedException(MethodInfo methodInfo, String expression) { + return new IllegalArgumentException( + "Expression: '" + expression + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' is malformed"); + } + + static ClassInfo getClassInfoFromBeanName(String beanName, IndexView index, Map springBeansNameToDotName, + Map springBeansNameToClassInfo, + String expression, MethodInfo methodInfo) { + ClassInfo beanClassInfo = springBeansNameToClassInfo.get(beanName); + if (beanClassInfo == null) { + DotName beanClassDotName = springBeansNameToDotName.get(beanName); + if (beanClassDotName == null) { + throw new IllegalArgumentException("Could not find bean named '" + beanName + + "' found in expression" + expression + "' in the @PreAuthorize annotation on method " + + methodInfo.name() + " of class " + methodInfo.declaringClass() + + " in the set of the application beans"); + } + beanClassInfo = index.getClassByName(beanClassDotName); + if (beanClassInfo == null) { + throw new IllegalStateException("Unable to locate class " + beanClassDotName + " in the index"); + } + springBeansNameToClassInfo.put(beanName, beanClassInfo); + } + return beanClassInfo; + } + + static int getParameterIndex(MethodInfo methodInfo, String parameterName, String expression) { + int parametersCount = methodInfo.parameters().size(); + int matchingParameterIndex = -1; + for (int i = 0; i < parametersCount; i++) { + if (parameterName.equals(methodInfo.parameterName(i))) { + matchingParameterIndex = i; + break; + } + } + + if (matchingParameterIndex == -1) { + throw new IllegalArgumentException( + "Expression: '" + expression + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' references parameter " + parameterName + + " that the method does not declare"); + } + return matchingParameterIndex; + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorData.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorData.java new file mode 100644 index 0000000000000..c4e04f965bfcf --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorData.java @@ -0,0 +1,58 @@ +package io.quarkus.spring.security.deployment; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +class StringPropertyAccessorData { + + private final ClassInfo matchingParameterClassInfo; + private final FieldInfo matchingParameterFieldInfo; + + private StringPropertyAccessorData(ClassInfo matchingParameterClassInfo, + FieldInfo matchingParameterFieldInfo) { + this.matchingParameterClassInfo = matchingParameterClassInfo; + this.matchingParameterFieldInfo = matchingParameterFieldInfo; + } + + /** + * Called with data parsed from a Spring expression like #person.name that is places inside a Spring security annotation on + * a method + */ + static StringPropertyAccessorData from(MethodInfo methodInfo, int matchingParameterIndex, String propertyName, + IndexView index, String expression) { + Type matchingParameterType = methodInfo.parameters().get(matchingParameterIndex); + ClassInfo matchingParameterClassInfo = index.getClassByName(matchingParameterType.name()); + if (matchingParameterClassInfo == null) { + throw new IllegalArgumentException( + "Expression: '" + expression + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' references class " + + matchingParameterType.name() + " which could not be in Jandex"); + } + FieldInfo matchingParameterFieldInfo = matchingParameterClassInfo.field(propertyName); + if (matchingParameterFieldInfo == null) { + throw new IllegalArgumentException( + "Expression: '" + expression + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' references unknown property '" + + propertyName + "' of class " + matchingParameterClassInfo); + } + if (!DotNames.STRING.equals(matchingParameterFieldInfo.type().name())) { + throw new IllegalArgumentException( + "Expression: '" + expression + "' in the @PreAuthorize annotation on method '" + methodInfo.name() + + "' of class '" + methodInfo.declaringClass() + "' references property '" + + propertyName + "' which is not a string"); + } + + return new StringPropertyAccessorData(matchingParameterClassInfo, matchingParameterFieldInfo); + } + + public ClassInfo getMatchingParameterClassInfo() { + return matchingParameterClassInfo; + } + + public FieldInfo getMatchingParameterFieldInfo() { + return matchingParameterFieldInfo; + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorGenerator.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorGenerator.java new file mode 100644 index 0000000000000..ba02aac68e4de --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/StringPropertyAccessorGenerator.java @@ -0,0 +1,87 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.gizmo.MethodDescriptor.ofMethod; + +import java.util.Set; + +import javax.inject.Singleton; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; + +import io.quarkus.deployment.bean.JavaBeanUtil; +import io.quarkus.deployment.util.HashUtil; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.spring.security.runtime.interceptor.accessor.StringPropertyAccessor; + +final class StringPropertyAccessorGenerator { + + private StringPropertyAccessorGenerator() { + } + + static String getAccessorClassName(ClassInfo classInfo) { + return "io.quarkus.spring.security.accessor." + classInfo.name().withoutPackagePrefix() + "_" + + HashUtil.sha1(classInfo.name().toString()) + "_Accessor"; + } + + /** + * Generates a class like the following: + * + *

+     * @Singleton
+     * public class Person_1234_Accessor implements StringPropertyAccessor {
+     *
+     *     public String access(Object obj, String property) {
+     *         Person person = (Person) obj;
+     *         if ("name".equals(property)) {
+     *             return person.getName();
+     *         }
+     *         if ("lastName".equals(property)) {
+     *             return person.getLastName();
+     *         }
+     *         throw new IllegalArgumentException("Unknown property '" + name + "'");
+     *     }
+     * }
+     * 
+ * + * This generated class is used by + * {@link io.quarkus.spring.security.runtime.interceptor.check.PrincipalNameFromParameterObjectSecurityCheck} + * to access fields of the object referenced by security expressions + */ + static String generate(ClassInfo classInfo, Set properties, ClassOutput classOutput) { + String generatedClassName = getAccessorClassName(classInfo); + try (ClassCreator cc = ClassCreator.builder() + .classOutput(classOutput).className(generatedClassName) + .interfaces(StringPropertyAccessor.class) + .build()) { + + cc.addAnnotation(Singleton.class); + + try (MethodCreator access = cc.getMethodCreator("access", String.class.getName(), Object.class, String.class)) { + ResultHandle objectParam = access.getMethodParam(0); + ResultHandle propertyParam = access.getMethodParam(1); + ResultHandle castedObjectParam = access.checkCast(objectParam, classInfo.name().toString()); + for (FieldInfo fieldInfo : properties) { + ResultHandle propertyName = access.load(fieldInfo.name()); + ResultHandle propertyNameEquals = access.invokeVirtualMethod( + ofMethod(Object.class, "equals", boolean.class, Object.class), + propertyName, propertyParam); + BranchResult propertyNameEqualsBranch = access.ifNonZero(propertyNameEquals); + BytecodeCreator propertyNameEqualsTrue = propertyNameEqualsBranch.trueBranch(); + ResultHandle result = propertyNameEqualsTrue.invokeVirtualMethod( + ofMethod(classInfo.name().toString(), "get" + JavaBeanUtil.capitalize(fieldInfo.name()), + String.class.getName()), + castedObjectParam); + propertyNameEqualsTrue.returnValue(result); + } + access.throwException(IllegalArgumentException.class, "Property unknown"); + } + } + return generatedClassName; + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/FromBeanHasRoleValueProducer.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/FromBeanHasRoleValueProducer.java new file mode 100644 index 0000000000000..a9a3ddedaf9c4 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/FromBeanHasRoleValueProducer.java @@ -0,0 +1,35 @@ +package io.quarkus.spring.security.deployment.roles; + +import static io.quarkus.gizmo.MethodDescriptor.ofMethod; + +import org.jboss.jandex.FieldInfo; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.ResultHandle; + +public class FromBeanHasRoleValueProducer implements HasRoleValueProducer { + + private final String beanName; + private final FieldInfo fieldInfo; + + public FromBeanHasRoleValueProducer(String beanName, FieldInfo fieldInfo) { + this.beanName = beanName; + this.fieldInfo = fieldInfo; + } + + @Override + public ResultHandle apply(BytecodeCreator creator) { + ResultHandle arcContainer = creator + .invokeStaticMethod(ofMethod(Arc.class, "container", ArcContainer.class)); + ResultHandle instanceHandle = creator.invokeInterfaceMethod( + ofMethod(ArcContainer.class, "instance", InstanceHandle.class, String.class), + arcContainer, creator.load(beanName)); + ResultHandle bean = creator + .invokeInterfaceMethod(ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle); + return creator.readInstanceField(FieldDescriptor.of(fieldInfo), bean); + } +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/HasRoleValueProducer.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/HasRoleValueProducer.java new file mode 100644 index 0000000000000..195a9df562476 --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/HasRoleValueProducer.java @@ -0,0 +1,9 @@ +package io.quarkus.spring.security.deployment.roles; + +import java.util.function.Function; + +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ResultHandle; + +public interface HasRoleValueProducer extends Function { +} diff --git a/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/StaticHasRoleValueProducer.java b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/StaticHasRoleValueProducer.java new file mode 100644 index 0000000000000..97c9907b18d1c --- /dev/null +++ b/extensions/spring-security/deployment/src/main/java/io/quarkus/spring/security/deployment/roles/StaticHasRoleValueProducer.java @@ -0,0 +1,18 @@ +package io.quarkus.spring.security.deployment.roles; + +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ResultHandle; + +public class StaticHasRoleValueProducer implements HasRoleValueProducer { + + private final String value; + + public StaticHasRoleValueProducer(String value) { + this.value = value; + } + + @Override + public ResultHandle apply(BytecodeCreator creator) { + return creator.load(value); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/BeanMethodCheckTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/BeanMethodCheckTest.java new file mode 100644 index 0000000000000..ecf9bc0e54cd2 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/BeanMethodCheckTest.java @@ -0,0 +1,104 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.cdi.SecurityTestUtils; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.spring.security.deployment.springapp.BeanWithBeanMethodChecks; +import io.quarkus.spring.security.deployment.springapp.Person; +import io.quarkus.spring.security.deployment.springapp.PersonChecker; +import io.quarkus.spring.security.deployment.springapp.PersonCheckerImpl; +import io.quarkus.spring.security.deployment.springapp.PrincipalChecker; +import io.quarkus.spring.security.deployment.springapp.SomeInterface; +import io.quarkus.spring.security.deployment.springapp.SomeInterfaceImpl; +import io.quarkus.spring.security.deployment.springapp.SpringConfiguration; +import io.quarkus.test.QuarkusUnitTest; + +public class BeanMethodCheckTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses( + Person.class, + PersonChecker.class, + PersonCheckerImpl.class, + PrincipalChecker.class, + BeanWithBeanMethodChecks.class, + SomeInterface.class, + SomeInterfaceImpl.class, + SpringConfiguration.class, + IdentityMock.class, + AuthData.class, + SecurityTestUtils.class)); + + @Inject + BeanWithBeanMethodChecks beanWithBeanMethodChecks; + + @Inject + SomeInterface someInterface; + + @Test + public void testNoParamsAlwaysPasses() { + assertSuccess(() -> beanWithBeanMethodChecks.noParamsAlwaysPasses(), "noParamsAlwaysPasses", ANONYMOUS); + assertSuccess(() -> beanWithBeanMethodChecks.noParamsAlwaysPasses(), "noParamsAlwaysPasses", ADMIN); + assertSuccess(() -> beanWithBeanMethodChecks.noParamsAlwaysPasses(), "noParamsAlwaysPasses", USER); + } + + @Test + public void testNoParamsNeverPasses() { + assertFailureFor(() -> beanWithBeanMethodChecks.noParamsNeverPasses(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> beanWithBeanMethodChecks.noParamsNeverPasses(), ForbiddenException.class, USER); + assertFailureFor(() -> beanWithBeanMethodChecks.noParamsNeverPasses(), ForbiddenException.class, ADMIN); + } + + @Test + public void testWithParams() { + assertFailureFor(() -> beanWithBeanMethodChecks.withParams("other", new Person("geo")), UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> beanWithBeanMethodChecks.withParams("geo", new Person("geo")), "withParams", ANONYMOUS); + assertFailureFor(() -> beanWithBeanMethodChecks.withParams("other", new Person("geo")), ForbiddenException.class, USER); + assertSuccess(() -> beanWithBeanMethodChecks.withParams("geo", new Person("geo")), "withParams", USER); + } + + // the reason for having this test is to ensure that caching of the generated classes doesn't mess up anything + @Test + public void testAnotherWithParams() { + assertFailureFor(() -> beanWithBeanMethodChecks.withParams("other", new Person("geo")), UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> beanWithBeanMethodChecks.withParams("geo", new Person("geo")), "withParams", ANONYMOUS); + assertFailureFor(() -> beanWithBeanMethodChecks.withParams("other", new Person("geo")), ForbiddenException.class, USER); + assertSuccess(() -> beanWithBeanMethodChecks.withParams("geo", new Person("geo")), "withParams", USER); + } + + @Test + public void testWithExtraUnusedParam() { + assertFailureFor(() -> someInterface.doSomething("other", 1, new Person("geo")), UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> someInterface.doSomething("geo", 1, new Person("geo")), "doSomething", ANONYMOUS); + assertFailureFor(() -> someInterface.doSomething("other", -1, new Person("geo")), ForbiddenException.class, USER); + assertSuccess(() -> someInterface.doSomething("geo", -1, new Person("geo")), "doSomething", USER); + } + + @Test + public void testPrincipalUsername() { + assertFailureFor(() -> beanWithBeanMethodChecks.principalChecker("user"), UnauthorizedException.class, + ANONYMOUS); + assertFailureFor(() -> beanWithBeanMethodChecks.principalChecker("other"), ForbiddenException.class, USER); + assertSuccess(() -> beanWithBeanMethodChecks.principalChecker("user"), "principalChecker", USER); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/MetaAnnotationsTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/MetaAnnotationsTest.java new file mode 100644 index 0000000000000..04ce5338c4093 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/MetaAnnotationsTest.java @@ -0,0 +1,78 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.cdi.SecurityTestUtils; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.spring.security.deployment.springapp.BeanWithMetaAnnotations; +import io.quarkus.spring.security.deployment.springapp.IsUser; +import io.quarkus.spring.security.deployment.springapp.IsUserOrAdmin; +import io.quarkus.test.QuarkusUnitTest; + +public class MetaAnnotationsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses( + IsUser.class, + IsUserOrAdmin.class, + BeanWithMetaAnnotations.class, + IdentityMock.class, + AuthData.class, + SecurityTestUtils.class)); + + @Inject + BeanWithMetaAnnotations beanWithMetaAnnotations; + + @Test + public void testPreAuthorizeMetaAnnotationIsUser() { + assertFailureFor(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUser(), UnauthorizedException.class, + ANONYMOUS); + assertFailureFor(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUser(), ForbiddenException.class, ADMIN); + assertSuccess(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUser(), "preAuthorizeMetaAnnotationIsUser", + USER); + } + + @Test + public void testPreAuthorizeMetaAnnotationIsUserOrAdmin() { + assertFailureFor(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUserOrAdmin(), UnauthorizedException.class, + ANONYMOUS); + assertSuccess(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUserOrAdmin(), + "preAuthorizeMetaAnnotationIsUserOrAdmin", + USER); + assertSuccess(() -> beanWithMetaAnnotations.preAuthorizeMetaAnnotationIsUserOrAdmin(), + "preAuthorizeMetaAnnotationIsUserOrAdmin", + ADMIN); + } + + @Test + public void testNotSecured() { + assertSuccess(() -> beanWithMetaAnnotations.notSecured(), "notSecured", ANONYMOUS); + assertSuccess(() -> beanWithMetaAnnotations.notSecured(), "notSecured", USER); + assertSuccess(() -> beanWithMetaAnnotations.notSecured(), "notSecured", ADMIN); + } + + @Test + public void testIsSecuredWithSecuredAnnotation() { + assertFailureFor(() -> beanWithMetaAnnotations.isSecuredWithSecuredAnnotation(), UnauthorizedException.class, + ANONYMOUS); + assertFailureFor(() -> beanWithMetaAnnotations.isSecuredWithSecuredAnnotation(), ForbiddenException.class, ADMIN); + assertSuccess(() -> beanWithMetaAnnotations.isSecuredWithSecuredAnnotation(), "isSecuredWithSecuredAnnotation", + USER); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeClassAnnotatedTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeClassAnnotatedTest.java new file mode 100644 index 0000000000000..d8fd14b1e039a --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeClassAnnotatedTest.java @@ -0,0 +1,87 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.cdi.SecurityTestUtils; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.spring.security.deployment.springapp.ComponentWithClassAnnotation; +import io.quarkus.spring.security.deployment.springapp.DenyAllOnClass; +import io.quarkus.spring.security.deployment.springapp.Person; +import io.quarkus.spring.security.deployment.springapp.Roles; +import io.quarkus.test.QuarkusUnitTest; + +public class SpringPreAuthorizeClassAnnotatedTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Roles.class, + Person.class, + ComponentWithClassAnnotation.class, + DenyAllOnClass.class, + IdentityMock.class, + AuthData.class, + SecurityTestUtils.class)); + + @Inject + private ComponentWithClassAnnotation springComponent; + + @Inject + private DenyAllOnClass denyAllOnClass; + + @Test + public void testUnannotated() { + assertFailureFor(() -> springComponent.unannotated(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.unannotated(), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.unannotated(), "unannotated", USER); + } + + @Test + public void testRestrictedOnMethod() { + assertFailureFor(() -> springComponent.restrictedOnMethod(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.restrictedOnMethod(), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.restrictedOnMethod(), "restrictedOnMethod", ADMIN); + } + + @Test + public void testSecuredWithSecuredAnnotation() { + assertFailureFor(() -> springComponent.securedWithSecuredAnnotation(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.securedWithSecuredAnnotation(), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.securedWithSecuredAnnotation(), "securedWithSecuredAnnotation", USER); + } + + @Test + public void testNoAnnotation() { + assertFailureFor(() -> denyAllOnClass.noAnnotation(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> denyAllOnClass.noAnnotation(), ForbiddenException.class, ADMIN); + assertFailureFor(() -> denyAllOnClass.noAnnotation(), ForbiddenException.class, USER); + } + + @Test + public void testPermitAll() { + assertSuccess(() -> denyAllOnClass.permitAll(), "permitAll", ANONYMOUS); + assertSuccess(() -> denyAllOnClass.permitAll(), "permitAll", ADMIN); + assertSuccess(() -> denyAllOnClass.permitAll(), "permitAll", USER); + } + + @Test + public void testAnnotatedWithSecured() { + assertFailureFor(() -> denyAllOnClass.annotatedWithSecured(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> denyAllOnClass.annotatedWithSecured(), ForbiddenException.class, USER); + assertSuccess(() -> denyAllOnClass.annotatedWithSecured(), "annotatedWithSecured", ADMIN); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java new file mode 100644 index 0000000000000..d5fe70c8f1f4c --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeTest.java @@ -0,0 +1,134 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.cdi.SecurityTestUtils; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.spring.security.deployment.springapp.Person; +import io.quarkus.spring.security.deployment.springapp.Roles; +import io.quarkus.spring.security.deployment.springapp.SpringComponent; +import io.quarkus.spring.security.deployment.springapp.SpringService; +import io.quarkus.test.QuarkusUnitTest; + +public class SpringPreAuthorizeTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Roles.class, + Person.class, + SpringComponent.class, + SpringService.class, + IdentityMock.class, + AuthData.class, + SecurityTestUtils.class)); + + @Inject + private SpringComponent springComponent; + + @Inject + private SpringService springService; + + @Test + public void testAccessibleForAdminOnly() { + assertFailureFor(() -> springComponent.accessibleForAdminOnly(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.accessibleForAdminOnly(), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.accessibleForAdminOnly(), "accessibleForAdminOnly", ADMIN); + } + + @Test + public void testAccessibleForUserOnly() { + assertFailureFor(() -> springComponent.accessibleForUserOnly(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.accessibleForUserOnly(), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.accessibleForUserOnly(), "accessibleForUserOnly", USER); + } + + @Test + public void testAccessibleForUserOnlyString() { + assertFailureFor(() -> springComponent.accessibleForUserOnlyString(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.accessibleForUserOnlyString(), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.accessibleForUserOnlyString(), "accessibleForUserOnlyString", USER); + } + + @Test + public void testAccessibleForUserAndAdmin() { + assertFailureFor(() -> springService.accessibleForUserAndAdmin(), UnauthorizedException.class, ANONYMOUS); + assertSuccess(() -> springService.accessibleForUserAndAdmin(), "accessibleForUserAndAdmin", USER); + assertSuccess(() -> springService.accessibleForUserAndAdmin(), "accessibleForUserAndAdmin", ADMIN); + } + + @Test + public void testAccessibleForUserAndAdminMixedTypes() { + assertFailureFor(() -> springService.accessibleForUserAndAdminMixedTypes(), UnauthorizedException.class, ANONYMOUS); + assertSuccess(() -> springService.accessibleForUserAndAdminMixedTypes(), "accessibleForUserAndAdminMixedTypes", USER); + assertSuccess(() -> springService.accessibleForUserAndAdminMixedTypes(), "accessibleForUserAndAdminMixedTypes", ADMIN); + } + + @Test + public void testAccessibleByAuthenticatedUsers() { + assertFailureFor(() -> springService.accessibleByAuthenticatedUsers(), UnauthorizedException.class, ANONYMOUS); + assertSuccess(() -> springService.accessibleByAuthenticatedUsers(), "authenticated", USER); + assertSuccess(() -> springService.accessibleByAuthenticatedUsers(), "authenticated", ADMIN); + } + + @Test + public void testAccessibleByAnonymous() { + assertSuccess(() -> springService.accessibleByAnonymousUser(), "anonymous", ANONYMOUS); + assertFailureFor(() -> springService.accessibleByAnonymousUser(), ForbiddenException.class, USER); + assertFailureFor(() -> springService.accessibleByAnonymousUser(), ForbiddenException.class, ADMIN); + } + + @Test + public void testPrincipalNameIs() { + assertFailureFor(() -> springComponent.principalNameIs(null, "whatever", null), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.principalNameIs(null, "whatever", null), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.principalNameIs(null, "user", null), "user", USER); + assertFailureFor(() -> springComponent.principalNameIs(null, "whatever", null), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.principalNameIs(null, "admin", null), "admin", ADMIN); + } + + @Test + public void testPrincipalNameIsNot() { + assertFailureFor(() -> springComponent.principalNameIsNot("whatever"), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.principalNameIsNot("user"), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.principalNameIsNot("whatever"), "whatever", USER); + assertFailureFor(() -> springComponent.principalNameIsNot("admin"), ForbiddenException.class, ADMIN); + assertSuccess(() -> springComponent.principalNameIsNot("whatever"), "whatever", ADMIN); + } + + @Test + public void testPrincipalNameFromObject() { + assertFailureFor(() -> springComponent.principalNameFromObject(new Person("whatever")), UnauthorizedException.class, + ANONYMOUS); + assertFailureFor(() -> springComponent.principalNameFromObject(new Person("whatever")), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.principalNameFromObject(new Person("user")), "user", USER); + } + + @Test + public void testNotSecured() { + assertSuccess(() -> springComponent.notSecured(), "notSecured", ANONYMOUS); + assertSuccess(() -> springComponent.notSecured(), "notSecured", USER); + assertSuccess(() -> springComponent.notSecured(), "notSecured", ADMIN); + } + + @Test + public void testSecuredWithSecuredAnnotation() { + assertFailureFor(() -> springComponent.securedWithSecuredAnnotation(), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> springComponent.securedWithSecuredAnnotation(), ForbiddenException.class, USER); + assertSuccess(() -> springComponent.securedWithSecuredAnnotation(), "securedWithSecuredAnnotation", ADMIN); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeWithExpressionsTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeWithExpressionsTest.java new file mode 100644 index 0000000000000..364359dcdac3a --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/SpringPreAuthorizeWithExpressionsTest.java @@ -0,0 +1,58 @@ +package io.quarkus.spring.security.deployment; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.cdi.SecurityTestUtils; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.spring.security.deployment.springapp.BeanWithAndOrExpressions; +import io.quarkus.test.QuarkusUnitTest; + +public class SpringPreAuthorizeWithExpressionsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses( + BeanWithAndOrExpressions.class, + IdentityMock.class, + AuthData.class, + SecurityTestUtils.class)); + + @Inject + private BeanWithAndOrExpressions bean; + + @Test + public void testAllowedForUser() { + assertFailureFor(() -> bean.allowedForUser("user"), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> bean.allowedForUser("user"), ForbiddenException.class, ADMIN); + assertSuccess(() -> bean.allowedForUser("user"), "allowedForUser", USER); + } + + @Test + public void testAllowedForUserOrAdmin() { + assertFailureFor(() -> bean.allowedForUserOrAdmin(), UnauthorizedException.class, ANONYMOUS); + assertSuccess(() -> bean.allowedForUserOrAdmin(), "allowedForUserOrAdmin", USER); + assertSuccess(() -> bean.allowedForUserOrAdmin(), "allowedForUserOrAdmin", ADMIN); + } + + @Test + public void testAllowedForAdminOrAnonymous() { + assertFailureFor(() -> bean.allowedForAdminOrAnonymous(), ForbiddenException.class, USER); + assertSuccess(() -> bean.allowedForAdminOrAnonymous(), "allowedForAdminOrAnonymous", ANONYMOUS); + assertSuccess(() -> bean.allowedForAdminOrAnonymous(), "allowedForAdminOrAnonymous", ADMIN); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithAndOrExpressions.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithAndOrExpressions.java new file mode 100644 index 0000000000000..577277f8a97cb --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithAndOrExpressions.java @@ -0,0 +1,23 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component +public class BeanWithAndOrExpressions { + + @PreAuthorize("hasAnyRole('user', 'admin') and #user == principal.username") + public String allowedForUser(String user) { + return "allowedForUser"; + } + + @PreAuthorize("hasRole('user') OR hasRole('admin')") + public String allowedForUserOrAdmin() { + return "allowedForUserOrAdmin"; + } + + @PreAuthorize("hasAnyRole('superadmin1', 'superadmin2') OR isAnonymous() OR hasRole('admin')") + public String allowedForAdminOrAnonymous() { + return "allowedForAdminOrAnonymous"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithBeanMethodChecks.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithBeanMethodChecks.java new file mode 100644 index 0000000000000..f6fd04b56f69a --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithBeanMethodChecks.java @@ -0,0 +1,33 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +@Service +public class BeanWithBeanMethodChecks { + + @PreAuthorize("@personChecker.isTrue()") + public String noParamsAlwaysPasses() { + return "noParamsAlwaysPasses"; + } + + @PreAuthorize("@personChecker.isFalse()") + public String noParamsNeverPasses() { + return "noParamsNeverPasses"; + } + + @PreAuthorize("@personChecker.check(#person, #input)") + public String withParams(String input, Person person) { + return "withParams"; + } + + @PreAuthorize("@personChecker.check(#person, #input)") + public String anotherWithParams(String input, Person person) { + return "anotherWithParams"; + } + + @PreAuthorize("@principalChecker.isSame(#input, authentication.principal.username)") + public String principalChecker(String input) { + return "principalChecker"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithMetaAnnotations.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithMetaAnnotations.java new file mode 100644 index 0000000000000..a7469e045246d --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/BeanWithMetaAnnotations.java @@ -0,0 +1,27 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Component; + +@Component +public class BeanWithMetaAnnotations { + + @IsUser + public String preAuthorizeMetaAnnotationIsUser() { + return "preAuthorizeMetaAnnotationIsUser"; + } + + @IsUserOrAdmin + public String preAuthorizeMetaAnnotationIsUserOrAdmin() { + return "preAuthorizeMetaAnnotationIsUserOrAdmin"; + } + + public String notSecured() { + return "notSecured"; + } + + @Secured("user") + public String isSecuredWithSecuredAnnotation() { + return "isSecuredWithSecuredAnnotation"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/ComponentWithClassAnnotation.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/ComponentWithClassAnnotation.java new file mode 100644 index 0000000000000..aef329432cb0a --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/ComponentWithClassAnnotation.java @@ -0,0 +1,24 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component +@PreAuthorize("hasRole('user')") +public class ComponentWithClassAnnotation { + + public String unannotated() { + return "unannotated"; + } + + @PreAuthorize("hasRole('admin')") + public String restrictedOnMethod() { + return "restrictedOnMethod"; + } + + @Secured("user") + public String securedWithSecuredAnnotation() { + return "securedWithSecuredAnnotation"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/DenyAllOnClass.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/DenyAllOnClass.java new file mode 100644 index 0000000000000..bc509874e1af8 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/DenyAllOnClass.java @@ -0,0 +1,24 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component +@PreAuthorize("denyAll()") +public class DenyAllOnClass { + + public String noAnnotation() { + return "noAnnotation"; + } + + @PreAuthorize("permitAll()") + public String permitAll() { + return "permitAll"; + } + + @Secured("admin") + public String annotatedWithSecured() { + return "annotatedWithSecured"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUser.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUser.java new file mode 100644 index 0000000000000..a6d6d9e90ba59 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUser.java @@ -0,0 +1,14 @@ +package io.quarkus.spring.security.deployment.springapp; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('user')") +public @interface IsUser { +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUserOrAdmin.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUserOrAdmin.java new file mode 100644 index 0000000000000..b2648f33fbd37 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/IsUserOrAdmin.java @@ -0,0 +1,14 @@ +package io.quarkus.spring.security.deployment.springapp; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.security.access.prepost.PreAuthorize; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('user') or hasAnyRole('admin', 'dummy')") +public @interface IsUserOrAdmin { +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Person.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Person.java new file mode 100644 index 0000000000000..c0d9a8537d6f8 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Person.java @@ -0,0 +1,14 @@ +package io.quarkus.spring.security.deployment.springapp; + +public class Person { + + private final String name; + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonChecker.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonChecker.java new file mode 100644 index 0000000000000..d848414fb9bdd --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonChecker.java @@ -0,0 +1,10 @@ +package io.quarkus.spring.security.deployment.springapp; + +public interface PersonChecker { + + boolean check(Person person, String input); + + boolean isTrue(); + + boolean isFalse(); +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonCheckerImpl.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonCheckerImpl.java new file mode 100644 index 0000000000000..775237debaf5f --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PersonCheckerImpl.java @@ -0,0 +1,19 @@ +package io.quarkus.spring.security.deployment.springapp; + +public class PersonCheckerImpl implements PersonChecker { + + @Override + public boolean check(Person person, String input) { + return person.getName().equals(input); + } + + @Override + public boolean isTrue() { + return true; + } + + @Override + public boolean isFalse() { + return false; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PrincipalChecker.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PrincipalChecker.java new file mode 100644 index 0000000000000..ceae69316d0e7 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/PrincipalChecker.java @@ -0,0 +1,11 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.stereotype.Component; + +@Component +public class PrincipalChecker { + + public boolean isSame(String input, String username) { + return input.equals(username); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Roles.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Roles.java new file mode 100644 index 0000000000000..533bafff6af78 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/Roles.java @@ -0,0 +1,10 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.stereotype.Component; + +@Component +public class Roles { + + public final String ADMIN = "admin"; + public final String USER = "user"; +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterface.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterface.java new file mode 100644 index 0000000000000..086ffde135de3 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterface.java @@ -0,0 +1,6 @@ +package io.quarkus.spring.security.deployment.springapp; + +public interface SomeInterface { + + String doSomething(String input, int num, Person person); +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterfaceImpl.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterfaceImpl.java new file mode 100644 index 0000000000000..106d02c82c47e --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SomeInterfaceImpl.java @@ -0,0 +1,14 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component +public class SomeInterfaceImpl implements SomeInterface { + + @Override + @PreAuthorize("@personChecker.check(#person, #input)") + public String doSomething(String input, int num, Person person) { + return "doSomething"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java new file mode 100644 index 0000000000000..0d86e8406fcf3 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringComponent.java @@ -0,0 +1,48 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +@Component +public class SpringComponent { + + @PreAuthorize("hasRole(@roles.ADMIN)") + public String accessibleForAdminOnly() { + return "accessibleForAdminOnly"; + } + + @PreAuthorize("hasRole(@roles.USER)") + public String accessibleForUserOnly() { + return "accessibleForUserOnly"; + } + + @PreAuthorize("hasRole('user')") + public String accessibleForUserOnlyString() { + return "accessibleForUserOnlyString"; + } + + @PreAuthorize("#username == authentication.principal.username") + public String principalNameIs(Object something, String username, Object somethingElse) { + return username; + } + + @PreAuthorize("#name != authentication.principal.username") + public String principalNameIsNot(String name) { + return name; + } + + @PreAuthorize("#person.name == authentication.principal.username") + public String principalNameFromObject(Person person) { + return person.getName(); + } + + public String notSecured() { + return "notSecured"; + } + + @Secured("admin") + public String securedWithSecuredAnnotation() { + return "securedWithSecuredAnnotation"; + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringConfiguration.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringConfiguration.java new file mode 100644 index 0000000000000..c7f0e2d0302b6 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringConfiguration.java @@ -0,0 +1,13 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringConfiguration { + + @Bean + public PersonChecker personChecker() { + return new PersonCheckerImpl(); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringService.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringService.java new file mode 100644 index 0000000000000..7de38bfeb5dd2 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringService.java @@ -0,0 +1,28 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +@Service +public class SpringService { + + @PreAuthorize("hasAnyRole(@roles.USER, @roles.ADMIN)") + public String accessibleForUserAndAdmin() { + return "accessibleForUserAndAdmin"; + } + + @PreAuthorize("hasAnyRole(@roles.USER, 'admin')") + public String accessibleForUserAndAdminMixedTypes() { + return "accessibleForUserAndAdminMixedTypes"; + } + + @PreAuthorize("isAuthenticated()") + public String accessibleByAuthenticatedUsers() { + return "authenticated"; + } + + @PreAuthorize("isAnonymous()") + public String accessibleByAnonymousUser() { + return "anonymous"; + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringPreauthorizeInterceptor.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringPreauthorizeInterceptor.java new file mode 100644 index 0000000000000..c907db9e6eb46 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/SpringPreauthorizeInterceptor.java @@ -0,0 +1,25 @@ +package io.quarkus.spring.security.runtime.interceptor; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.springframework.security.access.prepost.PreAuthorize; + +import io.quarkus.security.runtime.interceptor.SecurityHandler; + +@Interceptor +@PreAuthorize("") +@Priority(Interceptor.Priority.LIBRARY_BEFORE) +public class SpringPreauthorizeInterceptor { + + @Inject + SecurityHandler handler; + + @AroundInvoke + public Object intercept(InvocationContext ic) throws Exception { + return handler.handle(ic); + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/accessor/StringPropertyAccessor.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/accessor/StringPropertyAccessor.java new file mode 100644 index 0000000000000..6852692899987 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/accessor/StringPropertyAccessor.java @@ -0,0 +1,9 @@ +package io.quarkus.spring.security.runtime.interceptor.accessor; + +/** + * Implementation of this interface are generated for each object that is encountered in a security expression + */ +public interface StringPropertyAccessor { + + String access(Object object, String property); +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java new file mode 100644 index 0000000000000..0cab79d07c943 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AbstractBeanMethodSecurityCheck.java @@ -0,0 +1,30 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; + +/** + * Implementations of this class are generated for expressions in @PreAuthorize that + * invoke a method of a bean + */ +public abstract class AbstractBeanMethodSecurityCheck implements SecurityCheck { + + protected abstract boolean check(SecurityIdentity identity, Object[] parameters); + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + if (check(identity, parameters)) { + return; + } + if (identity.isAnonymous()) { + throw new UnauthorizedException(); + } else { + throw new ForbiddenException(); + } + } + +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java new file mode 100644 index 0000000000000..f3143449ca46e --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AllDelegatingSecurityCheck.java @@ -0,0 +1,26 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; +import java.util.List; + +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; + +/** + * A {@link SecurityCheck} where all delegates must pass + */ +public class AllDelegatingSecurityCheck implements SecurityCheck { + + private final List securityChecks; + + public AllDelegatingSecurityCheck(List securityChecks) { + this.securityChecks = securityChecks; + } + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + for (SecurityCheck securityCheck : securityChecks) { + securityCheck.apply(identity, method, parameters); + } + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java new file mode 100644 index 0000000000000..b6dced359c394 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnonymousCheck.java @@ -0,0 +1,22 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; + +public class AnonymousCheck implements SecurityCheck { + + public static final AnonymousCheck INSTANCE = new AnonymousCheck(); + + private AnonymousCheck() { + } + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + if (!identity.isAnonymous()) { + throw new ForbiddenException(); + } + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java new file mode 100644 index 0000000000000..138c4c7cda8a8 --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java @@ -0,0 +1,37 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; +import java.util.List; + +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; + +/** + * A {@link SecurityCheck} where if any of the delagates passes the security check then + * the delegate passes as well + */ +public class AnyDelegatingSecurityCheck implements SecurityCheck { + + private final List securityChecks; + + public AnyDelegatingSecurityCheck(List securityChecks) { + this.securityChecks = securityChecks; + } + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + SecurityException thrownException = null; + for (int i = 0; i < securityChecks.size(); i++) { + try { + securityChecks.get(i).apply(identity, method, parameters); + // no exception was thrown so we can just return + return; + } catch (SecurityException e) { + thrownException = e; + } + } + if (thrownException != null) { + throw thrownException; + } + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java new file mode 100644 index 0000000000000..848be36fc1c1b --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterObjectSecurityCheck.java @@ -0,0 +1,72 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; + +import io.quarkus.arc.Arc; +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; +import io.quarkus.spring.security.runtime.interceptor.accessor.StringPropertyAccessor; + +/** + * Instances of this classes are created in order to check if the value of property of method parameter + * inside a Spring Security expression matches the principal name + * + * Access to the property of the object is performed by delegating to a purpose generated + * accessor + */ +public class PrincipalNameFromParameterObjectSecurityCheck implements SecurityCheck { + + private final int index; + private final Class expectedParameterClass; + private final Class stringPropertyAccessorClass; + private final String propertyName; + + private PrincipalNameFromParameterObjectSecurityCheck(int index, String expectedParameterClass, + String stringPropertyAccessorClass, String propertyName) throws ClassNotFoundException { + this.index = index; + this.expectedParameterClass = Class.forName(expectedParameterClass, false, + Thread.currentThread().getContextClassLoader()); + this.stringPropertyAccessorClass = (Class) Class.forName(stringPropertyAccessorClass, + false, Thread.currentThread().getContextClassLoader()); + this.propertyName = propertyName; + } + + public static PrincipalNameFromParameterObjectSecurityCheck of(int index, String expectedParameterClass, + String stringPropertyAccessorClass, String propertyName) throws ClassNotFoundException { + return new PrincipalNameFromParameterObjectSecurityCheck(index, expectedParameterClass, stringPropertyAccessorClass, + propertyName); + } + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + if (index > parameters.length - 1) { + throw genericNotApplicableException(method); + } + Object parameterValue = parameters[index]; + if (!expectedParameterClass.equals(parameterValue.getClass())) { + throw genericNotApplicableException(method); + } + + String parameterValueStr = getStringValue(parameterValue); + + if (identity.isAnonymous()) { + throw new UnauthorizedException(); + } + + String name = identity.getPrincipal().getName(); + if (!name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } + } + + private String getStringValue(Object parameterValue) { + return Arc.container().instance(stringPropertyAccessorClass).get().access(parameterValue, propertyName); + } + + private IllegalStateException genericNotApplicableException(Method method) { + return new IllegalStateException( + "PrincipalNameFromParameterObjectSecurityCheck with index " + index + " cannot be applied to " + method); + } +} diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java new file mode 100644 index 0000000000000..e144cdf3759cc --- /dev/null +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/PrincipalNameFromParameterSecurityCheck.java @@ -0,0 +1,68 @@ +package io.quarkus.spring.security.runtime.interceptor.check; + +import java.lang.reflect.Method; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.interceptor.check.SecurityCheck; + +/** + * Instances of this classes are created in order to check if a method parameter + * inside a Spring Security expression matches the principal name + * + * Access to the property of the object is performed by delegating to a purpose generated + * accessor + */ +public class PrincipalNameFromParameterSecurityCheck implements SecurityCheck { + + private final int index; + private final CheckType checkType; + + private PrincipalNameFromParameterSecurityCheck(int index, CheckType checkType) { + this.index = index; + this.checkType = checkType; + } + + public static PrincipalNameFromParameterSecurityCheck of(int index, CheckType checkType) { + return new PrincipalNameFromParameterSecurityCheck(index, checkType); + } + + @Override + public void apply(SecurityIdentity identity, Method method, Object[] parameters) { + if (index > parameters.length - 1) { + throw genericNotApplicableException(method); + } + Object parameterValue = parameters[index]; + if (!(parameterValue instanceof String)) { + throw genericNotApplicableException(method); + } + String parameterValueStr = (String) parameterValue; + + if (identity.isAnonymous()) { + throw new UnauthorizedException(); + } + + String name = identity.getPrincipal().getName(); + if (checkType == CheckType.EQ) { + if (!name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } + } else if (checkType == CheckType.NEQ) { + if (name.equals(parameterValueStr)) { + throw new ForbiddenException(); + } + } + + } + + private IllegalStateException genericNotApplicableException(Method method) { + return new IllegalStateException( + "PrincipalNameFromParameterSecurityCheck with index " + index + " cannot be applied to " + method); + } + + public enum CheckType { + EQ, + NEQ + } +} diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/AlwaysFalseChecker.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/AlwaysFalseChecker.java new file mode 100644 index 0000000000000..68cdf5cc3cca5 --- /dev/null +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/AlwaysFalseChecker.java @@ -0,0 +1,11 @@ +package io.quarkus.it.spring.security; + +import org.springframework.stereotype.Component; + +@Component +public class AlwaysFalseChecker { + + public boolean check(String input) { + return false; + } +} diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Roles.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Roles.java new file mode 100644 index 0000000000000..38dd3d32c6f25 --- /dev/null +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/Roles.java @@ -0,0 +1,9 @@ +package io.quarkus.it.spring.security; + +import org.springframework.stereotype.Component; + +@Component +public class Roles { + + public final String VIEWER = "viewer"; +} diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithPreAuthorize.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithPreAuthorize.java new file mode 100644 index 0000000000000..0bd8f791e1b38 --- /dev/null +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/ServiceWithPreAuthorize.java @@ -0,0 +1,18 @@ +package io.quarkus.it.spring.security; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; + +@Service("serviceWithPreAuthorize") +public class ServiceWithPreAuthorize { + + @PreAuthorize("hasRole('user') or hasRole(@roles.VIEWER)") + public String allowedForUserOrViewer() { + return "allowedForUserOrViewer"; + } + + @PreAuthorize("@alwaysFalseChecker.check(#input)") + public String withAlwaysFalseChecker(String input) { + return "withAlwaysFalseChecker"; + } +} diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java index 06915ac951c2a..118ec627e1e52 100644 --- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/security/TheController.java @@ -2,6 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -22,6 +23,10 @@ public class TheController { @Qualifier("securedService") SecuredService securedService; + @Autowired + @Qualifier("serviceWithPreAuthorize") + ServiceWithPreAuthorize serviceWithPreAuthorize; + @GetMapping("/restrictedOnClass") public String noAdditionalConstraints() { return securedService.noAdditionalConstraints(); @@ -42,4 +47,20 @@ public String accessibleMethod() { return subclass.accessibleForAll(); } + @GetMapping("/allowedForUserOrViewer") + public String allowedForUserOrViewer() { + return serviceWithPreAuthorize.allowedForUserOrViewer(); + } + + @GetMapping("/withAlwaysFalseChecker") + public String withAlwaysFalseChecker() { + return serviceWithPreAuthorize.withAlwaysFalseChecker("whatever"); + } + + @GetMapping("/preAuthorizeOnController") + @PreAuthorize("isAuthenticated()") + public String preAuthorizeOnController() { + return "preAuthorizeOnController"; + } + } diff --git a/integration-tests/spring-web/src/main/resources/test-roles.properties b/integration-tests/spring-web/src/main/resources/test-roles.properties index aa7633ff35ee5..ef9322e6b0ef1 100644 --- a/integration-tests/spring-web/src/main/resources/test-roles.properties +++ b/integration-tests/spring-web/src/main/resources/test-roles.properties @@ -1,2 +1,4 @@ scott=admin,user -stuart=user \ No newline at end of file +stuart=user +george=viewer +aurea=admin \ No newline at end of file diff --git a/integration-tests/spring-web/src/main/resources/test-users.properties b/integration-tests/spring-web/src/main/resources/test-users.properties index 63c9d4c068cab..8836a0cb7fc18 100644 --- a/integration-tests/spring-web/src/main/resources/test-users.properties +++ b/integration-tests/spring-web/src/main/resources/test-users.properties @@ -1,2 +1,4 @@ scott=jb0ss -stuart=test \ No newline at end of file +stuart=test +george=geo +aurea=auri \ No newline at end of file diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java index 81f7d829a918b..13629f73f636e 100644 --- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java +++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java @@ -25,6 +25,34 @@ public void shouldRestrictAccessToSpecificRole() { Optional.of("accessibleForAdminOnly")); } + @Test + public void testAllowedForAdminOrViewer() { + String path = "/api/allowedForUserOrViewer"; + assertForAnonymous(path, 401, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("aurea", "auri"), path, 403, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("stuart", "test"), path, 200, + Optional.of("allowedForUserOrViewer")); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("george", "geo"), path, 200, + Optional.of("allowedForUserOrViewer")); + } + + @Test + public void testWithAlwaysFalseChecker() { + String path = "/api/withAlwaysFalseChecker"; + assertForAnonymous(path, 401, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("george", "geo"), path, 403, Optional.empty()); + } + + @Test + public void testPreAuthorizeOnController() { + String path = "/api/preAuthorizeOnController"; + assertForAnonymous(path, 401, Optional.empty()); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("stuart", "test"), path, 200, + Optional.of("preAuthorizeOnController")); + assertStatusAndContent(RestAssured.given().auth().preemptive().basic("aurea", "auri"), path, 200, + Optional.of("preAuthorizeOnController")); + } + @Test public void shouldAccessAllowed() { assertForAnonymous("/api/accessibleForAllMethod", 200, Optional.of("accessibleForAll")); From 39a10adfb067189004c3ddf3af9afb526d1375e0 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 12 Nov 2019 07:33:21 +0100 Subject: [PATCH 138/602] feat: add a build item to register packages resources for native image --- ...NativeImageResourceDirectoryBuildItem.java | 19 ++++++++++ .../steps/NativeImageAutoFeatureStep.java | 37 +++++++++++++++++++ .../src/main/asciidoc/writing-extensions.adoc | 29 ++++++++------- 3 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceDirectoryBuildItem.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceDirectoryBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceDirectoryBuildItem.java new file mode 100644 index 0000000000000..60847c404ac56 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourceDirectoryBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A build item that indicates that directory resources should be included in the native image + */ +public final class NativeImageResourceDirectoryBuildItem extends MultiBuildItem { + + private final String path; + + public NativeImageResourceDirectoryBuildItem(String path) { + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index 794114ce1b76d..4fd7fc1272246 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -2,17 +2,26 @@ import static io.quarkus.gizmo.MethodDescriptor.ofMethod; +import java.io.File; +import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; @@ -22,10 +31,12 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.GeneratedNativeImageClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; @@ -56,6 +67,32 @@ public class NativeImageAutoFeatureStep { static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry"; static final String LOCALIZATION_SUPPORT = "com.oracle.svm.core.jdk.LocalizationSupport"; + @BuildStep + List registerPackageResources( + List nativeImageResourceDirectories, + DeploymentClassLoaderBuildItem classLoader) + throws IOException, URISyntaxException { + List resources = new ArrayList<>(); + + for (NativeImageResourceDirectoryBuildItem nativeImageResourceDirectory : nativeImageResourceDirectories) { + String path = classLoader.getClassLoader().getResource(nativeImageResourceDirectory.getPath()).getPath(); + File resourceFile = Paths.get(new URL(path.substring(0, path.indexOf("!"))).toURI()).toFile(); + try (JarFile jarFile = new JarFile(resourceFile)) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String resourceName = entry.getName(); + if (!entry.isDirectory() && resourceName.startsWith(nativeImageResourceDirectory.getPath()) + && !resourceName.endsWith(".class")) { + resources.add(new NativeImageResourceBuildItem(resourceName)); + } + } + } + } + + return resources; + } + @BuildStep void generateFeature(BuildProducer nativeImageClass, List runtimeInitializedClassBuildItems, diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 5b7a68781a322..7c368b3c20f57 100755 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -1642,6 +1642,9 @@ executable. Some of these build items are listed below: `io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem`:: Includes static resources into the native executable. +`io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem`:: + Includes directory's static resources into the native executable. + `io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem`:: A class that will be reinitialized at runtime by Substrate. This will result in the static initializer running twice. @@ -2099,16 +2102,16 @@ public final class MyExtProcessor { @BuildStep void registerNativeImageReources(BuildProducer services) { String service = "META-INF/services/" + io.quarkus.SomeService.class.getName(); - + // find out all the implementation classes listed in the service files - Set implementations = + Set implementations = ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), service); - // register every listed implementation class so they can be instantiated + // register every listed implementation class so they can be instantiated // in native-image at run-time services.produce( - new ServiceProviderBuildItem(io.quarkus.SomeService.class.getName(), + new ServiceProviderBuildItem(io.quarkus.SomeService.class.getName(), implementations.toArray(new String[0]))); } } @@ -2130,13 +2133,13 @@ public final class MyExtProcessor { void registerNativeImageReources(BuildProducer resource, BuildProducer reflectionClasses) { String service = "META-INF/services/" + io.quarkus.SomeService.class.getName(); - + // register the service file so it is visible in native-image resource.produce(new NativeImageResourceBuildItem(service)); - - // register every listed implementation class so they can be inspected/instantiated + + // register every listed implementation class so they can be inspected/instantiated // in native-image at run-time - Set implementations = + Set implementations = ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), service); reflectionClasses.produce( @@ -2148,7 +2151,7 @@ public final class MyExtProcessor { While this is the easiest way to get your services running natively, it's less efficient than scanning the implementation classes at build time and generating code that registers them at static-init time instead of relying on reflection. -You can achieve that by adapting the previous build step to use a static-init recorder instead of registering +You can achieve that by adapting the previous build step to use a static-init recorder instead of registering classes for reflection: [source,java] @@ -2157,19 +2160,19 @@ public final class MyExtProcessor { @BuildStep @Record(ExecutionTime.STATIC_INIT) - void registerNativeImageReources(RecorderContext recorderContext, + void registerNativeImageReources(RecorderContext recorderContext, SomeServiceRecorder recorder) { String service = "META-INF/services/" + io.quarkus.SomeService.class.getName(); - + // read the implementation classes Collection> implementationClasses = new LinkedHashSet<>(); Set implementations = ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), service); for(String implementation : implementations) { - implementationClasses.add((Class) + implementationClasses.add((Class) recorderContext.classProxy(implementation)); } - + // produce a static-initializer with those classes recorder.configure(implementationClasses); } From eccc419a86f8364c2f873a1d622e866d0aac28f1 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 12 Nov 2019 20:04:08 +0100 Subject: [PATCH 139/602] fix(vertx-graphql): make the extension work in native mode fixes #5248 --- ci-templates/stages.yml | 3 ++- .../deployment/VertxGraphqlProcessor.java | 22 ++++++++++++++++++- .../vertx/graphql/it/VertxGraphqlTest.java | 20 ++++++++++++----- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index e2f47e4bb51f5..0d8abbfcf7c92 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -310,11 +310,12 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 25 + timeoutInMinutes: 30 modules: - resteasy-jackson - vertx - vertx-http + - vertx-graphql - virtual-http name: http diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java index 453a354cd0a52..94e39a0bf8766 100644 --- a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java +++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java @@ -1,11 +1,18 @@ package io.quarkus.vertx.graphql.deployment; +import java.util.Arrays; +import java.util.List; + import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImagePackageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.vertx.http.deployment.WebsocketSubProtocolsBuildItem; +import io.vertx.ext.web.handler.graphql.impl.GraphQLBatch; +import io.vertx.ext.web.handler.graphql.impl.GraphQLInputDeserializer; +import io.vertx.ext.web.handler.graphql.impl.GraphQLQuery; class VertxGraphqlProcessor { - @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.VERTX_GRAPHQL); @@ -15,4 +22,17 @@ FeatureBuildItem feature() { WebsocketSubProtocolsBuildItem websocketSubProtocols() { return new WebsocketSubProtocolsBuildItem("graphql-ws"); } + + @BuildStep + List registerForReflection() { + return Arrays.asList( + new ReflectiveClassBuildItem(true, true, GraphQLInputDeserializer.class.getName()), + new ReflectiveClassBuildItem(true, true, GraphQLBatch.class.getName()), + new ReflectiveClassBuildItem(true, true, GraphQLQuery.class.getName())); + } + + @BuildStep + NativeImagePackageResourceBuildItem registerNativeImageResources() { + return new NativeImagePackageResourceBuildItem("io/vertx/ext/web/handler/graphiql"); + } } diff --git a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java index a2cdaba2349b7..dc01f4c0d248e 100644 --- a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java +++ b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java @@ -6,10 +6,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; - import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -20,14 +20,22 @@ @QuarkusTest class VertxGraphqlTest { - - @Inject - Vertx vertx; - public static int getPortFromConfig() { return ConfigProvider.getConfig().getOptionalValue("quarkus.http.test-port", Integer.class).orElse(8081); } + private static Vertx vertx; + + @BeforeAll + public static void initializeVertx() { + vertx = Vertx.vertx(); + } + + @AfterAll + public static void closeVertx() { + vertx.close(); + } + @Test public void testGraphQlQuery() { given().contentType(ContentType.JSON).body("{ \"query\" : \"{ hello }\" }") From d06314027b48f6532e451f298ad840b54ab91b6d Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Thu, 14 Nov 2019 12:51:02 +0100 Subject: [PATCH 140/602] feat(vertx-graphql): adds the possibility to control graphql-ui path and resources inclusion --- extensions/vertx-graphql/deployment/pom.xml | 10 +++++ .../deployment/VertxGraphqlConfig.java | 32 ++++++++++++++ .../deployment/VertxGraphqlProcessor.java | 42 +++++++++++++++++-- .../deployment/ErroneousConfigTest.java | 25 +++++++++++ .../ServingUIFromCustomPathTest.java | 23 ++++++++++ .../ServingUIFromDefaultPathTest.java | 21 ++++++++++ .../graphql/runtime/VertxGraphqlRecorder.java | 34 +++++++++++++++ 7 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java create mode 100644 extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ErroneousConfigTest.java create mode 100644 extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromCustomPathTest.java create mode 100644 extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromDefaultPathTest.java create mode 100644 extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java diff --git a/extensions/vertx-graphql/deployment/pom.xml b/extensions/vertx-graphql/deployment/pom.xml index 2bd6495c005c9..0b2217a09f08d 100644 --- a/extensions/vertx-graphql/deployment/pom.xml +++ b/extensions/vertx-graphql/deployment/pom.xml @@ -26,6 +26,16 @@ io.quarkus quarkus-vertx-graphql + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test +
diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java new file mode 100644 index 0000000000000..3e84627bf9783 --- /dev/null +++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlConfig.java @@ -0,0 +1,32 @@ +package io.quarkus.vertx.graphql.deployment; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +public final class VertxGraphqlConfig { + /** + * GraphQL UI configuration + */ + @ConfigItem + VertxGraphqlUiConfig ui; + + @ConfigGroup + public static class VertxGraphqlUiConfig { + /** + * If GraphQL UI should be included every time. By default this is only included when the application is running + * in dev mode. + */ + @ConfigItem(defaultValue = "false") + boolean alwaysInclude; + + /** + * The path where GraphQL UI is available. + *

+ * The value `/` is not allowed as it blocks the application from serving anything else. + */ + @ConfigItem(defaultValue = "/graphql-ui") + String path; + } +} diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java index 94e39a0bf8766..f14127eec0658 100644 --- a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java +++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java @@ -2,17 +2,31 @@ import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImagePackageResourceBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.vertx.graphql.runtime.VertxGraphqlRecorder; +import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.WebsocketSubProtocolsBuildItem; +import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.graphql.impl.GraphQLBatch; import io.vertx.ext.web.handler.graphql.impl.GraphQLInputDeserializer; import io.vertx.ext.web.handler.graphql.impl.GraphQLQuery; class VertxGraphqlProcessor { + private static Pattern TRAILING_SLASH_SUFFIX_REGEX = Pattern.compile("/+$"); + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.VERTX_GRAPHQL); @@ -32,7 +46,29 @@ List registerForReflection() { } @BuildStep - NativeImagePackageResourceBuildItem registerNativeImageResources() { - return new NativeImagePackageResourceBuildItem("io/vertx/ext/web/handler/graphiql"); + @Record(ExecutionTime.STATIC_INIT) + void registerVertxGraphqlUI(VertxGraphqlRecorder recorder, + BuildProducer nativeResourcesProducer, VertxGraphqlConfig config, + LaunchModeBuildItem launchMode, BuildProducer displayableEndpoints, + BuildProducer routes) { + + boolean includeVertxGraphqlUi = launchMode.getLaunchMode().isDevOrTest() || config.ui.alwaysInclude; + if (!includeVertxGraphqlUi) { + return; + } + + Matcher matcher = TRAILING_SLASH_SUFFIX_REGEX.matcher(config.ui.path); + String path = matcher.replaceAll(""); + if (path.isEmpty()) { + throw new ConfigurationException( + "quarkus.vertx-graphql.ui.path was set to \"" + config.ui.path + + "\", this is not allowed as it blocks the application from serving anything else."); + } + + Handler handler = recorder.handler(path); + routes.produce(new RouteBuildItem(path, handler)); + routes.produce(new RouteBuildItem(path + "/*", handler)); + displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(path + "/")); + nativeResourcesProducer.produce(new NativeImageResourceDirectoryBuildItem("io/vertx/ext/web/handler/graphiql")); } } diff --git a/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ErroneousConfigTest.java b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ErroneousConfigTest.java new file mode 100644 index 0000000000000..23fa0e18ddbc7 --- /dev/null +++ b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ErroneousConfigTest.java @@ -0,0 +1,25 @@ +package io.quarkus.vertx.graphql.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class ErroneousConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setExpectedException(ConfigurationException.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("quarkus.vertx-graphql.ui.path=/\n"), "application.properties")); + + @Test + public void shouldNotStartApplicationIfPathIsASlash() { + Assertions.fail(); + } +} diff --git a/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromCustomPathTest.java b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromCustomPathTest.java new file mode 100644 index 0000000000000..feddb7f11b575 --- /dev/null +++ b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromCustomPathTest.java @@ -0,0 +1,23 @@ +package io.quarkus.vertx.graphql.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ServingUIFromCustomPathTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("quarkus.vertx-graphql.ui.path=/custom\n"), "application.properties")); + + @Test + public void shouldServeVertxGraphqlUiFromCustomPath() { + RestAssured.when().get("/custom").then().statusCode(200); + } +} diff --git a/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromDefaultPathTest.java b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromDefaultPathTest.java new file mode 100644 index 0000000000000..4eb9cd8110313 --- /dev/null +++ b/extensions/vertx-graphql/deployment/src/test/java/io/quarkus/vertx/graphql/deployment/ServingUIFromDefaultPathTest.java @@ -0,0 +1,21 @@ +package io.quarkus.vertx.graphql.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ServingUIFromDefaultPathTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void shouldServeVertxGraphqlUiFromDefaultPath() { + RestAssured.when().get("/graphql-ui").then().statusCode(200); + } +} diff --git a/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java b/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java new file mode 100644 index 0000000000000..a720daf837cc3 --- /dev/null +++ b/extensions/vertx-graphql/runtime/src/main/java/io/quarkus/vertx/graphql/runtime/VertxGraphqlRecorder.java @@ -0,0 +1,34 @@ +package io.quarkus.vertx.graphql.runtime; + +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.graphql.GraphiQLHandler; +import io.vertx.ext.web.handler.graphql.GraphiQLHandlerOptions; + +@Recorder +public class VertxGraphqlRecorder { + public Handler handler(String path) { + + GraphiQLHandlerOptions options = new GraphiQLHandlerOptions(); + options.setEnabled(true); + + Handler handler = GraphiQLHandler.create(options); + + return new Handler() { + @Override + public void handle(RoutingContext event) { + if (event.normalisedPath().length() == path.length()) { + + event.response().setStatusCode(302); + event.response().headers().set(HttpHeaders.LOCATION, path + "/"); + event.response().end(); + return; + } + + handler.handle(event); + } + }; + } +} From ff0c3d14ec4a55fe6eef6c454101a36b3b2ed99e Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Mon, 18 Nov 2019 17:29:43 +0100 Subject: [PATCH 141/602] test: properly close vertx instances --- .../java/io/quarkus/mailer/runtime/MailerImplTest.java | 2 +- .../java/io/quarkus/mailer/runtime/MockMailerImplTest.java | 2 +- .../java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java index 73f794af8c466..b289520a3ff13 100644 --- a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java +++ b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MailerImplTest.java @@ -48,7 +48,7 @@ static void startWiser() { @AfterAll static void stopWiser() { wiser.stop(); - vertx.close(); + vertx.close().toCompletableFuture().join(); } @BeforeEach diff --git a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java index 1df77c571bf40..e10a92f73a42a 100644 --- a/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java +++ b/extensions/mailer/runtime/src/test/java/io/quarkus/mailer/runtime/MockMailerImplTest.java @@ -32,7 +32,7 @@ static void start() { @AfterAll static void stop() { - vertx.close(); + vertx.close().toCompletableFuture().join(); } @BeforeEach diff --git a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java index dc01f4c0d248e..4ee948d76791e 100644 --- a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java +++ b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.is; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.microprofile.config.ConfigProvider; @@ -32,8 +33,10 @@ public static void initializeVertx() { } @AfterAll - public static void closeVertx() { - vertx.close(); + public static void closeVertx() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + vertx.close((h) -> latch.countDown()); + latch.await(); } @Test From 63d8cffef746a74622619351266a36c36925ff83 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Thu, 21 Nov 2019 15:44:46 +0100 Subject: [PATCH 142/602] feat(quartz): add clustered jobs support Fixes #3520 --- bom/deployment/pom.xml | 5 + ci-templates/stages.yml | 1 + extensions/agroal/deployment/pom.xml | 4 + extensions/agroal/pom.xml | 1 + extensions/agroal/spi/pom.xml | 23 ++ .../deployment/DataSourceDriverBuildItem.java | 0 .../DataSourceInitializedBuildItem.java | 0 extensions/quartz/deployment/pom.xml | 104 ++++---- .../QuartzJDBCDriverDialectBuildItem.java | 20 ++ .../quartz/deployment/QuartzProcessor.java | 127 ++++++++-- .../quartz/test/MissingDataSourceTest.java | 26 ++ ...upportedClusteredJobConfigurationTest.java | 28 +++ extensions/quartz/runtime/pom.xml | 133 +++++----- .../QuarkusQuartzConnectionPoolProvider.java | 78 ++++++ .../quartz/runtime/QuartzBuildTimeConfig.java | 41 +++ .../quartz/runtime/QuartzRecorder.java | 7 +- .../quartz/runtime/QuartzScheduler.java | 128 +++++++--- .../quarkus/quartz/runtime/QuartzSupport.java | 17 +- .../io/quarkus/quartz/runtime/StoreType.java | 17 ++ .../runtime/graal/QuartzSubstitutions.java | 28 +++ integration-tests/pom.xml | 1 + integration-tests/quartz/pom.xml | 131 ++++++++++ .../io/quarkus/it/quartz/CountResource.java | 20 ++ .../java/io/quarkus/it/quartz/Counter.java | 29 +++ .../src/main/resources/application.properties | 17 ++ .../db/migration/V1.0.1__QuarkusQuartz.sql | 238 ++++++++++++++++++ .../io/quarkus/it/quartz/QuartzITCase.java | 8 + .../io/quarkus/it/quartz/QuartzTestCase.java | 28 +++ .../io/quarkus/it/quartz/TestResources.java | 8 + 29 files changed, 1095 insertions(+), 173 deletions(-) create mode 100644 extensions/agroal/spi/pom.xml rename extensions/agroal/{deployment => spi}/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java (100%) rename extensions/agroal/{deployment => spi}/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java (100%) create mode 100644 extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java create mode 100644 integration-tests/quartz/pom.xml create mode 100644 integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java create mode 100644 integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java create mode 100644 integration-tests/quartz/src/main/resources/application.properties create mode 100644 integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index d971194fab1a3..d68c2b2084011 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -106,6 +106,11 @@ quarkus-agroal-deployment ${project.version} + + io.quarkus + quarkus-agroal-spi + ${project.version} + io.quarkus quarkus-artemis-core diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 0d8abbfcf7c92..5d34e5f4cde87 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -349,6 +349,7 @@ stages: modules: - kogito - kubernetes-client + - quartz name: misc_3 - template: native-build-steps.yaml diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 03167b34a1efd..92d20fa9b228a 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-agroal + + io.quarkus + quarkus-agroal-spi + io.quarkus quarkus-narayana-jta-deployment diff --git a/extensions/agroal/pom.xml b/extensions/agroal/pom.xml index 47b4f6984e30d..4eb57e49b3eb2 100644 --- a/extensions/agroal/pom.xml +++ b/extensions/agroal/pom.xml @@ -14,6 +14,7 @@ Quarkus - Agroal pom + spi deployment runtime diff --git a/extensions/agroal/spi/pom.xml b/extensions/agroal/spi/pom.xml new file mode 100644 index 0000000000000..dca7328ae8c8b --- /dev/null +++ b/extensions/agroal/spi/pom.xml @@ -0,0 +1,23 @@ + + + + quarkus-agroal-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-agroal-spi + Quarkus - Agroal - SPI + + + + io.quarkus + quarkus-core-deployment + + + + diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java similarity index 100% rename from extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java rename to extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java similarity index 100% rename from extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java rename to extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java diff --git a/extensions/quartz/deployment/pom.xml b/extensions/quartz/deployment/pom.xml index 5e0e82e4636f5..ce55f3ac36e3b 100644 --- a/extensions/quartz/deployment/pom.xml +++ b/extensions/quartz/deployment/pom.xml @@ -1,58 +1,62 @@ - - quarkus-quartz-parent - io.quarkus - 999-SNAPSHOT - ../ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + quarkus-quartz-parent + io.quarkus + 999-SNAPSHOT + ../ + - 4.0.0 + 4.0.0 - quarkus-quartz-deployment - Quarkus - Scheduler Quartz - Deployment + quarkus-quartz-deployment + Quarkus - Quartz - Deployment - - - io.quarkus - quarkus-core-deployment - - - io.quarkus - quarkus-arc-deployment - - - io.quarkus - quarkus-scheduler-deployment - - - io.quarkus - quarkus-quartz - + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-scheduler-deployment + + + io.quarkus + quarkus-agroal-spi + + + io.quarkus + quarkus-quartz + - - io.quarkus - quarkus-junit5-internal - test - - + + io.quarkus + quarkus-junit5-internal + test + + - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java new file mode 100644 index 0000000000000..0fa3375a54c19 --- /dev/null +++ b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.quartz.deployment; + +import java.util.Optional; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Holds the SQL driver dialect {@link org.quartz.impl.jdbcjobstore.StdJDBCDelegate driver delegate} to use. + */ +final class QuartzJDBCDriverDialectBuildItem extends SimpleBuildItem { + private final Optional driver; + + public QuartzJDBCDriverDialectBuildItem(Optional driver) { + this.driver = driver; + } + + public Optional getDriver() { + return driver; + } +} diff --git a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java index 220de421ecee0..9c8f66702c9cf 100644 --- a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java +++ b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java @@ -2,13 +2,27 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import java.sql.Connection; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.quartz.core.QuartzSchedulerThread; +import org.quartz.core.SchedulerSignalerImpl; +import org.quartz.impl.StdSchedulerFactory; +import org.quartz.impl.jdbcjobstore.AttributeRestoringConnectionInvocationHandler; +import org.quartz.impl.jdbcjobstore.HSQLDBDelegate; +import org.quartz.impl.jdbcjobstore.JobStoreSupport; +import org.quartz.impl.jdbcjobstore.MSSQLDelegate; +import org.quartz.impl.jdbcjobstore.PostgreSQLDelegate; +import org.quartz.impl.jdbcjobstore.StdJDBCDelegate; +import org.quartz.impl.triggers.AbstractTrigger; +import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.simpl.CascadingClassLoadHelper; -import org.quartz.simpl.RAMJobStore; +import org.quartz.simpl.SimpleInstanceIdGenerator; import org.quartz.simpl.SimpleThreadPool; +import io.quarkus.agroal.deployment.DataSourceDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.Capabilities; @@ -17,18 +31,22 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.configuration.ConfigurationError; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.quartz.runtime.QuarkusQuartzConnectionPoolProvider; +import io.quarkus.quartz.runtime.QuartzBuildTimeConfig; import io.quarkus.quartz.runtime.QuartzRecorder; import io.quarkus.quartz.runtime.QuartzRuntimeConfig; import io.quarkus.quartz.runtime.QuartzScheduler; import io.quarkus.quartz.runtime.QuartzSupport; +import io.quarkus.quartz.runtime.StoreType; /** * @author Martin Kouba */ public class QuartzProcessor { - @BuildStep CapabilityBuildItem capability() { return new CapabilityBuildItem(Capabilities.QUARTZ); @@ -40,41 +58,118 @@ AdditionalBeanBuildItem beans() { } @BuildStep - List reflectiveClasses() { + NativeImageProxyDefinitionBuildItem connectionProxy(QuartzBuildTimeConfig config) { + if (config.storeType.equals(StoreType.DB)) { + return new NativeImageProxyDefinitionBuildItem(Connection.class.getName()); + } + return null; + } + + @BuildStep + QuartzJDBCDriverDialectBuildItem driver(Optional dataSourceDriver, + QuartzBuildTimeConfig config) { + if (config.storeType == StoreType.RAM) { + if (config.clustered) { + throw new ConfigurationError("Clustered jobs configured with unsupported job store option"); + } + + return new QuartzJDBCDriverDialectBuildItem(Optional.empty()); + } + + if (!dataSourceDriver.isPresent()) { + String message = String.format( + "JDBC Store configured but '%s' datasource is not configured properly. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource-guide", + config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default"); + throw new ConfigurationError(message); + } + + return new QuartzJDBCDriverDialectBuildItem(Optional.of(guessDriver(dataSourceDriver))); + } + + private String guessDriver(Optional dataSourceDriver) { + if (!dataSourceDriver.isPresent()) { + return StdJDBCDelegate.class.getName(); + } + + String resolvedDriver = dataSourceDriver.get().getDriver(); + if (resolvedDriver.contains("postgresql")) { + return PostgreSQLDelegate.class.getName(); + } + if (resolvedDriver.contains("org.h2.Driver")) { + return HSQLDBDelegate.class.getName(); + } + + if (resolvedDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerResource")) { + return MSSQLDelegate.class.getName(); + } + + return StdJDBCDelegate.class.getName(); + + } + + @BuildStep + List reflectiveClasses(QuartzBuildTimeConfig config, + QuartzJDBCDriverDialectBuildItem driverDialect) { List reflectiveClasses = new ArrayList<>(); - reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName())); + StoreType storeType = config.storeType; + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleThreadPool.class.getName())); - reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, RAMJobStore.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleInstanceIdGenerator.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, storeType.clazz)); + + if (storeType.equals(StoreType.DB)) { + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, JobStoreSupport.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, Connection.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, AbstractTrigger.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleTriggerImpl.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, driverDialect.getDriver().get())); + reflectiveClasses + .add(new ReflectiveClassBuildItem(true, true, "io.quarkus.quartz.runtime.QuartzScheduler$InvokerJob")); + reflectiveClasses + .add(new ReflectiveClassBuildItem(true, false, QuarkusQuartzConnectionPoolProvider.class.getName())); + } + return reflectiveClasses; } @BuildStep - public void logCleanup(BuildProducer logCleanupFilter) { - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.impl.StdSchedulerFactory", + public List logCleanup(QuartzBuildTimeConfig config) { + StoreType storeType = config.storeType; + List logCleanUps = new ArrayList<>(); + logCleanUps.add(new LogCleanupFilterBuildItem(StdSchedulerFactory.class.getName(), "Quartz scheduler version:", "Using default implementation for", "Quartz scheduler 'QuarkusQuartzScheduler'")); - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.QuartzScheduler", + logCleanUps.add(new LogCleanupFilterBuildItem(org.quartz.core.QuartzScheduler.class.getName(), "Quartz Scheduler v", "JobFactory set to:", "Scheduler meta-data:", "Scheduler QuarkusQuartzScheduler")); - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.simpl.RAMJobStore", - "RAMJobStore initialized.")); - - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.SchedulerSignalerImpl", + logCleanUps.add(new LogCleanupFilterBuildItem(storeType.clazz, storeType.name + " initialized.", "Handling", + "Using db table-based data access locking", "JDBCJobStore threads will inherit ContextClassLoader of thread", + "Couldn't rollback jdbc connection", "Database connection shutdown unsuccessful")); + logCleanUps.add(new LogCleanupFilterBuildItem(SchedulerSignalerImpl.class.getName(), "Initialized Scheduler Signaller of type")); + logCleanUps.add(new LogCleanupFilterBuildItem(QuartzSchedulerThread.class.getName(), + "QuartzSchedulerThread Inheriting ContextClassLoader")); + logCleanUps.add(new LogCleanupFilterBuildItem(SimpleThreadPool.class.getName(), + "Job execution threads will use class loader of thread")); + + logCleanUps.add(new LogCleanupFilterBuildItem(AttributeRestoringConnectionInvocationHandler.class.getName(), + "Failed restore connection's original")); + return logCleanUps; } @BuildStep @Record(RUNTIME_INIT) - public void build(QuartzRuntimeConfig runtimeConfig, QuartzRecorder recorder, BeanContainerBuildItem beanContainer, - BuildProducer serviceStart) { - recorder.initialize(runtimeConfig, beanContainer.getValue()); + public void build(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, QuartzRecorder recorder, + BeanContainerBuildItem beanContainer, + BuildProducer serviceStart, QuartzJDBCDriverDialectBuildItem driverDialect) { + recorder.initialize(runtimeConfig, buildTimeConfig, beanContainer.getValue(), driverDialect.getDriver()); // Make sure that StartupEvent is fired after the init serviceStart.produce(new ServiceStartBuildItem("quartz")); } - } diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java new file mode 100644 index 0000000000000..04a1694bdfbc9 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java @@ -0,0 +1,26 @@ +package io.quarkus.quartz.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class MissingDataSourceTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(ConfigurationError.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset("quarkus.quartz.store-type=db"), "application.properties")); + + @Test + public void shouldFailAndNotReachHere() { + Assertions.fail(); + } +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java new file mode 100644 index 0000000000000..0fc885b2ed857 --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java @@ -0,0 +1,28 @@ +package io.quarkus.quartz.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class UnsupportedClusteredJobConfigurationTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(ConfigurationError.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset( + "quarkus.quartz.store-type=ram\nquarkus.quartz.clustered=true"), + "application.properties")); + + @Test + public void shouldFailWhenConfiguringClusteredJobWithRamStore() { + Assertions.fail(); + } +} diff --git a/extensions/quartz/runtime/pom.xml b/extensions/quartz/runtime/pom.xml index cfc00d070b7b5..af327d6c85612 100644 --- a/extensions/quartz/runtime/pom.xml +++ b/extensions/quartz/runtime/pom.xml @@ -1,71 +1,76 @@ - - quarkus-quartz-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + quarkus-quartz-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 - quarkus-quartz - Quarkus - Scheduler Quartz - Runtime - - - io.quarkus - quarkus-scheduler - - - com.oracle.substratevm - svm - - - org.quartz-scheduler - quartz - - - com.zaxxer - HikariCP-java6 - - - com.mchange - c3p0 - - - - - jakarta.transaction - jakarta.transaction-api - - + quarkus-quartz + Quarkus - Quartz - Runtime + + + io.quarkus + quarkus-agroal + true + + + io.quarkus + quarkus-scheduler + + + com.oracle.substratevm + svm + + + org.quartz-scheduler + quartz + + + com.zaxxer + HikariCP-java6 + + + com.mchange + c3p0 + + + + + jakarta.transaction + jakarta.transaction-api + + - + - - - - io.quarkus - quarkus-bootstrap-maven-plugin - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java new file mode 100644 index 0000000000000..e39fd122baec5 --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java @@ -0,0 +1,78 @@ +package io.quarkus.quartz.runtime; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.enterprise.util.AnnotationLiteral; +import javax.sql.DataSource; + +import org.quartz.utils.PoolingConnectionProvider; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; + +public class QuarkusQuartzConnectionPoolProvider implements PoolingConnectionProvider { + private AgroalDataSource dataSource; + private static String dataSourceName; + + public QuarkusQuartzConnectionPoolProvider() { + final ArcContainer container = Arc.container(); + final InstanceHandle instanceHandle; + final boolean useDefaultDataSource = "QUARKUS_QUARTZ_DEFAULT_DATASOURCE".equals(dataSourceName); + if (useDefaultDataSource) { + instanceHandle = container.instance(AgroalDataSource.class); + } else { + instanceHandle = container.instance(AgroalDataSource.class, new DataSourceLiteral(dataSourceName)); + } + if (instanceHandle.isAvailable()) { + this.dataSource = instanceHandle.get(); + } else { + String message = String.format( + "JDBC Store configured but '%s' datasource is missing. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource", + useDefaultDataSource ? "default" : dataSourceName); + throw new IllegalStateException(message); + } + } + + @Override + public DataSource getDataSource() { + return dataSource; + } + + @Override + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + @Override + public void shutdown() { + // Do nothing as the connection will be closed inside the Agroal extension + } + + @Override + public void initialize() { + + } + + static void setDataSourceName(String dataSourceName) { + QuarkusQuartzConnectionPoolProvider.dataSourceName = dataSourceName; + } + + private static class DataSourceLiteral extends AnnotationLiteral + implements io.quarkus.agroal.DataSource { + + private String name; + + public DataSourceLiteral(String name) { + this.name = name; + } + + @Override + public String value() { + return name; + } + + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java new file mode 100644 index 0000000000000..4411910f4a0f0 --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java @@ -0,0 +1,41 @@ +package io.quarkus.quartz.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class QuartzBuildTimeConfig { + /** + * Enable cluster mode or not. + *

+ * If enabled make sure to set the appropriate cluster properties. + */ + @ConfigItem + public boolean clustered; + + /** + * The type of store to use. + *

+ * When using the `db` store type configuration value make sure that you have the datasource configured. + * See Configuring your datasource for more information. + *

+ * To create Quartz tables, you can perform a schema migration via the Flyway + * extension using a SQL script matching your database picked from Quartz + * repository. + */ + @ConfigItem(defaultValue = "ram") + public StoreType storeType; + + /** + * The name of the datasource to use. + *

+ * Optionally needed when using the `db` store type. + * If not specified, defaults to using the default datasource. + */ + @ConfigItem(name = "datasource") + public Optional dataSourceName; +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java index d694e8fe5ac1a..cbbd0b4892d2e 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java @@ -1,14 +1,17 @@ package io.quarkus.quartz.runtime; +import java.util.Optional; + import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.annotations.Recorder; @Recorder public class QuartzRecorder { - public void initialize(QuartzRuntimeConfig runtimeConfig, BeanContainer container) { + public void initialize(QuartzRuntimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, BeanContainer container, + Optional driverDialect) { QuartzSupport support = container.instance(QuartzSupport.class); - support.initialize(runtimeConfig); + support.initialize(runTimeConfig, buildTimeConfig, driverDialect); } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java index 1198bc60441a3..0ed976fce018f 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java @@ -9,6 +9,8 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.BeforeDestroyed; import javax.enterprise.event.Observes; import javax.inject.Singleton; @@ -17,8 +19,8 @@ import org.quartz.CronScheduleBuilder; import org.quartz.Job; import org.quartz.JobBuilder; +import org.quartz.JobDetail; import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; @@ -55,7 +57,6 @@ public class QuartzScheduler implements Scheduler { private final Map invokers; public QuartzScheduler(SchedulerSupport schedulerSupport, QuartzSupport quartzSupport, Config config) { - if (schedulerSupport.getScheduledMethods().isEmpty()) { this.triggerNameSequence = null; this.scheduler = null; @@ -66,21 +67,7 @@ public QuartzScheduler(SchedulerSupport schedulerSupport, QuartzSupport quartzSu this.invokers = new HashMap<>(); try { - Properties props = new Properties(); - props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID, "QuarkusQuartzScheduler"); - props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "QuarkusQuartzScheduler"); - props.put(StdSchedulerFactory.PROP_SCHED_WRAP_JOB_IN_USER_TX, false); - props.put(StdSchedulerFactory.PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD, - true); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", - "" + quartzSupport.getRuntimeConfig().threadCount); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadPriority", - "" + quartzSupport.getRuntimeConfig().threadPriority); - props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".misfireThreshold", "60000"); - props.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, "org.quartz.simpl.RAMJobStore"); - props.put(StdSchedulerFactory.PROP_SCHED_RMI_EXPORT, false); - props.put(StdSchedulerFactory.PROP_SCHED_RMI_PROXY, false); + Properties props = getSchedulerConfigurationProperties(quartzSupport); SchedulerFactory schedulerFactory = new StdSchedulerFactory(props); scheduler = schedulerFactory.getScheduler(); @@ -109,8 +96,9 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler scheduler) thr for (Scheduled scheduled : method.getSchedules()) { String name = triggerNameSequence.getAndIncrement() + "_" + method.getInvokerClassName(); JobBuilder jobBuilder = JobBuilder.newJob(InvokerJob.class) - .withIdentity(name, Scheduler.class.getName()).usingJobData(INVOKER_KEY, - method.getInvokerClassName()); + .withIdentity(name, Scheduler.class.getName()) + .usingJobData(INVOKER_KEY, method.getInvokerClassName()) + .requestRecovery(); ScheduleBuilder scheduleBuilder; String cron = scheduled.cron().trim(); @@ -161,8 +149,13 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler scheduler) thr .plusMillis(scheduled.delayUnit().toMillis(scheduled.delay())).toEpochMilli())); } - scheduler.scheduleJob(jobBuilder.build(), triggerBuilder.build()); - LOGGER.debugf("Scheduled business method %s with config %s", method.getMethodDescription(), scheduled); + JobDetail job = jobBuilder.build(); + if (scheduler.checkExists(job.getKey())) { + scheduler.deleteJob(job.getKey()); + } + scheduler.scheduleJob(job, triggerBuilder.build()); + LOGGER.debugf("Scheduled business method %s with config %s", method.getMethodDescription(), + scheduled); } } } catch (SchedulerException e) { @@ -204,21 +197,76 @@ void start(@Observes StartupEvent startupEvent) { } } + /** + * Need to gracefully shutdown the scheduler making sure that all triggers have been + * released before datasource shutdown. + * + * @param event ignored + */ + void destroy(@BeforeDestroyed(ApplicationScoped.class) Object event) { // + if (scheduler != null) { + try { + scheduler.shutdown(true); // gracefully shutdown + } catch (SchedulerException e) { + LOGGER.warnf("Unable to gracefully shutdown the scheduler", e); + } + } + } + @PreDestroy void destroy() { if (scheduler != null) { try { - scheduler.shutdown(); + if (!scheduler.isShutdown()) { + scheduler.shutdown(false); // force shutdown + } } catch (SchedulerException e) { - LOGGER.warnf("Unable to shutdown scheduler", e); + LOGGER.warnf("Unable to shutdown the scheduler", e); } } } + private Properties getSchedulerConfigurationProperties(QuartzSupport quartzSupport) { + Properties props = new Properties(); + QuartzBuildTimeConfig buildTimeConfig = quartzSupport.getBuildTimeConfig(); + props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID, "AUTO"); + props.put("org.quartz.scheduler.skipUpdateCheck", "true"); + props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "QuarkusQuartzScheduler"); + props.put(StdSchedulerFactory.PROP_SCHED_WRAP_JOB_IN_USER_TX, "false"); + props.put(StdSchedulerFactory.PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD, "true"); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", + "" + quartzSupport.getRuntimeConfig().threadCount); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadPriority", + "" + quartzSupport.getRuntimeConfig().threadPriority); + props.put(StdSchedulerFactory.PROP_SCHED_RMI_EXPORT, "false"); + props.put(StdSchedulerFactory.PROP_SCHED_RMI_PROXY, "false"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, buildTimeConfig.storeType.clazz); + + if (buildTimeConfig.storeType == StoreType.DB) { + String dataSource = buildTimeConfig.dataSourceName.orElse("QUARKUS_QUARTZ_DEFAULT_DATASOURCE"); + QuarkusQuartzConnectionPoolProvider.setDataSourceName(dataSource); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".useProperties", "true"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".misfireThreshold", "60000"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".tablePrefix", "QRTZ_"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".dataSource", dataSource); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".driverDelegateClass", + quartzSupport.getDriverDialect().get()); + props.put(StdSchedulerFactory.PROP_DATASOURCE_PREFIX + "." + dataSource + ".connectionProvider.class", + QuarkusQuartzConnectionPoolProvider.class.getName()); + if (buildTimeConfig.clustered) { + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".isClustered", "true"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".clusterCheckinInterval", "20000"); // 20 seconds + } + } + + return props; + } + class InvokerJob implements Job { @Override - public void execute(JobExecutionContext context) throws JobExecutionException { + public void execute(JobExecutionContext context) { Trigger trigger = new Trigger() { @Override @@ -239,23 +287,25 @@ public String getId() { } }; String invokerClass = context.getJobDetail().getJobDataMap().getString(INVOKER_KEY); - invokers.get(invokerClass).invoke(new ScheduledExecution() { - - @Override - public Trigger getTrigger() { - return trigger; - } + ScheduledInvoker scheduledInvoker = invokers.get(invokerClass); + if (scheduledInvoker != null) { // could be null from previous runs + scheduledInvoker.invoke(new ScheduledExecution() { + @Override + public Trigger getTrigger() { + return trigger; + } - @Override - public Instant getScheduledFireTime() { - return context.getScheduledFireTime().toInstant(); - } + @Override + public Instant getScheduledFireTime() { + return context.getScheduledFireTime().toInstant(); + } - @Override - public Instant getFireTime() { - return context.getFireTime().toInstant(); - } - }); + @Override + public Instant getFireTime() { + return context.getFireTime().toInstant(); + } + }); + } } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java index 53a908533ef6e..c510e585d4973 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java @@ -1,18 +1,31 @@ package io.quarkus.quartz.runtime; +import java.util.Optional; + import javax.inject.Singleton; @Singleton public class QuartzSupport { private QuartzRuntimeConfig runtimeConfig; + private QuartzBuildTimeConfig buildTimeConfig; + private Optional driverDialect; - void initialize(QuartzRuntimeConfig runtimeConfig) { - this.runtimeConfig = runtimeConfig; + void initialize(QuartzRuntimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, Optional driverDialect) { + this.runtimeConfig = runTimeConfig; + this.buildTimeConfig = buildTimeConfig; + this.driverDialect = driverDialect; } public QuartzRuntimeConfig getRuntimeConfig() { return runtimeConfig; } + public QuartzBuildTimeConfig getBuildTimeConfig() { + return buildTimeConfig; + } + + public Optional getDriverDialect() { + return driverDialect; + } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java new file mode 100644 index 0000000000000..fb43eebbc3289 --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java @@ -0,0 +1,17 @@ +package io.quarkus.quartz.runtime; + +import org.quartz.impl.jdbcjobstore.JobStoreTX; +import org.quartz.simpl.RAMJobStore; + +public enum StoreType { + RAM(RAMJobStore.class.getName(), RAMJobStore.class.getSimpleName()), + DB(JobStoreTX.class.getName(), JobStoreTX.class.getSimpleName()); + + public String name; + public String clazz; + + StoreType(String clazz, String name) { + this.clazz = clazz; + this.name = name; + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java index c408fb2f1fcd2..07cc9ced71ee5 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java @@ -1,8 +1,11 @@ package io.quarkus.quartz.runtime.graal; +import java.io.ByteArrayOutputStream; import java.rmi.RemoteException; +import java.sql.ResultSet; import org.quartz.core.RemotableQuartzScheduler; +import org.quartz.impl.jdbcjobstore.StdJDBCDelegate; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -37,5 +40,30 @@ protected RemotableQuartzScheduler getRemoteScheduler() { } +@TargetClass(StdJDBCDelegate.class) +final class Target_org_quartz_impl_jdbc_jobstore_StdJDBCDelegate { + + /** + * Activate the usage of {@link java.util.Properties} to avoid Object serialization + * which is not supported by GraalVM - see https://github.com/oracle/graal/issues/460 + * + * @return true + */ + @Substitute + protected boolean canUseProperties() { + return true; + } + + @Substitute + protected ByteArrayOutputStream serializeObject(Object obj) { + throw new IllegalStateException("Object serialization not supported."); // should not reach here + } + + @Substitute + protected Object getObjectFromBlob(ResultSet rs, String colName) { + throw new IllegalStateException("Object serialization not supported."); // should not reach here + } +} + final class QuartzSubstitutions { } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 7fc88094f682f..4b5bdea0b3a70 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -86,6 +86,7 @@ elytron-security-jdbc vertx-graphql jpa-without-entity + quartz diff --git a/integration-tests/quartz/pom.xml b/integration-tests/quartz/pom.xml new file mode 100644 index 0000000000000..d0581b8d737f9 --- /dev/null +++ b/integration-tests/quartz/pom.xml @@ -0,0 +1,131 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-quartz + Quarkus - Integration Tests - Quartz + The Quartz integration test module + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-quartz + + + + io.quarkus + quarkus-agroal + + + + io.quarkus + quarkus-flyway + + + + io.quarkus + quarkus-jdbc-h2 + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + diff --git a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java new file mode 100644 index 0000000000000..fc0aef6fc913a --- /dev/null +++ b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java @@ -0,0 +1,20 @@ +package io.quarkus.it.quartz; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/scheduler/count") +public class CountResource { + + @Inject + Counter counter; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Integer getCount() { + return counter.get(); + } +} diff --git a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java new file mode 100644 index 0000000000000..f5aa6745f023e --- /dev/null +++ b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java @@ -0,0 +1,29 @@ +package io.quarkus.it.quartz; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.scheduler.Scheduled; + +@ApplicationScoped +public class Counter { + + AtomicInteger counter; + + @PostConstruct + void init() { + counter = new AtomicInteger(); + } + + public int get() { + return counter.get(); + } + + @Scheduled(cron = "0/1 * * * * ?") + void increment() { + counter.incrementAndGet(); + } + +} \ No newline at end of file diff --git a/integration-tests/quartz/src/main/resources/application.properties b/integration-tests/quartz/src/main/resources/application.properties new file mode 100644 index 0000000000000..6107a8ad8a3a9 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# datasource configuration +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.max-size=8 +quarkus.datasource.min-size=2 + +# Quartz configuration +quarkus.quartz.store-type=db +quarkus.quartz.clustered=true + +# flyway to create Quartz tables +quarkus.flyway.connect-retries=10 +quarkus.flyway.table=flyway_quarkus_history +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0 +quarkus.flyway.baseline-description=Quartz diff --git a/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql b/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql new file mode 100644 index 0000000000000..b362b7e53fc07 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql @@ -0,0 +1,238 @@ +CREATE TABLE QRTZ_CALENDARS ( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR (200) NOT NULL , + CALENDAR IMAGE NOT NULL +); + +CREATE TABLE QRTZ_CRON_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + CRON_EXPRESSION VARCHAR (120) NOT NULL , + TIME_ZONE_ID VARCHAR (80) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR (95) NOT NULL , + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + INSTANCE_NAME VARCHAR (200) NOT NULL , + FIRED_TIME BIGINT NOT NULL , + SCHED_TIME BIGINT NOT NULL , + PRIORITY INTEGER NOT NULL , + STATE VARCHAR (16) NOT NULL, + JOB_NAME VARCHAR (200) NULL , + JOB_GROUP VARCHAR (200) NULL , + IS_NONCONCURRENT BOOLEAN NULL , + REQUESTS_RECOVERY BOOLEAN NULL +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR (200) NOT NULL +); + +CREATE TABLE QRTZ_SCHEDULER_STATE ( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR (200) NOT NULL , + LAST_CHECKIN_TIME BIGINT NOT NULL , + CHECKIN_INTERVAL BIGINT NOT NULL +); + +CREATE TABLE QRTZ_LOCKS ( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR (40) NOT NULL +); + +CREATE TABLE QRTZ_JOB_DETAILS ( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + JOB_CLASS_NAME VARCHAR (250) NOT NULL , + IS_DURABLE BOOLEAN NOT NULL , + IS_NONCONCURRENT BOOLEAN NOT NULL , + IS_UPDATE_DATA BOOLEAN NOT NULL , + REQUESTS_RECOVERY BOOLEAN NOT NULL , + JOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + REPEAT_COUNT BIGINT NOT NULL , + REPEAT_INTERVAL BIGINT NOT NULL , + TIMES_TRIGGERED BIGINT NOT NULL +); + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INTEGER NULL, + INT_PROP_2 INTEGER NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + BLOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + NEXT_FIRE_TIME BIGINT NULL , + PREV_FIRE_TIME BIGINT NULL , + PRIORITY INTEGER NULL , + TRIGGER_STATE VARCHAR (16) NOT NULL , + TRIGGER_TYPE VARCHAR (8) NOT NULL , + START_TIME BIGINT NOT NULL , + END_TIME BIGINT NULL , + CALENDAR_NAME VARCHAR (200) NULL , + MISFIRE_INSTR SMALLINT NULL , + JOB_DATA IMAGE NULL +); + +ALTER TABLE QRTZ_CALENDARS ADD + CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY + ( + SCHED_NAME, + CALENDAR_NAME + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_FIRED_TRIGGERS ADD + CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + ENTRY_ID + ); + +ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD + CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SCHEDULER_STATE ADD + CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY + ( + SCHED_NAME, + INSTANCE_NAME + ); + +ALTER TABLE QRTZ_LOCKS ADD + CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY + ( + SCHED_NAME, + LOCK_NAME + ); + +ALTER TABLE QRTZ_JOB_DETAILS ADD + CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ) REFERENCES QRTZ_JOB_DETAILS ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +COMMIT; diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java new file mode 100644 index 0000000000000..075e767cfb937 --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.quartz; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class QuartzITCase extends QuartzTestCase { + +} diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java new file mode 100644 index 0000000000000..758d80e6e3ab2 --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java @@ -0,0 +1,28 @@ +package io.quarkus.it.quartz; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.response.Response; + +@QuarkusTest +public class QuartzTestCase { + + @Test + public void testCount() throws InterruptedException { + // Wait at least 1 second + Thread.sleep(1000); + Response response = given() + .when().get("/scheduler/count"); + String body = response.asString(); + int count = Integer.valueOf(body); + assertTrue(count > 0); + response + .then() + .statusCode(200); + } + +} diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java new file mode 100644 index 0000000000000..ecc74f854233f --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java @@ -0,0 +1,8 @@ +package io.quarkus.it.quartz; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +} From 7dd31c3dd0850a4162f0daa6906fa62909705b62 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Sat, 23 Nov 2019 20:53:06 +0100 Subject: [PATCH 143/602] docs: add Quartz documentation --- docs/src/main/asciidoc/index.adoc | 1 + docs/src/main/asciidoc/quartz.adoc | 340 ++++++++++++++++++ docs/src/main/asciidoc/scheduler.adoc | 2 + .../resources/META-INF/quarkus-extension.yaml | 5 +- 4 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 docs/src/main/asciidoc/quartz.adoc diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index 0ab71bddc98f6..b8e96aa79ebaa 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -20,6 +20,7 @@ include::quarkus-intro.adoc[tag=intro] * link:lifecycle.html[Application Initialization and Termination] * link:rest-json.html[Writing JSON REST Services] * link:scheduler.html[Schedule Periodic Tasks] +* link:quartz.html[Schedule Periodic Tasks with Quartz] * link:websockets.html[Using Websockets] * link:validation.html[Validation with Hibernate Validator] * link:transaction.html[Using Transactions] diff --git a/docs/src/main/asciidoc/quartz.adoc b/docs/src/main/asciidoc/quartz.adoc new file mode 100644 index 0000000000000..ccf96c73e9ab0 --- /dev/null +++ b/docs/src/main/asciidoc/quartz.adoc @@ -0,0 +1,340 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Scheduling Periodic Tasks with Quartz + +include::./attributes.adoc[] + +Modern applications often need to run specific tasks periodically. +In this guide, you learn how to schedule periodic clustered tasks using the http://www.quartz-scheduler.org/[Quartz] extension. + +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + +TIP: If you only need to run in-memory scheduler use the link:scheduler[Scheduler] extension. + +== Prerequisites + +To complete this guide, you need: + +* less than 10 minutes +* an IDE +* JDK 1.8+ installed with `JAVA_HOME` configured appropriately +* Apache Maven 3.5.3+ +* Docker and Docker Compose installed on your machine + +== Architecture + +In this guide, we are going to expose one Rest API `tasks` to visualise the list of tasks created by a Quartz job running every 10 seconds. + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can go right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `quartz-quickstart` {quickstarts-tree-url}/quartz-quickstart[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +[source,shell,subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=quartz-quickstart \ + -DclassName="org.acme.quartz.TaskResource" \ + -Dpath="/tasks" \ + -Dextensions="quartz, hibernate-orm-panache, flyway, resteasy-jsonb, jdbc-postgresql" +cd quartz-quickstart +---- + +It generates: + +* the Maven structure +* a landing page accessible on `http://localhost:8080` +* example `Dockerfile` files for both `native` and `jvm` modes +* the application configuration file +* an `org.acme.quartz.TaskResource` resource +* an associated test + +The Maven project also imports the Quarkus Quartz extension. + +== Creating the Task Entity + +In the `org.acme.quartz` package, create the `Task` class, with the following content: + +[source,java] +---- +package org.acme.quartz; + +import javax.persistence.Entity; +import java.time.Instant; +import javax.persistence.Table; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +@Table(name="TASKS") +public class Task extends PanacheEntity { <1> + public Instant createdAt; + + public Task() { + createdAt = Instant.now(); + } + + public Task(Instant time) { + this.createdAt = time; + } +} +---- +1. Declare the entity using https://quarkus.io/guides/hibernate-orm-panache[Panache] + +== Creating a scheduled job + +In the `org.acme.quartz` package, create the `TaskBean` class, with the following content: + +[source,java] +---- +package org.acme.quartz; + +import javax.enterprise.context.ApplicationScoped; + +import javax.transaction.Transactional; + +import io.quarkus.scheduler.Scheduled; + +@ApplicationScoped <1> +public class TaskBean { + + @Transactional + @Scheduled(every = "10s") <2> + void schedule() { + Task task = new Task(); <3> + task.persist(); <4> + } +} +---- +1. Declare the bean in the _application_ scope +2. Use the `@Scheduled` annotation to instruct Quarkus to run this method every 10 seconds. +3. Create a new `Task` with the current start time. +4. Persist the task in database using https://quarkus.io/guides/hibernate-orm-panache[Panache]. + +== Updating the application configuration file + +Edit the `application.properties` file and add the below configuration: +[source,shell] +---- +# Quartz configuration +quarkus.quartz.clustered=true <1> +quarkus.quartz.store-type=db <2> + +# Datasource configuration. +quarkus.datasource.url=jdbc:postgresql://localhost/quarkus_test +quarkus.datasource.driver=org.postgresql.Driver +quarkus.datasource.username=quarkus_test +quarkus.datasource.password=quarkus_test + +# Hibernate configuration +quarkus.hibernate-orm.database.generation=none +quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.sql-load-script=no-file + +# flyway configuration +quarkus.flyway.connect-retries=10 +quarkus.flyway.table=flyway_quarkus_history +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0 +quarkus.flyway.baseline-description=Quartz +---- + +1. Indicate that the scheduler will be run in clustered mode +2. Use the database store to persist job related information so that they can be shared between nodes + +== Updating the resource and the test + +Edit the `TaskResource` class, and update the content to: + +[source,java] +---- +package org.acme.quartz; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/tasks") +@Produces(MediaType.APPLICATION_JSON) +public class TaskResource { + + @GET + public List listAll() { + return Task.listAll(); <1> + } +} +---- +1. Retrieve the list of created tasks from the database + +We also need to update the tests. Edit the `TaskResourceTest` class to match: + +[source,java] +---- +package org.acme.quartz; + +import io.quarkus.test.junit.QuarkusTest; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; + +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class TaskResourceTest { + + @Test + public void tasks() throws InterruptedException { + Thread.sleep(1000); // wait at least a second to have the first task created + given() + .when().get("/tasks") + .then() + .statusCode(200) + .body("size()", is(greaterThanOrEqualTo(1))); <1> + } +} +---- +1. Ensure that we have a `200` response and at least one task created + +== Creating Quartz Tables + +Add a SQL migration file named `src/main/resources/db/migration/V2.0.0__QuarkusQuartzTasks.sql` with the content copied from +file with the content from {quickstarts-blob-url}/quartz-quickstart/src/main/resources/db/migration/V2.0.0__QuarkusQuartzTasks.sql[V2.0.0__QuarkusQuartzTasks.sql]. + +== Configuring the load balancer + +In the root directory, create a `nginx.conf` file with the following content: + +[source,conf] +---- +user nginx; + +events { + worker_connections 1000; +} + +http { + server { + listen 8080; + location / { + proxy_pass http://tasks:8080; <1> + } + } +} +---- + +1. Route all traffic to our tasks application + +== Setting Application Deployment + +In the root directory, create a `docker-compose.yml` file with the following content: + +[source,yaml] +---- +version: '3' + +services: + tasks: <1> + image: quarkus-quickstarts/quartz:1.0 + build: + context: ./ + dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm} + environment: + QUARKUS_DATASOURCE_URL: jdbc:postgresql://postgres/quarkus_test + networks: + - tasks-network + depends_on: + - postgres + + nginx: <2> + image: nginx:1.17.6 + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - tasks + ports: + - 8080:8080 + networks: + - tasks-network + + postgres: <3> + image: postgres:11.3 + container_name: quarkus_test + environment: + - POSTGRES_USER=quarkus_test + - POSTGRES_PASSWORD=quarkus_test + - POSTGRES_DB=quarkus_test + ports: + - 5432:5432 + networks: + - tasks-network + +networks: + tasks-network: + driver: bridge +---- + +1. Define the tasks service +2. Define the nginx load balancer to route incoming traffic to an appropriate node +3. Define the configuration to run the database + +== Running the database + +In a separate terminal, run the below command: + +[source,shell] +---- +docker-compose up postgres <1> +---- + +1. Start the database instance using the configuration options supplied in the `docker-compose.yml` file + +== Run the application in Dev Mode + +Run the application with: `./mvnw quarkus:dev`. +After a few seconds, open another terminal and run `curl localhost:8080/tasks` to verify that we have at least one task created. + +As usual, the application can be packaged using `./mvnw clean package` and executed using the `-runner.jar` file. +You can also generate the native executable with `./mvnw clean package -Pnative`. + +== Packaging the application and run several instances + +The application can be packaged using `./mvnw clean package`. Once the build is successful, run the below command: + +[source,shell] +---- +docker-compose up --scale tasks=2 --scale nginx=1 <1> +---- + +1. Start two instances of the application and a load balancer + +After a few seconds, in another terminal, run `curl localhost:8080/tasks` to verify that tasks were only created at different instants and in an interval of 10 seconds. + +You can also generate the native executable with `./mvnw clean package -Pnative`. + +[[quartz-configuration-reference]] +== Quartz Configuration Reference + +include::{generated-dir}/config/quarkus-quartz.adoc[leveloffset=+1, opts=optional] diff --git a/docs/src/main/asciidoc/scheduler.adoc b/docs/src/main/asciidoc/scheduler.adoc index 6981be9a3e1a6..53d5423077ca6 100644 --- a/docs/src/main/asciidoc/scheduler.adoc +++ b/docs/src/main/asciidoc/scheduler.adoc @@ -10,6 +10,8 @@ include::./attributes.adoc[] Modern applications often need to run specific tasks periodically. In this guide, you learn how to schedule periodic tasks. +TIP: If you need a clustered scheduler use the link:quartz[Quartz extension]. + == Prerequisites To complete this guide, you need: diff --git a/extensions/quartz/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/quartz/runtime/src/main/resources/META-INF/quarkus-extension.yaml index a518bc3dfed0f..b34bb338a30c2 100644 --- a/extensions/quartz/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/quartz/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,11 +1,12 @@ --- -name: "Scheduler - tasks" +name: "Quartz" metadata: keywords: - "scheduler" + - "quartz" - "tasks" - "periodic-tasks" - guide: "https://quarkus.io/guides/scheduler" + guide: "https://quarkus.io/guides/quartz" categories: - "miscellaneous" status: "preview" From 9ea8569b6c03aa93a2713d8e2469d735f87e4500 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Sat, 30 Nov 2019 18:24:31 +0100 Subject: [PATCH 144/602] Deprecated isEmptyString replacement --- .../io/quarkus/it/spring/web/SpringControllerTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java index 13629f73f636e..2383590700d34 100644 --- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java +++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java @@ -1,7 +1,8 @@ package io.quarkus.it.spring.web; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.is; import java.util.Optional; @@ -136,7 +137,7 @@ public void testFirstResponseStatusHoldingException() { public void testSecondResponseStatusHoldingException() { RestAssured.when().get("/exception/second").then() .contentType("text/plain") - .body(isEmptyString()) + .body(is(emptyString())) .statusCode(503); } @@ -144,7 +145,7 @@ public void testSecondResponseStatusHoldingException() { public void testExceptionHandlerVoidReturnType() { RestAssured.when().get("/exception/void").then() .contentType("text/plain") - .body(isEmptyString()) + .body(is(emptyString())) .statusCode(400); } @@ -152,7 +153,7 @@ public void testExceptionHandlerVoidReturnType() { public void testExceptionHandlerWithoutResponseStatusOnExceptionOrMethod() { RestAssured.when().get("/exception/unannotated").then() .contentType("text/plain") - .body(isEmptyString()) + .body(is(emptyString())) .statusCode(204); } From 3e0582addc46e363fc7506cbd4dfafd7505d6714 Mon Sep 17 00:00:00 2001 From: Jose Quaresma Date: Sat, 30 Nov 2019 15:55:56 +0000 Subject: [PATCH 145/602] quarkus-quickstart-native now puts the built app at /home/quarkus/application --- docs/src/main/asciidoc/deploying-to-openshift-s2i.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/deploying-to-openshift-s2i.adoc b/docs/src/main/asciidoc/deploying-to-openshift-s2i.adoc index 75d8d6a7c628e..abacfa07a717b 100644 --- a/docs/src/main/asciidoc/deploying-to-openshift-s2i.adoc +++ b/docs/src/main/asciidoc/deploying-to-openshift-s2i.adoc @@ -90,8 +90,8 @@ The following command will create a chained build that is triggered whenever the oc new-build --name=minimal-quarkus-quickstart-native \ --docker-image=registry.access.redhat.com/ubi7-dev-preview/ubi-minimal \ --source-image=quarkus-quickstart-native \ - --source-image-path='/home/quarkus/quarkus-quickstart-1.0-SNAPSHOT-runner:.' \ - --dockerfile=$'FROM registry.access.redhat.com/ubi7-dev-preview/ubi-minimal:latest\nCOPY *-runner /application\nCMD /application\nEXPOSE 8080' \ + --source-image-path='/home/quarkus/application:.' \ + --dockerfile=$'FROM registry.access.redhat.com/ubi7-dev-preview/ubi-minimal:latest\nCOPY application /application\nCMD /application\nEXPOSE 8080' ---- To create a service from the minimal build run the following command: From f5776ed5b1e70ffb95491445a59c25fdc9b55a6a Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Sun, 1 Dec 2019 11:30:24 +0100 Subject: [PATCH 146/602] refactor: remove unused constants from ExtensionLoader --- .../main/java/io/quarkus/deployment/ExtensionLoader.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 009c873d332cf..a1bb53b30753a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -98,12 +98,6 @@ private ExtensionLoader() { } private static final Logger cfgLog = Logger.getLogger("io.quarkus.configuration"); - - public static final String BUILD_TIME_CONFIG = "io.quarkus.runtime.generated.BuildTimeConfig"; - public static final String BUILD_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.BuildTimeConfigRoot"; - public static final String RUN_TIME_CONFIG = "io.quarkus.runtime.generated.RunTimeConfig"; - public static final String RUN_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.RunTimeConfigRoot"; - private static final String CONFIG_ROOTS_LIST = "META-INF/quarkus-config-roots.list"; @SuppressWarnings("deprecation") From ec3697e5b4a4f49dfab90853c3347493da79cdeb Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 29 Nov 2019 20:44:07 +0200 Subject: [PATCH 147/602] Increase timeout for JDK builds Done because there have been sporadic timeouts in PRs --- ci-templates/stages.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 5d34e5f4cde87..00535cd13e19b 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -85,7 +85,7 @@ stages: - job: Build_JDK8_Linux displayName: 'Build JDK8 Linux' condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) - timeoutInMinutes: 60 + timeoutInMinutes: 75 pool: vmImage: 'Ubuntu 16.04' @@ -105,7 +105,7 @@ stages: - job: Windows_Build displayName: 'Windows JVM Build' condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) - timeoutInMinutes: 60 + timeoutInMinutes: 75 pool: # Always use hosted pool for windows vmImage: 'vs2017-win2016' @@ -127,7 +127,7 @@ stages: options: '-B --settings azure-mvn-settings.xml -Dno-native -Dno-format' - job: Build_JDK11_Linux - timeoutInMinutes: 60 + timeoutInMinutes: 75 condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) displayName: 'Linux JDK11 Build' pool: ${{parameters.poolSettings}} @@ -141,7 +141,7 @@ stages: - template: prepare-cache.yaml - job: Build_JDK12_Linux - timeoutInMinutes: 60 + timeoutInMinutes: 75 condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) displayName: 'Linux JDK12 Build' pool: ${{parameters.poolSettings}} From 7bfb25aa0fb014f77ebba80a942b2b2cb43ecac9 Mon Sep 17 00:00:00 2001 From: Dennis Baerten Date: Sun, 1 Dec 2019 08:36:26 +0100 Subject: [PATCH 148/602] Remove the unused property minLevel from CategoryConfig --- .../java/io/quarkus/runtime/logging/CategoryConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java index 136fc3686b219..60c5c4f78f917 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java @@ -6,12 +6,6 @@ @ConfigGroup public class CategoryConfig { - /** - * The minimum level that this category can be set to - */ - @ConfigItem(defaultValue = "inherit") - String minLevel; - /** * The log level level for this category */ From b4c609b86bde1b4682ef40088e55a340b935716c Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 27 Nov 2019 20:14:55 +0530 Subject: [PATCH 149/602] Use "file" scheme absolute URIs for "Class-Path" entries we create in the MANIFEST.MF of our -dev.jar Discussion about the semantics of this Class-Path attributes can be found at https://mail.openjdk.java.net/pipermail/core-libs-dev/2019-November/063491.html --- .../io/quarkus/dev/ClassLoaderCompiler.java | 17 ++++++++++++----- .../src/main/java/io/quarkus/maven/DevMojo.java | 15 ++------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java b/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java index 4a42569d1cdb3..47812a78b586d 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java +++ b/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java @@ -122,12 +122,19 @@ public ClassLoaderCompiler(ClassLoader classLoader, } Object classPath = mf.getMainAttributes().get(Attributes.Name.CLASS_PATH); if (classPath != null) { - for (String i : WHITESPACE_PATTERN.split(classPath.toString())) { + for (String classPathEntry : WHITESPACE_PATTERN.split(classPath.toString())) { + final URI cpEntryURI = new URI(classPathEntry); File f; - try { - f = Paths.get(new URI("file", null, "/", null).resolve(new URI(i))).toFile(); - } catch (URISyntaxException e) { - f = new File(file.getParentFile(), i); + // if it's a "file" scheme URI, then use the path as a file system path + // without the need to resolve it + if (cpEntryURI.isAbsolute() && cpEntryURI.getScheme().equals("file")) { + f = new File(cpEntryURI.getPath()); + } else { + try { + f = Paths.get(new URI("file", null, "/", null).resolve(cpEntryURI)).toFile(); + } catch (URISyntaxException e) { + f = new File(file.getParentFile(), classPathEntry); + } } if (f.exists()) { toParse.add(f.getAbsolutePath()); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 0fe778d55b60d..2aa5acca4ae0c 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -57,7 +57,6 @@ import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; -import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.dev.DevModeContext; import io.quarkus.dev.DevModeMain; import io.quarkus.maven.components.MavenVersionEnforcer; @@ -383,23 +382,13 @@ private void addProject(DevModeContext devModeContext, LocalProject localProject } private void addToClassPaths(StringBuilder classPathManifest, DevModeContext classPath, File file) { - URI uri = file.toPath().toAbsolutePath().toUri(); + final URI uri = file.toPath().toAbsolutePath().toUri(); try { classPath.getClassPath().add(uri.toURL()); } catch (MalformedURLException e) { throw new RuntimeException(e); } - String path = uri.getRawPath(); - if (PropertyUtils.isWindows()) { - if (path.length() > 2 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':') { - path = "/" + path; - } - } - classPathManifest.append(path); - if (file.isDirectory() && path.charAt(path.length() - 1) != '/') { - classPathManifest.append("/"); - } - classPathManifest.append(" "); + classPathManifest.append(uri).append(" "); } class DevModeRunner { From f459bcf538fc964ce0d63685cab7431164f3577f Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 29 Nov 2019 17:24:35 +1100 Subject: [PATCH 150/602] Vert.x is resumed when using non-default paths If the servlet context path is set then Undertow would be registered using a RouteBuildItem instead of a DefaultRouteBuildItem. This behaves differently in terms of resuming in the request, so if both the context path was set and security was in use the request body could disappear sometimes. --- .../ResteasyStandaloneBuildStep.java | 4 +- .../deployment/UndertowBuildStep.java | 2 +- .../undertow/test/TestIdentityController.java | 37 +++++++++++++++++++ .../undertow/test/TestIdentityProvider.java | 0 .../vertx/http/deployment/RouteBuildItem.java | 20 +++++++++- .../http/deployment/VertxHttpProcessor.java | 2 +- .../vertx/http/runtime/VertxHttpRecorder.java | 12 ++++-- .../src/main/resources/application.properties | 2 +- .../it/undertow/elytron/BaseAuthTest.java | 6 ++- .../elytron/WebXmlPermissionsTestCase.java | 14 +++---- 10 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityController.java create mode 100644 extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityProvider.java diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java index 9d82ed6286eed..9102d5b0bac0f 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java @@ -223,7 +223,7 @@ public void boot(ShutdownContextBuildItem shutdown, // We need to register a special handler for non-default deployment path (specified as application path or resteasyConfig.path) Handler handler = recorder.vertxRequestHandler(vertx.getVertx(), beanContainer.getValue()); // Exact match for resources matched to the root path - routes.produce(new RouteBuildItem(standalone.deploymentRootPath, handler)); + routes.produce(new RouteBuildItem(standalone.deploymentRootPath, handler, false)); String matchPath = standalone.deploymentRootPath; if (matchPath.endsWith("/")) { matchPath += "*"; @@ -231,7 +231,7 @@ public void boot(ShutdownContextBuildItem shutdown, matchPath += "/*"; } // Match paths that begin with the deployment path - routes.produce(new RouteBuildItem(matchPath, handler)); + routes.produce(new RouteBuildItem(matchPath, handler, false)); } boolean isVirtual = requireVirtual.isPresent(); diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index b485042e208c3..015f199cf4015 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -173,7 +173,7 @@ public ServiceStartBuildItem boot(UndertowDeploymentRecorder recorder, if (servletContextPathBuildItem.getServletContextPath().equals("/")) { undertowProducer.accept(new DefaultRouteBuildItem(ut)); } else { - routeProducer.produce(new RouteBuildItem(servletContextPathBuildItem.getServletContextPath() + "/*", ut)); + routeProducer.produce(new RouteBuildItem(servletContextPathBuildItem.getServletContextPath() + "/*", ut, false)); } return new ServiceStartBuildItem("undertow"); } diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityController.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityController.java new file mode 100644 index 0000000000000..1de68ddd50e0f --- /dev/null +++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityController.java @@ -0,0 +1,37 @@ +package io.quarkus.undertow.test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class TestIdentityController { + + public static final Map idenitities = new ConcurrentHashMap<>(); + + public static Builder resetRoles() { + idenitities.clear(); + return new Builder(); + } + + public static class Builder { + public Builder add(String username, String password, String... roles) { + idenitities.put(username, new TestIdentity(username, password, roles)); + return this; + } + } + + public static final class TestIdentity { + + public final String username; + public final String password; + public final Set roles; + + private TestIdentity(String username, String password, String... roles) { + this.username = username; + this.password = password; + this.roles = new HashSet<>(Arrays.asList(roles)); + } + } +} diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityProvider.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/TestIdentityProvider.java new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java index 326c7c6028c5d..2202c354cf63c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java @@ -15,21 +15,36 @@ public final class RouteBuildItem extends MultiBuildItem { private final Function routeFunction; private final Handler handler; private final HandlerType type; + private final boolean resume; - public RouteBuildItem(Function routeFunction, Handler handler, HandlerType type) { + public RouteBuildItem(Function routeFunction, Handler handler, HandlerType type, + boolean resume) { this.routeFunction = routeFunction; this.handler = handler; this.type = type; + this.resume = resume; + } + + public RouteBuildItem(Function routeFunction, Handler handler, HandlerType type) { + this(routeFunction, handler, type, true); } public RouteBuildItem(Function routeFunction, Handler handler) { this(routeFunction, handler, HandlerType.NORMAL); } + public RouteBuildItem(String route, Handler handler, HandlerType type, boolean resume) { + this(new BasicRoute(route), handler, type, resume); + } + public RouteBuildItem(String route, Handler handler, HandlerType type) { this(new BasicRoute(route), handler, type); } + public RouteBuildItem(String route, Handler handler, boolean resume) { + this(new BasicRoute(route), handler, HandlerType.NORMAL, resume); + } + public RouteBuildItem(String route, Handler handler) { this(new BasicRoute(route), handler); } @@ -46,4 +61,7 @@ public Function getRouteFunction() { return routeFunction; } + public boolean isResume() { + return resume; + } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 2ec89082b8ba1..2dcebfca9208d 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -91,7 +91,7 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder, RuntimeValue router = recorder.initializeRouter(vertx.getVertx(), launchModeBuildItem.getLaunchMode(), shutdown); for (RouteBuildItem route : routes) { - recorder.addRoute(router, route.getRouteFunction(), route.getHandler(), route.getType()); + recorder.addRoute(router, route.getRouteFunction(), route.getHandler(), route.getType(), route.isResume()); } return new VertxWebRouterBuildItem(router); diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index ccdbdc3c4a8d5..4b7b13dfbc37a 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -468,16 +468,20 @@ public void warnIfPortChanged(HttpConfiguration config, int port) { } public void addRoute(RuntimeValue router, Function route, Handler handler, - HandlerType blocking) { + HandlerType blocking, boolean resume) { Route vr = route.apply(router.getValue()); + Handler requestHandler = handler; + if (resume) { + requestHandler = new ResumeHandler(handler); + } if (blocking == HandlerType.BLOCKING) { - vr.blockingHandler(new ResumeHandler(handler)); + vr.blockingHandler(requestHandler); } else if (blocking == HandlerType.FAILURE) { - vr.failureHandler(new ResumeHandler(handler)); + vr.failureHandler(requestHandler); } else { - vr.handler(new ResumeHandler(handler)); + vr.handler(requestHandler); } } diff --git a/integration-tests/elytron-undertow/src/main/resources/application.properties b/integration-tests/elytron-undertow/src/main/resources/application.properties index 7d1579c82d686..20955e50fb574 100644 --- a/integration-tests/elytron-undertow/src/main/resources/application.properties +++ b/integration-tests/elytron-undertow/src/main/resources/application.properties @@ -6,4 +6,4 @@ quarkus.security.users.embedded.roles.mary=managers quarkus.security.users.embedded.users.poul=poul quarkus.security.users.embedded.roles.poul=interns quarkus.security.users.embedded.plain-text=true - +quarkus.servlet.context-path=/foo diff --git a/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthTest.java b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthTest.java index fc018be7887e7..fc503ff005301 100644 --- a/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthTest.java +++ b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthTest.java @@ -3,6 +3,7 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.is; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -12,6 +13,7 @@ class BaseAuthTest { @Test + @RepeatedTest(100) void testPost() { // This is a regression test in that we had a problem where the Vert.x request was not paused // before the authentication filters ran and the post message was thrown away by Vert.x because @@ -21,7 +23,7 @@ void testPost() { .body("Bill") .contentType(ContentType.TEXT) .when() - .post("/") + .post("/foo/") .then() .statusCode(200) .body(is("hello Bill")); @@ -32,7 +34,7 @@ void testGet() { given() .header("Authorization", "Basic am9objpqb2hu") .when() - .get("/") + .get("/foo/") .then() .statusCode(200) .body(is("hello")); diff --git a/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/WebXmlPermissionsTestCase.java b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/WebXmlPermissionsTestCase.java index 9bcb98cb0d73b..64c46ce2eae10 100644 --- a/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/WebXmlPermissionsTestCase.java +++ b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/WebXmlPermissionsTestCase.java @@ -21,7 +21,7 @@ void testPost() { .body("Bill") .contentType(ContentType.TEXT) .when() - .post("/") + .post("/foo/") .then() .statusCode(200) .body(is("hello Bill")); @@ -31,7 +31,7 @@ void testPost() { void testOpenApiNoPermissions() { given() .when() - .get("/openapi") + .get("/foo/openapi") .then() .statusCode(401); } @@ -41,7 +41,7 @@ void testOpenApiWithWrongAuth() { given() .header("Authorization", "Basic am9objpqb2hu") .when() - .get("/openapi") + .get("/foo/openapi") .then() .statusCode(403); } @@ -52,7 +52,7 @@ void testOpenApiWithAuth() { .auth() .basic("mary", "mary") .when() - .get("/openapi") + .get("/foo/openapi") .then() .statusCode(200); } @@ -62,7 +62,7 @@ void testSecuredServletWithWrongAuth() { given() .header("Authorization", "Basic am9objpqb2hu") .when() - .get("/secure/a") + .get("/foo/secure/a") .then() .statusCode(403); } @@ -71,7 +71,7 @@ void testSecuredServletWithWrongAuth() { void testSecuredServletWithNoAuth() { given() .when() - .get("/secure/a") + .get("/foo/secure/a") .then() .statusCode(401); } @@ -82,7 +82,7 @@ void testSecuredServletWithAuth() { .auth() .basic("mary", "mary") .when() - .get("/secure/a") + .get("/foo/secure/a") .then() .statusCode(200); } From 91d3310d88aecd8d6707564b0d396ff1d318cbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 29 Nov 2019 13:24:44 +0100 Subject: [PATCH 151/602] feat: query hint --- .../io/quarkus/hibernate/orm/panache/PanacheQuery.java | 9 +++++++++ .../hibernate/orm/panache/runtime/PanacheQueryImpl.java | 6 ++++++ .../main/java/io/quarkus/it/panache/TestEndpoint.java | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java index a0935fe22c9d1..3e1ac136ff607 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java @@ -123,6 +123,15 @@ public interface PanacheQuery { */ public PanacheQuery withLock(LockModeType lockModeType); + /** + * Set a query property or hint on the underlying JPA Query. + * + * @param hintName name of the property or hint. + * @param value value for the property or hint. + * @return this query, modified + */ + public PanacheQuery withHint(String hintName, Object value); + // Results /** diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java index d21ccec9d98b1..54ff38a33d257 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java @@ -97,6 +97,12 @@ public PanacheQuery withLock(LockModeType lockModeType) { return (PanacheQuery) this; } + @Override + public PanacheQuery withHint(String hintName, Object value) { + jpaQuery.setHint(hintName, value); + return (PanacheQuery) this; + } + // Results @Override diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java index 5f86e5b1d385d..353383ed6487f 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java @@ -18,6 +18,7 @@ import javax.ws.rs.core.MediaType; import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.jpa.QueryHints; import org.junit.jupiter.api.Assertions; import io.quarkus.hibernate.orm.panache.PanacheQuery; @@ -92,6 +93,11 @@ public String testModel() { Assertions.assertEquals(1, persons.size()); Assertions.assertEquals(person, persons.get(0)); + // next calls to this query will be cached + persons = Person.find("name = ?1", "stef").withHint(QueryHints.HINT_CACHEABLE, "true").list(); + Assertions.assertEquals(1, persons.size()); + Assertions.assertEquals(person, persons.get(0)); + persons = Person.list("name = ?1", "stef"); Assertions.assertEquals(1, persons.size()); Assertions.assertEquals(person, persons.get(0)); From bede473dda27a495ce1a3baf6823de4d1abfb8de Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 29 Nov 2019 16:47:12 +0200 Subject: [PATCH 152/602] Add support for auto-registering Jackson modules --- .../jackson/deployment/JacksonProcessor.java | 40 +++++++++++++++++-- .../quarkus/jackson/ObjectMapperProducer.java | 4 -- .../spi/ClassPathJacksonModuleBuildItem.java | 23 +++++++++++ integration-tests/jackson/model/pom.xml | 10 +---- .../model/InheritedModelWithBuilderBase.java | 21 ++-------- .../model/ModelWithBuilder.java | 21 ++-------- .../model/RegisteredPojoModel.java | 20 +--------- integration-tests/jackson/service/pom.xml | 5 +++ .../reproducer/MyObjectMapperCustomizer.java | 23 +++++++++++ .../reproducer/ObjectMapperModulesTest.java | 31 ++++++++++++++ 10 files changed, 128 insertions(+), 70 deletions(-) create mode 100644 extensions/jackson/spi/src/main/java/io/quarkus/jackson/spi/ClassPathJacksonModuleBuildItem.java create mode 100644 integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java create mode 100644 integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index ec7837e443642..71c2239c4bce1 100755 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -4,6 +4,7 @@ import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; import static org.jboss.jandex.AnnotationTarget.Kind.METHOD; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,6 +43,7 @@ import io.quarkus.gizmo.ResultHandle; import io.quarkus.jackson.ObjectMapperCustomizer; import io.quarkus.jackson.ObjectMapperProducer; +import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem; import io.quarkus.jackson.spi.JacksonModuleBuildItem; public class JacksonProcessor { @@ -50,6 +52,14 @@ public class JacksonProcessor { private static final DotName JSON_SERIALIZE = DotName.createSimple(JsonSerialize.class.getName()); private static final DotName BUILDER_VOID = DotName.createSimple(Void.class.getName()); + private static final String TIME_MODULE = "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"; + private static final String JDK8_MODULE = "com.fasterxml.jackson.datatype.jdk8.Jdk8Module"; + private static final String PARAMETER_NAMES_MODULE = "com.fasterxml.jackson.module.paramnames.ParameterNamesModule"; + + // this list can probably be enriched with more modules + private static final List MODULES_NAMES_TO_AUTO_REGISTER = Arrays.asList(TIME_MODULE, JDK8_MODULE, + PARAMETER_NAMES_MODULE); + @Inject BuildProducer reflectiveClass; @@ -130,12 +140,28 @@ private void addReflectiveClass(boolean methods, boolean fields, String... class reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className)); } - // Generate a ObjectMapperCustomizer bean that registers each serializer / deserializer with ObjectMapper + @BuildStep + void autoRegisterModules(BuildProducer classPathJacksonModules) { + for (String module : MODULES_NAMES_TO_AUTO_REGISTER) { + registerModuleIfOnClassPath(module, classPathJacksonModules); + } + } + + private void registerModuleIfOnClassPath(String moduleClassName, + BuildProducer classPathJacksonModules) { + try { + Class.forName(moduleClassName, false, Thread.currentThread().getContextClassLoader()); + classPathJacksonModules.produce(new ClassPathJacksonModuleBuildItem(moduleClassName)); + } catch (Exception ignored) { + } + } + + // Generate a ObjectMapperCustomizer bean that registers each serializer / deserializer as well as detected modules with the ObjectMapper @BuildStep void generateCustomizer(BuildProducer generatedBeans, - List jacksonModules) { + List jacksonModules, List classPathJacksonModules) { - if (jacksonModules.isEmpty()) { + if (jacksonModules.isEmpty() && classPathJacksonModules.isEmpty()) { return; } @@ -196,6 +222,14 @@ void generateCustomizer(BuildProducer generatedBeans, objectMapper, module); } + for (ClassPathJacksonModuleBuildItem classPathJacksonModule : classPathJacksonModules) { + ResultHandle module = customize + .newInstance(MethodDescriptor.ofConstructor(classPathJacksonModule.getModuleClassName())); + customize.invokeVirtualMethod( + MethodDescriptor.ofMethod(ObjectMapper.class, "registerModule", ObjectMapper.class, Module.class), + objectMapper, module); + } + customize.returnValue(null); } } diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java index b5be5c822b68f..ddc397f7e0ba3 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java @@ -6,9 +6,6 @@ import javax.inject.Singleton; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import io.quarkus.arc.DefaultBean; @@ -20,7 +17,6 @@ public class ObjectMapperProducer { @Produces public ObjectMapper objectMapper(Instance customizers) { ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules(new Jdk8Module(), new JavaTimeModule(), new ParameterNamesModule()); for (ObjectMapperCustomizer customizer : customizers) { customizer.customize(objectMapper); } diff --git a/extensions/jackson/spi/src/main/java/io/quarkus/jackson/spi/ClassPathJacksonModuleBuildItem.java b/extensions/jackson/spi/src/main/java/io/quarkus/jackson/spi/ClassPathJacksonModuleBuildItem.java new file mode 100644 index 0000000000000..bedf70a98a80b --- /dev/null +++ b/extensions/jackson/spi/src/main/java/io/quarkus/jackson/spi/ClassPathJacksonModuleBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.jackson.spi; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * BuildItem used to signal that some Jackson module has been detected on the classpath + * + * The modules are then registered with the ObjectMapper. + * + * Note: Modules are assumed to have a default constructor + */ +public final class ClassPathJacksonModuleBuildItem extends MultiBuildItem { + + private final String moduleClassName; + + public ClassPathJacksonModuleBuildItem(String moduleClassName) { + this.moduleClassName = moduleClassName; + } + + public String getModuleClassName() { + return moduleClassName; + } +} diff --git a/integration-tests/jackson/model/pom.xml b/integration-tests/jackson/model/pom.xml index a41b4a609f544..a43fe3c70e557 100644 --- a/integration-tests/jackson/model/pom.xml +++ b/integration-tests/jackson/model/pom.xml @@ -36,7 +36,7 @@ io.quarkus - quarkus-core + quarkus-arc @@ -57,14 +57,6 @@ com.fasterxml.jackson.module jackson-module-parameter-names - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - org.junit.jupiter diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java index a3eec8478ba5b..acba0bad18d96 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java +++ b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java @@ -2,14 +2,10 @@ import java.util.Optional; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +import io.quarkus.arc.Arc; /** * Model class with inheritance and builder. @@ -98,18 +94,7 @@ public B withVersion(int version) { // ------------------------------------------------------------------------- protected static ObjectMapper getObjectMapper() { - if (null == objectMapper) { - objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) - .registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()); - } - return objectMapper; + return Arc.container().instance(ObjectMapper.class).get(); } } diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java index 2e5fddb3bed04..cf0cddca82bb0 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java +++ b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java @@ -2,17 +2,13 @@ import java.io.IOException; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +import io.quarkus.arc.Arc; /** * Simple model class. @@ -136,18 +132,7 @@ public ModelWithBuilder build() { // ------------------------------------------------------------------------- private static ObjectMapper getObjectMapper() { - if (null == objectMapper) { - objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) - .registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()); - } - return objectMapper; + return Arc.container().instance(ObjectMapper.class).get(); } } diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java index 1997b30a8905d..8e72ae371bed1 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java +++ b/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java @@ -2,14 +2,9 @@ import java.io.IOException; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import io.quarkus.arc.Arc; import io.quarkus.runtime.annotations.RegisterForReflection; /** @@ -89,18 +84,7 @@ public void setValue(String value) { // ------------------------------------------------------------------------- private static ObjectMapper getObjectMapper() { - if (null == objectMapper) { - objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) - .registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()); - } - return objectMapper; + return Arc.container().instance(ObjectMapper.class).get(); } } diff --git a/integration-tests/jackson/service/pom.xml b/integration-tests/jackson/service/pom.xml index 7f9c0191e531b..2acbe606daa66 100644 --- a/integration-tests/jackson/service/pom.xml +++ b/integration-tests/jackson/service/pom.xml @@ -39,6 +39,11 @@ rest-assured test + + org.assertj + assertj-core + test + diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java b/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java new file mode 100644 index 0000000000000..5e625bbcb4aff --- /dev/null +++ b/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java @@ -0,0 +1,23 @@ +package io.quarkus.reproducer; + +import javax.inject.Singleton; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import io.quarkus.jackson.ObjectMapperCustomizer; + +@Singleton +public class MyObjectMapperCustomizer implements ObjectMapperCustomizer { + + @Override + public void customize(ObjectMapper objectMapper) { + objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .setSerializationInclusion(JsonInclude.Include.NON_ABSENT); + } +} diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java b/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java new file mode 100644 index 0000000000000..6b1cfa05e3ac2 --- /dev/null +++ b/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java @@ -0,0 +1,31 @@ +package io.quarkus.reproducer; + +import static org.assertj.core.api.Assertions.*; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ObjectMapperModulesTest { + + private static final Object JDK8_MODULE_TYPE_ID = new Jdk8Module().getTypeId(); + private static final Object JAVA_TIME_MODULE_TYPE_ID = new JavaTimeModule().getTypeId(); + private static final Object PARAMETER_NAMES_MODULE_TYPE_ID = new ParameterNamesModule().getTypeId(); + + @Inject + ObjectMapper objectMapper; + + @Test + public void testExpectedModulesAreRegistered() { + assertThat(objectMapper.getRegisteredModuleIds()) + .contains(JDK8_MODULE_TYPE_ID, JAVA_TIME_MODULE_TYPE_ID, PARAMETER_NAMES_MODULE_TYPE_ID); + } +} From ac806ecfa174ebfe2b3246dca1e543caeee2272f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 29 Nov 2019 17:27:34 +0200 Subject: [PATCH 153/602] Ensure that the Kotlin and Scala Jackson modules are auto-registered if present --- docs/src/main/asciidoc/kotlin.adoc | 7 ++++++- extensions/kotlin/deployment/pom.xml | 4 ++++ .../kotlin/deployment/KotlinProcessor.java | 18 ++++++++++++++++++ extensions/scala/deployment/pom.xml | 4 ++++ .../scala/deployment/ScalaProcessor.java | 18 ++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index 625ce474f06a3..8c4ef69dffad4 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -389,4 +389,9 @@ One thing to note is that the live reload feature is not available when making c == Packaging the application -As usual, the application can be packaged using `./mvnw clean package` and executed using the `-runner.jar` file. You can also build the native executable using `./mvnw package -Pnative`, or `./gradlew buildNative`. \ No newline at end of file +As usual, the application can be packaged using `./mvnw clean package` and executed using the `-runner.jar` file. You can also build the native executable using `./mvnw package -Pnative`, or `./gradlew buildNative`. + +== Kotlin and Jackson + +If the `com.fasterxml.jackson.module:jackson-module-kotlin` dependency and the `quarkus-jackson` extension (or the `quarkus-resteasy-extension`) have been added to project, +then Quarkus automatically registers the `KotlinModule` to the `ObjectMapper` bean (see link:rest-json#Jackson[this] guide for more details). \ No newline at end of file diff --git a/extensions/kotlin/deployment/pom.xml b/extensions/kotlin/deployment/pom.xml index 5eda3361dc307..52d5f703acf46 100644 --- a/extensions/kotlin/deployment/pom.xml +++ b/extensions/kotlin/deployment/pom.xml @@ -19,6 +19,10 @@ io.quarkus quarkus-kotlin + + io.quarkus + quarkus-jackson-spi + io.quarkus diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java index 3596ca27d9651..e26857051a35d 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java @@ -1,12 +1,30 @@ package io.quarkus.kotlin.deployment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem; public class KotlinProcessor { + private static final String KOTLIN_JACKSON_MODULE = "com.fasterxml.jackson.module.kotlin.KotlinModule"; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.KOTLIN); } + + /* + * Register the Kotlin Jackson module if that has been added to the classpath + * Producing the BuildItem is entirely safe since if quarkus-jackson is not on the classpath + * the BuildItem will just be ignored + */ + @BuildStep + void registerKotlinJacksonModule(BuildProducer classPathJacksonModules) { + try { + Class.forName(KOTLIN_JACKSON_MODULE, false, Thread.currentThread().getContextClassLoader()); + classPathJacksonModules.produce(new ClassPathJacksonModuleBuildItem(KOTLIN_JACKSON_MODULE)); + } catch (Exception ignored) { + } + } } diff --git a/extensions/scala/deployment/pom.xml b/extensions/scala/deployment/pom.xml index 960847e8873bf..1de5ed080bc47 100644 --- a/extensions/scala/deployment/pom.xml +++ b/extensions/scala/deployment/pom.xml @@ -19,6 +19,10 @@ io.quarkus quarkus-scala + + io.quarkus + quarkus-jackson-spi + io.quarkus diff --git a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaProcessor.java b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaProcessor.java index 61c0ddbb7a74d..2b0d8729cdcef 100644 --- a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaProcessor.java +++ b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaProcessor.java @@ -1,12 +1,30 @@ package io.quarkus.scala.deployment; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem; public class ScalaProcessor { + private static final String SCALA_JACKSON_MODULE = "com.fasterxml.jackson.module.scala.DefaultScalaModule"; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.SCALA); } + + /* + * Register the Scala Jackson module if that has been added to the classpath + * Producing the BuildItem is entirely safe since if quarkus-jackson is not on the classpath + * the BuildItem will just be ignored + */ + @BuildStep + void registerScalaJacksonModule(BuildProducer classPathJacksonModules) { + try { + Class.forName(SCALA_JACKSON_MODULE, false, Thread.currentThread().getContextClassLoader()); + classPathJacksonModules.produce(new ClassPathJacksonModuleBuildItem(SCALA_JACKSON_MODULE)); + } catch (Exception ignored) { + } + } } From 12d6e9dd7039c7d20459bc610344cd54436d9212 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Thu, 28 Nov 2019 23:30:43 +0100 Subject: [PATCH 154/602] Remove GraalVM internal class usage for JDK11 compatibility --- .../steps/NativeImageAutoFeatureStep.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index 4fd7fc1272246..e7706d74122ae 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -22,12 +22,11 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; -import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -58,9 +57,10 @@ public class NativeImageAutoFeatureStep { private static final String GRAAL_AUTOFEATURE = "io/quarkus/runner/AutoFeature"; private static final MethodDescriptor IMAGE_SINGLETONS_LOOKUP = ofMethod(ImageSingletons.class, "lookup", Object.class, Class.class); - private static final MethodDescriptor INITIALIZE_AT_RUN_TIME = ofMethod(RuntimeClassInitializationSupport.class, - "initializeAtRunTime", void.class, Class.class, String.class); - private static final MethodDescriptor RERUN_INITIALIZATION = ofMethod(RuntimeClassInitializationSupport.class, + private static final MethodDescriptor INITIALIZE_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class, + "initializeAtRunTime", void.class, Class[].class); + private static final MethodDescriptor RERUN_INITIALIZATION = ofMethod( + "org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport", "rerunInitialization", void.class, Class.class, String.class); static final String RUNTIME_REFLECTION = RuntimeReflection.class.getName(); static final String BEFORE_ANALYSIS_ACCESS = Feature.BeforeAnalysisAccess.class.getName(); @@ -135,39 +135,37 @@ public void write(String s, byte[] bytes) { cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); } - ResultHandle initSingleton = overallCatch.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, - overallCatch.loadClass(RuntimeClassInitializationSupport.class)); - ResultHandle quarkus = overallCatch.load("Quarkus"); - if (!runtimeInitializedClassBuildItems.isEmpty()) { ResultHandle thisClass = overallCatch.loadClass(GRAAL_AUTOFEATURE); ResultHandle cl = overallCatch.invokeVirtualMethod(ofMethod(Class.class, "getClassLoader", ClassLoader.class), thisClass); - for (String i : runtimeInitializedClassBuildItems.stream().map(RuntimeInitializedClassBuildItem::getClassName) - .collect(Collectors.toList())) { + ResultHandle classes = overallCatch.newArray(Class.class, + overallCatch.load(runtimeInitializedClassBuildItems.size())); + for (int i = 0; i < runtimeInitializedClassBuildItems.size(); i++) { TryBlock tc = overallCatch.tryBlock(); ResultHandle clazz = tc.invokeStaticMethod( ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load(i), tc.load(false), cl); - tc.invokeInterfaceMethod(INITIALIZE_AT_RUN_TIME, initSingleton, clazz, quarkus); - + tc.load(runtimeInitializedClassBuildItems.get(i).getClassName()), tc.load(false), cl); + tc.writeArrayValue(classes, i, clazz); CatchBlockCreator cc = tc.addCatch(Throwable.class); cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); } - + overallCatch.invokeStaticMethod(INITIALIZE_AT_RUN_TIME, classes); } // hack in reinitialization of process info classes - { + if (!runtimeReinitializedClassBuildItems.isEmpty()) { ResultHandle thisClass = overallCatch.loadClass(GRAAL_AUTOFEATURE); ResultHandle cl = overallCatch.invokeVirtualMethod(ofMethod(Class.class, "getClassLoader", ClassLoader.class), thisClass); - for (String i : runtimeReinitializedClassBuildItems.stream().map(RuntimeReinitializedClassBuildItem::getClassName) - .collect(Collectors.toList())) { + ResultHandle initSingleton = overallCatch.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP, + overallCatch.loadClass("org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport")); + ResultHandle quarkus = overallCatch.load("Quarkus"); + for (RuntimeReinitializedClassBuildItem runtimeReinitializedClass : runtimeReinitializedClassBuildItems) { TryBlock tc = overallCatch.tryBlock(); ResultHandle clazz = tc.invokeStaticMethod( ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load(i), tc.load(false), cl); + tc.load(runtimeReinitializedClass.getClassName()), tc.load(false), cl); tc.invokeInterfaceMethod(RERUN_INITIALIZATION, initSingleton, clazz, quarkus); CatchBlockCreator cc = tc.addCatch(Throwable.class); From 5f7ef212e23fcaf6a900eac16eb9daab9b11cdf4 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 5 Nov 2019 00:12:50 +0100 Subject: [PATCH 155/602] generate a documentation file for each config root and general config items (non config group Fixes #5144 Follows up #5044 --- .../annotation/processor/Constants.java | 1 - .../ExtensionAnnotationProcessor.java | 13 +- .../generate_doc/ConfigDoItemFinder.java | 16 ++- .../ConfigDocGeneratedOutput.java | 70 +++++++++++ .../processor/generate_doc/ConfigDocItem.java | 11 ++ .../generate_doc/ConfigDocItemScanner.java | 83 +++++++----- .../processor/generate_doc/ConfigDocKey.java | 13 +- .../generate_doc/ConfigDocWriter.java | 45 +------ .../generate_doc/DocGeneratorUtil.java | 118 +++++++++++++++--- .../ScannedConfigDocsItemHolder.java | 36 ++++-- .../generate_doc/DocGeneratorUtilTest.java | 37 +++++- .../main/asciidoc/building-native-image.adoc | 2 +- docs/src/main/asciidoc/logging.adoc | 2 +- docs/src/main/asciidoc/maven-tooling.adoc | 2 +- .../docs/generation/AllConfigGenerator.java | 30 +++-- 15 files changed, 344 insertions(+), 135 deletions(-) create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocGeneratedOutput.java diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index 6aa0ae55fcffd..d5025d22624ed 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -17,7 +17,6 @@ final public class Constants { public static final char DOT = '.'; public static final String EMPTY = ""; public static final String DASH = "-"; - public static final String CORE = "core-"; public static final String ADOC_EXTENSION = ".adoc"; public static final String DIGIT_OR_LOWERCASE = "^[a-z0-9]+$"; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index 5ab400889ac41..843d0ccdb0308 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -65,9 +65,10 @@ import org.jboss.jdeparser.JType; import org.jboss.jdeparser.JTypes; +import io.quarkus.annotation.processor.generate_doc.ConfigDocGeneratedOutput; import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner; import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter; -import io.quarkus.annotation.processor.generate_doc.ScannedConfigDocsItemHolder; +import io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil; public class ExtensionAnnotationProcessor extends AbstractProcessor { @@ -235,12 +236,12 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) } try { - final ScannedConfigDocsItemHolder scannedConfigDocsItemHolder = configDocItemScanner + final Set outputs = configDocItemScanner .scanExtensionsConfigurationItems(javaDocProperties); - - configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getAllConfigItemsPerExtension(), - true); // generate extension doc with search engine activate - configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getConfigGroupConfigItems(), false); // generate config group docs with search engine deactivated + for (ConfigDocGeneratedOutput output : outputs) { + DocGeneratorUtil.sort(output.getConfigDocItems()); // sort before writing + configDocWriter.writeAllExtensionConfigDocumentation(output); + } } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate extension doc: " + e); return; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index 4b10bd2c3bbd3..b2ce89ede927a 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -57,7 +57,7 @@ ScannedConfigDocsItemHolder findInMemoryConfigurationItems() { final int sectionLevel = 2; final List configDocItems = recursivelyFindConfigItems(element, configRootInfo.getName(), configRootInfo.getConfigPhase(), false, sectionLevel); - holder.addToAllConfigItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); + holder.addConfigRootItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); } return holder; @@ -227,6 +227,7 @@ private List recursivelyFindConfigItems(Element element, String p configDocKey.setType(type); configDocKey.setList(list); configDocKey.setOptional(optional); + configDocKey.setWithinAConfigGroup(sectionLevel > 2); configDocKey.setConfigPhase(configPhase); configDocKey.setDefaultValue(defaultValue); configDocKey.setDocMapKey(configDocMapKey); @@ -244,19 +245,16 @@ private List recursivelyFindConfigItems(Element element, String p private List recordConfigItemsFromConfigGroup(ConfigPhase configPhase, String name, Element configGroup, ConfigDocSection configSection, boolean withinAMap, int sectionLevel) { - final List groupConfigItems; final List configDocItems = new ArrayList<>(); - - if (configSection != null) { + final List groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, + sectionLevel + 1); + if (configSection == null) { + configDocItems.addAll(groupConfigItems); + } else { final ConfigDocItem configDocItem = new ConfigDocItem(); configDocItem.setConfigDocSection(configSection); configDocItems.add(configDocItem); - groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, - sectionLevel + 1); configSection.addConfigDocItems(groupConfigItems); - } else { - groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, sectionLevel); - configDocItems.addAll(groupConfigItems); } String configGroupName = configGroup.asType().toString(); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocGeneratedOutput.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocGeneratedOutput.java new file mode 100644 index 0000000000000..95715a245e897 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocGeneratedOutput.java @@ -0,0 +1,70 @@ +package io.quarkus.annotation.processor.generate_doc; + +import java.util.List; +import java.util.Objects; + +import io.quarkus.annotation.processor.Constants; + +public class ConfigDocGeneratedOutput { + private final String fileName; + private final boolean searchable; + private final boolean hasAnchorPrefix; + private final List configDocItems; + + public ConfigDocGeneratedOutput(String fileName, boolean searchable, List configDocItems, + boolean hasAnchorPrefix) { + this.fileName = fileName; + this.searchable = searchable; + this.configDocItems = configDocItems; + this.hasAnchorPrefix = hasAnchorPrefix; + } + + public String getFileName() { + return fileName; + } + + public boolean isSearchable() { + return searchable; + } + + public List getConfigDocItems() { + return configDocItems; + } + + public String getAnchorPrefix() { + if (!hasAnchorPrefix) { + return Constants.EMPTY; + } + + String anchorPrefix = fileName; + if (fileName.endsWith(Constants.ADOC_EXTENSION)) { + anchorPrefix = anchorPrefix.substring(0, anchorPrefix.length() - 5); + } + + return anchorPrefix + "_"; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ConfigDocGeneratedOutput that = (ConfigDocGeneratedOutput) o; + return Objects.equals(fileName, that.fileName); + } + + @Override + public int hashCode() { + return Objects.hash(fileName); + } + + @Override + public String toString() { + return "ConfigItemsOutput{" + + "fileName='" + fileName + '\'' + + ", searchable=" + searchable + + ", configDocItems=" + configDocItems + + '}'; + } +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItem.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItem.java index a821304d39fa2..3b675205ef8b8 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItem.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItem.java @@ -89,6 +89,17 @@ public boolean isWithinAMap() { return false; } + @JsonIgnore + public boolean isWithinAConfigGroup() { + if (isConfigSection()) { + return true; + } else if (isConfigKey() && configDocKey.isWithinAConfigGroup()) { + return true; + } + + return false; + } + /** * TODO determine section ordering * diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java index 6f49b204b9b8d..3bbaf9f63a768 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java @@ -1,6 +1,7 @@ package io.quarkus.annotation.processor.generate_doc; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigRootDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.deriveConfigRootName; @@ -98,15 +99,11 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { } } - /** - * Return a data structure which contains two maps of config items. - * 1. A map of all extensions config items accessible via - * {@link ScannedConfigDocsItemHolder#getAllConfigItemsPerExtension()} - * 2. a map of all config groups config items accessible via {@link ScannedConfigDocsItemHolder#getConfigGroupConfigItems()} - */ - public ScannedConfigDocsItemHolder scanExtensionsConfigurationItems(Properties javaDocProperties) + public Set scanExtensionsConfigurationItems(Properties javaDocProperties) throws IOException { + Set configDocGeneratedOutputs = new HashSet<>(); + final ConfigDoItemFinder configDoItemFinder = new ConfigDoItemFinder(configRoots, configGroups, javaDocProperties); final ScannedConfigDocsItemHolder inMemoryScannedItemsHolder = configDoItemFinder.findInMemoryConfigurationItems(); @@ -122,11 +119,16 @@ public ScannedConfigDocsItemHolder scanExtensionsConfigurationItems(Properties j configurationRootsParExtensionFileName); } - Map> allConfigItemsPerExtension = computeAllExtensionConfigItems(inMemoryScannedItemsHolder, + Set allConfigItemsPerExtension = generateAllConfigItemsOutputs(inMemoryScannedItemsHolder, allExtensionGeneratedDocs, configurationRootsParExtensionFileName); - Map> configGroupConfigItems = computeConfigGroupFilesNames(inMemoryScannedItemsHolder); + Set configGroupConfigItems = generateAllConfigGroupOutputs(inMemoryScannedItemsHolder); + Set configRootConfigItems = generateAllConfigRootOutputs(inMemoryScannedItemsHolder); - return new ScannedConfigDocsItemHolder(allConfigItemsPerExtension, configGroupConfigItems); + configDocGeneratedOutputs.addAll(configGroupConfigItems); + configDocGeneratedOutputs.addAll(allConfigItemsPerExtension); + configDocGeneratedOutputs.addAll(configRootConfigItems); + + return configDocGeneratedOutputs; } private void createOutputFolder() throws IOException { @@ -196,7 +198,7 @@ private void updateConfigurationRootsList(Properties configurationRootsParExtens private void updateScannedExtensionArtifactFiles(ScannedConfigDocsItemHolder inMemoryScannedItemsHolder, Properties allExtensionGeneratedDocs, Properties configurationRootsParExtensionFileName) throws IOException { - for (Map.Entry> entry : inMemoryScannedItemsHolder.getAllConfigItemsPerExtension() + for (Map.Entry> entry : inMemoryScannedItemsHolder.getConfigRootConfigItems() .entrySet()) { String serializableConfigRootDoc = OBJECT_MAPPER.writeValueAsString(entry.getValue()); allExtensionGeneratedDocs.put(entry.getKey(), serializableConfigRootDoc); @@ -220,13 +222,10 @@ private void updateScannedExtensionArtifactFiles(ScannedConfigDocsItemHolder inM } } - /** - * returns a Map of with extension generated file name and the list of its associated config items. - */ - private Map> computeAllExtensionConfigItems( + private Set generateAllConfigItemsOutputs( ScannedConfigDocsItemHolder inMemoryScannedItemsHolder, Properties allExtensionGeneratedDocs, Properties configurationRootsParExtensionFileName) throws IOException { - Map> configItemsParExtensionFileNames = new HashMap<>(); + Set outputs = new HashSet<>(); Set extensionFileNamesToGenerate = processorClassMembers .stream() @@ -241,35 +240,59 @@ private Map> computeAllExtensionConfigItems( } String[] extensionConfigRoots = extensionConfigRootsProperty.split(EXTENSION_LIST_SEPARATOR); + List extensionConfigItems = new ArrayList<>(); + for (String configRoot : extensionConfigRoots) { - List configDocItems = inMemoryScannedItemsHolder.getAllConfigItemsPerExtension().get(configRoot); + List configDocItems = inMemoryScannedItemsHolder.getConfigRootConfigItems().get(configRoot); if (configDocItems == null) { String serializedContent = allExtensionGeneratedDocs.getProperty(configRoot); configDocItems = OBJECT_MAPPER.readValue(serializedContent, new TypeReference>() { }); } - final List existingConfigDocItems = configItemsParExtensionFileNames - .computeIfAbsent(extensionFileName, (key) -> new ArrayList<>()); - DocGeneratorUtil.appendConfigItemsIntoExistingOnes(existingConfigDocItems, configDocItems); + DocGeneratorUtil.appendConfigItemsIntoExistingOnes(extensionConfigItems, configDocItems); } + + outputs.add(new ConfigDocGeneratedOutput(extensionFileName, true, extensionConfigItems, true)); + + List generalConfigItems = extensionConfigItems + .stream() + .filter(ConfigDocItem::isWithinAConfigGroup) + .collect(Collectors.toList()); + + if (!generalConfigItems.isEmpty()) { + String fileName = extensionFileName.replaceAll("\\.adoc$", "-general-config-items.adoc"); + outputs.add(new ConfigDocGeneratedOutput(fileName, false, generalConfigItems, true)); + } + } - return configItemsParExtensionFileNames; + return outputs; } - /** - * returns a Map of with config group generated file name and the list of its associated config items. - */ - private Map> computeConfigGroupFilesNames( + private Set generateAllConfigGroupOutputs( ScannedConfigDocsItemHolder inMemoryScannedItemsHolder) { - Map> configItemsParConfigGroupFileNames = new HashMap<>(); - for (Map.Entry> entry : inMemoryScannedItemsHolder.getConfigGroupConfigItems().entrySet()) { - String extensionDocFileName = computeConfigGroupDocFileName(entry.getKey()); - configItemsParConfigGroupFileNames.put(extensionDocFileName, entry.getValue()); + + return inMemoryScannedItemsHolder + .getConfigGroupConfigItems() + .entrySet() + .stream() + .map(entry -> new ConfigDocGeneratedOutput(computeConfigGroupDocFileName(entry.getKey()), false, + entry.getValue(), true)) + .collect(Collectors.toSet()); + } + + private Set generateAllConfigRootOutputs(ScannedConfigDocsItemHolder inMemoryScannedItemsHolder) { + Map> configRootConfigItems = inMemoryScannedItemsHolder.getConfigRootConfigItems(); + Set outputs = new HashSet<>(); + for (ConfigRootInfo configRootInfo : configRoots) { + String clazz = configRootInfo.getClazz().getQualifiedName().toString(); + List configDocItems = configRootConfigItems.get(clazz); + String fileName = computeConfigRootDocFileName(clazz, configRootInfo.getName()); + outputs.add(new ConfigDocGeneratedOutput(fileName, false, configDocItems, true)); } - return configItemsParConfigGroupFileNames; + return outputs; } /** diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java index 72525c41f75e0..9b2015a7e291e 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocKey.java @@ -21,6 +21,7 @@ final public class ConfigDocKey implements ConfigDocElement, Comparable allItems) + public void writeAllExtensionConfigDocumentation(ConfigDocGeneratedOutput output) throws IOException { - generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve("all-config.adoc"), "", true, allItems); - } - - /** - * Sort docs keys. The sorted list will contain the properties in the following order - * - 1. Map config items as last elements of the generated docs. - * - 2. Build time properties will come first. - * - 3. Otherwise respect source code declaration order. - * - 4. Elements within a configuration section will appear at the end of the generated doc while preserving described in - * 1-4. - */ - public static void sort(List configDocItems) { - Collections.sort(configDocItems); - for (ConfigDocItem configDocItem : configDocItems) { - if (configDocItem.isConfigSection()) { - sort(configDocItem.getConfigDocSection().getConfigDocItems()); - } - } + generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve(output.getFileName()), output.getAnchorPrefix(), + output.isSearchable(), output.getConfigDocItems()); } /** diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java index fe88cd3ef7fcf..27d16a858222d 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java @@ -1,6 +1,7 @@ package io.quarkus.annotation.processor.generate_doc; import java.time.Duration; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -17,9 +18,10 @@ import io.quarkus.annotation.processor.Constants; public class DocGeneratorUtil { + private static final String CORE = "core"; private static final String CONFIG = "Config"; private static final String CONFIGURATION = "Configuration"; - private static String CONFIG_GROUP_PREFIX = "config-group-"; + private static String CONFIG_GROUP_DOC_PREFIX = "config-group-"; static final String VERTX_JAVA_DOC_SITE = "https://vertx.io/docs/apidocs/"; static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/javase/8/docs/api/"; static final String AGROAL_API_JAVA_DOC_SITE = "https://jar-download.com/javaDoc/io.agroal/agroal-api/1.5/index.html?"; @@ -217,12 +219,12 @@ public String next() { }; } - static String join(String delim, Iterator it) { + static String join(Iterator it) { final StringBuilder b = new StringBuilder(); if (it.hasNext()) { b.append(it.next()); while (it.hasNext()) { - b.append(delim); + b.append("-"); b.append(it.next()); } } @@ -230,7 +232,7 @@ static String join(String delim, Iterator it) { } static String hyphenate(String orig) { - return join("-", lowerCase(camelHumpsIterator(orig))); + return join(lowerCase(camelHumpsIterator(orig))); } static String hyphenateEnumValue(String orig) { @@ -278,10 +280,46 @@ public static String computeExtensionDocFileName(String configRoot) { extensionNameBuilder.append(Constants.DASH); if (Constants.DEPLOYMENT.equals(extensionName) || Constants.RUNTIME.equals(extensionName)) { - final String configClass = configRoot.substring(configRoot.lastIndexOf(Constants.DOT) + 1); - extensionName = hyphenate(configClass); - extensionNameBuilder.append(Constants.CORE); + extensionNameBuilder.append(CORE); + } else if (subgroup != null && !Constants.DEPLOYMENT.equals(subgroup) + && !Constants.RUNTIME.equals(subgroup) && !Constants.COMMON.equals(subgroup) + && subgroup.matches(Constants.DIGIT_OR_LOWERCASE)) { + extensionNameBuilder.append(extensionName); + extensionNameBuilder.append(Constants.DASH); + extensionNameBuilder.append(subgroup); + + final String qualifier = matcher.group(3); + if (qualifier != null && !Constants.DEPLOYMENT.equals(qualifier) + && !Constants.RUNTIME.equals(qualifier) && !Constants.COMMON.equals(qualifier) + && qualifier.matches(Constants.DIGIT_OR_LOWERCASE)) { + extensionNameBuilder.append(Constants.DASH); + extensionNameBuilder.append(qualifier); + } + } else { extensionNameBuilder.append(extensionName); + } + } + + extensionNameBuilder.append(Constants.ADOC_EXTENSION); + return extensionNameBuilder.toString(); + } + + /** + * Guess extension name from given configuration root class name + */ + public static String computeExtensionGeneralConfigDocFileName(String configRoot) { + StringBuilder extensionNameBuilder = new StringBuilder(); + final Matcher matcher = Constants.PKG_PATTERN.matcher(configRoot); + if (!matcher.find()) { + extensionNameBuilder.append(configRoot); + } else { + String extensionName = matcher.group(1); + final String subgroup = matcher.group(2); + extensionNameBuilder.append(Constants.QUARKUS); + extensionNameBuilder.append(Constants.DASH); + + if (Constants.DEPLOYMENT.equals(extensionName) || Constants.RUNTIME.equals(extensionName)) { + extensionNameBuilder.append(CORE); } else if (subgroup != null && !Constants.DEPLOYMENT.equals(subgroup) && !Constants.RUNTIME.equals(subgroup) && !Constants.COMMON.equals(subgroup) && subgroup.matches(Constants.DIGIT_OR_LOWERCASE)) { @@ -308,17 +346,49 @@ public static String computeExtensionDocFileName(String configRoot) { /** * Guess config group file name from given configuration group class name */ - public static String computeConfigGroupDocFileName(String configGroup) { - final Matcher matcher = Constants.PKG_PATTERN.matcher(configGroup); + public static String computeConfigGroupDocFileName(String configGroupClassName) { + final String sanitizedClassName; + final Matcher matcher = Constants.PKG_PATTERN.matcher(configGroupClassName); + if (!matcher.find()) { - return CONFIG_GROUP_PREFIX + hyphenate(configGroup) + Constants.ADOC_EXTENSION; + sanitizedClassName = CONFIG_GROUP_DOC_PREFIX + Constants.DASH + hyphenate(configGroupClassName); + } else { + String replacement = Constants.DASH + CONFIG_GROUP_DOC_PREFIX + Constants.DASH; + sanitizedClassName = configGroupClassName + .replaceFirst("io.", "") + .replaceFirst("\\.runtime\\.", replacement) + .replaceFirst("\\.deployment\\.", replacement); } - String replacement = Constants.DASH + CONFIG_GROUP_PREFIX; - String sanitizedClassName = configGroup - .replaceFirst("io.", "") - .replaceFirst("\\.runtime\\.", replacement) - .replaceFirst("\\.deployment\\.", replacement); + return hyphenate(sanitizedClassName) + .replaceAll("[\\.-]+", Constants.DASH) + + Constants.ADOC_EXTENSION; + } + + /** + * Guess config root file name from given configuration root class name. + */ + public static String computeConfigRootDocFileName(String configRootClassName, String rootName) { + String sanitizedClassName; + final Matcher matcher = Constants.PKG_PATTERN.matcher(configRootClassName); + + if (!matcher.find()) { + sanitizedClassName = rootName + Constants.DASH + hyphenate(configRootClassName); + } else { + String deployment = Constants.DOT + Constants.DEPLOYMENT + Constants.DOT; + String runtime = Constants.DOT + Constants.RUNTIME + Constants.DOT; + + if (configRootClassName.contains(deployment)) { + sanitizedClassName = configRootClassName + .substring(configRootClassName.indexOf(deployment) + deployment.length()); + } else if (configRootClassName.contains(runtime)) { + sanitizedClassName = configRootClassName.substring(configRootClassName.indexOf(runtime) + runtime.length()); + } else { + sanitizedClassName = configRootClassName.replaceFirst("io.quarkus.", ""); + } + + sanitizedClassName = rootName + Constants.DASH + sanitizedClassName; + } return hyphenate(sanitizedClassName) .replaceAll("[\\.-]+", Constants.DASH) @@ -402,4 +472,22 @@ static String deriveConfigRootName(String simpleClassName, ConfigPhase configPha } return Constants.QUARKUS + Constants.DOT + hyphenate(simpleClassName); } + + /** + * Sort docs keys. The sorted list will contain the properties in the following order + * - 1. Map config items as last elements of the generated docs. + * - 2. Build time properties will come first. + * - 3. Otherwise respect source code declaration order. + * - 4. Elements within a configuration section will appear at the end of the generated doc while preserving described in + * 1-4. + */ + public static void sort(List configDocItems) { + Collections.sort(configDocItems); + for (ConfigDocItem configDocItem : configDocItems) { + if (configDocItem.isConfigSection()) { + sort(configDocItem.getConfigDocSection().getConfigDocItems()); + } + } + } + } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java index 3a9e309706a9d..803c1dc8a5f49 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java @@ -4,37 +4,47 @@ import java.util.List; import java.util.Map; -final public class ScannedConfigDocsItemHolder { - private final Map> allConfigItemsPerExtension; +final class ScannedConfigDocsItemHolder { + private final Map> generalConfigItems; + private final Map> configRootConfigItems; private final Map> configGroupConfigItems; public ScannedConfigDocsItemHolder() { - this(new HashMap<>(), new HashMap<>()); + this(new HashMap<>(), new HashMap<>(), new HashMap<>()); } - public ScannedConfigDocsItemHolder(Map> allConfigItemsPerExtension, - Map> configGroupConfigItems) { - this.allConfigItemsPerExtension = allConfigItemsPerExtension; + public ScannedConfigDocsItemHolder(Map> configRootConfigItems, + Map> configGroupConfigItems, Map> generalConfigItems) { + this.configRootConfigItems = configRootConfigItems; this.configGroupConfigItems = configGroupConfigItems; - } - - public Map> getAllConfigItemsPerExtension() { - return allConfigItemsPerExtension; + this.generalConfigItems = generalConfigItems; } public Map> getConfigGroupConfigItems() { return configGroupConfigItems; } - public void addToAllConfigItems(String configRootName, List configDocItems) { - allConfigItemsPerExtension.put(configRootName, configDocItems); + public Map> getConfigRootConfigItems() { + return configRootConfigItems; + } + + public Map> getGeneralConfigItems() { + return generalConfigItems; } public void addConfigGroupItems(String configGroupName, List configDocItems) { configGroupConfigItems.put(configGroupName, configDocItems); } + public void addConfigRootItems(String configRoot, List configDocItems) { + configRootConfigItems.put(configRoot, configDocItems); + } + + public void addGeneralConfigItems(String configRoot, List configDocItems) { + configRootConfigItems.put(configRoot, configDocItems); + } + public boolean isEmpty() { - return allConfigItemsPerExtension.isEmpty(); + return configRootConfigItems.isEmpty(); } } diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java index b5504cc41148a..cb3f029f17613 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java @@ -5,6 +5,7 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.VERTX_JAVA_DOC_SITE; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.appendConfigItemsIntoExistingOnes; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigRootDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.deriveConfigRootName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; @@ -138,19 +139,19 @@ public void shouldReturnConfigRootNameWhenComputingExtensionName() { } @Test - public void shouldAddCoreInComputedExtensionName() { + public void shouldUseCoreForConfigRootsCoreModuleWhenComputingExtensionName() { String configRoot = "io.quarkus.runtime.RuntimeConfig"; - String expected = "quarkus-core-runtime-config.adoc"; + String expected = "quarkus-core.adoc"; String fileName = computeExtensionDocFileName(configRoot); assertEquals(expected, fileName); configRoot = "io.quarkus.deployment.BuildTimeConfig"; - expected = "quarkus-core-build-time-config.adoc"; + expected = "quarkus-core.adoc"; fileName = computeExtensionDocFileName(configRoot); assertEquals(expected, fileName); configRoot = "io.quarkus.deployment.path.BuildTimeConfig"; - expected = "quarkus-core-build-time-config.adoc"; + expected = "quarkus-core.adoc"; fileName = computeExtensionDocFileName(configRoot); assertEquals(expected, fileName); } @@ -206,6 +207,34 @@ public void shouldUseHyphenatedClassNameWithoutRuntimeOrDeploymentNamespaceWhenC assertEquals(expected, fileName); } + @Test + public void shouldUseHyphenatedClassNameWithEverythingBeforeRuntimeOrDeploymentNamespaceReplacedByConfigRootNameWhenComputingConfigRootFileName() { + String configRoot = "ClassName"; + String expected = "root-name-class-name.adoc"; + String fileName = computeConfigRootDocFileName(configRoot, "root-name"); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.agroal.runtime.ClassName"; + expected = "quarkus-datasource-class-name.adoc"; + fileName = computeConfigRootDocFileName(configRoot, "quarkus-datasource"); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.keycloak.deployment.RealmConfig"; + expected = "quarkus-keycloak-realm-config.adoc"; + fileName = computeConfigRootDocFileName(configRoot, "quarkus-keycloak"); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.extension.deployment.BuildTimeConfig"; + expected = "quarkus-root-10-build-time-config.adoc"; + fileName = computeConfigRootDocFileName(configRoot, "quarkus-root-10"); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.extension.deployment.name.BuildTimeConfig"; + expected = "quarkus-config-root-name-build-time-config.adoc"; + fileName = computeConfigRootDocFileName(configRoot, "quarkus-config-root"); + assertEquals(expected, fileName); + } + @Test public void shouldPreserveExistingConfigItemsWhenAppendAnEmptyConfigItems() { List existingConfigItems = Arrays.asList(new ConfigDocItem(), new ConfigDocItem()); diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index 0a6336e6c1936..355182168237a 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -398,7 +398,7 @@ These are provided in `application.properties` the same as any other config prop The properties are shown below: -include::{generated-dir}/config/quarkus-core-native-config.adoc[opts=optional] +include::{generated-dir}/config/quarkus-native-pkg-native-config.adoc[opts=optional] == What's next? diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index c75fdadf1de7e..9c85f9a152a74 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -13,7 +13,7 @@ This guide explains logging and how to configure it. Run time configuration of logging is done through the normal `application.properties` file. -include::{generated-dir}/config/quarkus-core-log-config.adoc[opts=optional, leveloffset=+1] +include::{generated-dir}/config/quarkus-log-logging-log-config.adoc[opts=optional, leveloffset=+1] === Logging categories diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index 01ad7f8cbb406..da65d7f8a297e 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -467,7 +467,7 @@ These are provided in `application.properties` the same as any other config prop The properties are shown below: -include::{generated-dir}/config/quarkus-core-package-config.adoc[opts=optional] +include::{generated-dir}/config/quarkus-package-pkg-package-config.adoc[opts=optional] [[custom-test-configuration-profile]] === Custom test configuration profile in JVM mode diff --git a/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java b/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java index eef2d4ee3861c..9d01af74e2266 100644 --- a/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java +++ b/docs/src/main/java/io/quarkus/docs/generation/AllConfigGenerator.java @@ -21,12 +21,11 @@ import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResult; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import io.quarkus.annotation.processor.generate_doc.ConfigDocGeneratedOutput; import io.quarkus.annotation.processor.generate_doc.ConfigDocItem; import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner; import io.quarkus.annotation.processor.generate_doc.ConfigDocSection; @@ -37,8 +36,7 @@ import io.quarkus.docs.generation.ExtensionJson.Extension; public class AllConfigGenerator { - public static void main(String[] args) - throws AppModelResolverException, JsonParseException, JsonMappingException, IOException { + public static void main(String[] args) throws AppModelResolverException, IOException { if (args.length != 2) { // exit 1 will break Maven throw new IllegalArgumentException("Usage: "); @@ -165,7 +163,7 @@ public static void main(String[] args) for (Map.Entry> entry : sortedConfigItemsByExtension.entrySet()) { final List configDocItems = entry.getValue(); // sort the items - ConfigDocWriter.sort(configDocItems); + DocGeneratorUtil.sort(configDocItems); // insert a header ConfigDocSection header = new ConfigDocSection(); header.setSectionDetailsTitle(entry.getKey()); @@ -177,19 +175,29 @@ public static void main(String[] args) } // write our docs - configDocWriter.writeAllExtensionConfigDocumentation(allItems); + ConfigDocGeneratedOutput allConfigGeneratedOutput = new ConfigDocGeneratedOutput("all-config.adoc", true, allItems, + false); + configDocWriter.writeAllExtensionConfigDocumentation(allConfigGeneratedOutput); } private static String guessExtensionNameFromDocumentationFileName(String docFileName) { // sanitise - if (docFileName.startsWith("quarkus-")) + if (docFileName.startsWith("quarkus-")) { docFileName = docFileName.substring(8); - if (docFileName.endsWith(".adoc")) + } + + if (docFileName.endsWith(".adoc")) { docFileName = docFileName.substring(0, docFileName.length() - 5); - if (docFileName.endsWith("-config")) + } + + if (docFileName.endsWith("-config")) { docFileName = docFileName.substring(0, docFileName.length() - 7); - if (docFileName.endsWith("-configuration")) + } + + if (docFileName.endsWith("-configuration")) { docFileName = docFileName.substring(0, docFileName.length() - 14); + } + docFileName = docFileName.replace('-', ' '); return capitalize(docFileName); } @@ -224,4 +232,4 @@ private static void collectConfigRoots(ZipFile zf, Extension extension, Map Date: Sat, 23 Nov 2019 11:00:02 +0100 Subject: [PATCH 156/602] strip Runtime suffix when dereving config root name in doc generation handle optional config group in doc generation Fixes #5703 Follows up #5387 --- .../generate_doc/ConfigDoItemFinder.java | 45 ++++++++++++++----- .../generate_doc/ConfigDocSection.java | 12 ++++- .../generate_doc/DocGeneratorUtil.java | 10 +++-- .../SummaryTableDocFormatter.java | 9 ++-- .../generate_doc/DocGeneratorUtilTest.java | 4 ++ 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index b2ce89ede927a..1bf8aa40940f2 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -167,14 +167,15 @@ private List recursivelyFindConfigItems(Element element, String p } else { final ConfigDocKey configDocKey = new ConfigDocKey(); configDocKey.setWithinAMap(withinAMap); - boolean optional = false; boolean list = false; + boolean optional = false; if (!typeMirror.getKind().isPrimitive()) { DeclaredType declaredType = (DeclaredType) typeMirror; TypeElement typeElement = (TypeElement) declaredType.asElement(); Name qualifiedName = typeElement.getQualifiedName(); optional = qualifiedName.toString().startsWith(Optional.class.getName()); - list = qualifiedName.contentEquals(List.class.getName()); + list = qualifiedName.contentEquals(List.class.getName()) + || qualifiedName.contentEquals(Set.class.getName()); List typeArguments = declaredType.getTypeArguments(); if (!typeArguments.isEmpty()) { @@ -186,8 +187,7 @@ private List recursivelyFindConfigItems(Element element, String p if (configGroup != null) { name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); List groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, - configGroup, - configSection, true, sectionLevel); + configGroup, configSection, true, sectionLevel); configDocItems.addAll(groupConfigItems); continue; } else { @@ -198,13 +198,36 @@ private List recursivelyFindConfigItems(Element element, String p } else { // FIXME: this is for Optional and List TypeMirror realTypeMirror = typeArguments.get(0); - if (optional && (realTypeMirror.toString().startsWith(List.class.getName()) - || realTypeMirror.getKind() == TypeKind.ARRAY)) { - list = true; - DeclaredType declaredRealType = (DeclaredType) typeMirror; - typeArguments = declaredRealType.getTypeArguments(); - if (!typeArguments.isEmpty()) { - realTypeMirror = typeArguments.get(0); + String typeInString = realTypeMirror.toString(); + + if (optional) { + configGroup = configGroups.get(typeInString); + if (configGroup != null) { + if (configSection == null) { + final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection( + rawJavaDoc, + sectionLevel); + configSection = new ConfigDocSection(); + configSection.setWithinAMap(withinAMap); + configSection.setConfigPhase(configPhase); + configSection.setSectionDetails(sectionHolder.details); + configSection.setSectionDetailsTitle(sectionHolder.title); + configSection.setName(parentName + Constants.DOT + hyphenatedFieldName); + } + configSection.setOptional(true); + List groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, + configGroup, configSection, withinAMap, sectionLevel); + configDocItems.addAll(groupConfigItems); + continue; + } else if ((typeInString.startsWith(List.class.getName()) + || typeInString.startsWith(Set.class.getName()) + || realTypeMirror.getKind() == TypeKind.ARRAY)) { + list = true; + DeclaredType declaredRealType = (DeclaredType) typeMirror; + typeArguments = declaredRealType.getTypeArguments(); + if (!typeArguments.isEmpty()) { + realTypeMirror = typeArguments.get(0); + } } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocSection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocSection.java index b74802a238939..bcdfe98b4966e 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocSection.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocSection.java @@ -8,6 +8,7 @@ final public class ConfigDocSection implements ConfigDocElement, Comparable { private String name; + private boolean optional; private boolean withinAMap; private String sectionDetails; private String sectionDetailsTitle; @@ -100,12 +101,13 @@ public int hashCode() { public String toString() { return "ConfigDocSection{" + "name='" + name + '\'' + + ", optional='" + optional + '\'' + ", withinAMap=" + withinAMap + ", sectionDetails='" + sectionDetails + '\'' + ", sectionDetailsTitle='" + sectionDetailsTitle + '\'' + ", configPhase=" + configPhase + ", configDocItems=" + configDocItems + - ", anchorPrefix=" + anchorPrefix + + ", anchorPrefix='" + anchorPrefix + '\'' + '}'; } @@ -132,4 +134,12 @@ public void setAnchorPrefix(String anchorPrefix) { public String getAnchorPrefix() { return anchorPrefix; } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java index 27d16a858222d..91cd934472d90 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java @@ -458,18 +458,20 @@ private static String typeSimpleName(TypeMirror typeMirror) { } static String deriveConfigRootName(String simpleClassName, ConfigPhase configPhase) { - int length = simpleClassName.length(); + String simpleNameInLowerCase = simpleClassName.toLowerCase(); + int length = simpleNameInLowerCase.length(); - if (simpleClassName.endsWith(CONFIG)) { + if (simpleNameInLowerCase.endsWith(CONFIG.toLowerCase())) { String sanitized = simpleClassName.substring(0, length - CONFIG.length()); return deriveConfigRootName(sanitized, configPhase); - } else if (simpleClassName.endsWith(CONFIGURATION)) { + } else if (simpleNameInLowerCase.endsWith(CONFIGURATION.toLowerCase())) { String sanitized = simpleClassName.substring(0, length - CONFIGURATION.length()); return deriveConfigRootName(sanitized, configPhase); - } else if (simpleClassName.endsWith(configPhase.getConfigSuffix())) { + } else if (simpleNameInLowerCase.endsWith(configPhase.getConfigSuffix().toLowerCase())) { String sanitized = simpleClassName.substring(0, length - configPhase.getConfigSuffix().length()); return deriveConfigRootName(sanitized, configPhase); } + return Constants.QUARKUS + Constants.DOT + hyphenate(simpleClassName); } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java index d75d66017d297..42aa12accdeae 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java @@ -11,7 +11,8 @@ final class SummaryTableDocFormatter implements DocFormatter { public static final String SEARCHABLE_TABLE_CLASS = ".searchable"; // a css class indicating if a table is searchable public static final String CONFIGURATION_TABLE_CLASS = ".configuration-reference"; private static final String TABLE_ROW_FORMAT = "\n\na|%s [[%s]]`link:#%s[%s]`\n\n[.description]\n--\n%s\n--|%s %s\n|%s\n"; - private static final String TABLE_SECTION_ROW_FORMAT = "\n\nh|[[%s]]link:#%s[%s]\nh|Type\nh|Default"; + private static final String SECTION_TITLE = "[[%s]]link:#%s[%s]"; + private static final String TABLE_SECTION_ROW_FORMAT = "\n\nh|%s\n%s\nh|Type\nh|Default"; private static final String TABLE_HEADER_FORMAT = "[.configuration-legend]%s\n[%s, cols=\"80,.^10,.^10\"]\n|==="; private String anchorPrefix = ""; @@ -88,8 +89,10 @@ public void format(Writer writer, ConfigDocKey configDocKey) throws IOException @Override public void format(Writer writer, ConfigDocSection configDocSection) throws IOException { String anchor = anchorPrefix + getAnchor(configDocSection.getName()); - final String sectionRow = String.format(TABLE_SECTION_ROW_FORMAT, anchor, anchor, - configDocSection.getSectionDetailsTitle()); + String sectionTitle = String.format(SECTION_TITLE, anchor, anchor, configDocSection.getSectionDetailsTitle()); + final String sectionRow = String.format(TABLE_SECTION_ROW_FORMAT, sectionTitle, + configDocSection.isOptional() ? "This configuration section is optional" : Constants.EMPTY); + writer.append(sectionRow); for (ConfigDocItem configDocItem : configDocSection.getConfigDocItems()) { diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java index cb3f029f17613..d27fb8fefc25e 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java @@ -337,6 +337,10 @@ public void derivingConfigRootNameTestCase() { actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); assertEquals("quarkus.root-name", actual); + simpleClassName = "RootNameRuntimeConfig"; + actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); + assertEquals("quarkus.root-name", actual); + simpleClassName = "RootNameRunTimeConfiguration"; actual = deriveConfigRootName(simpleClassName, ConfigPhase.RUN_TIME); assertEquals("quarkus.root-name", actual); From c3814730e768ac282391db1c5d4dae28b71d006e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 20 Nov 2019 17:06:54 +0100 Subject: [PATCH 157/602] Provides Optional support on Hibernate with Panache --- .../main/asciidoc/hibernate-orm-panache.adoc | 4 ++++ .../orm/panache/PanacheEntityBase.java | 24 +++++++++++++++++++ .../hibernate/orm/panache/PanacheQuery.java | 19 +++++++++++++++ .../orm/panache/PanacheRepositoryBase.java | 23 ++++++++++++++++++ .../orm/panache/runtime/JpaOperations.java | 9 +++++++ .../orm/panache/runtime/PanacheQueryImpl.java | 21 ++++++++++++++++ .../io/quarkus/it/panache/TestEndpoint.java | 22 +++++++++++++++++ .../panache/book/BookEntityResource.java | 8 ++++++- .../panache/book/BookRepositoryResource.java | 8 ++++++- 9 files changed, 136 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index 68b35b1ce60f3..dacec14eff3f5 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -161,6 +161,10 @@ List allPersons = Person.listAll(); // finding a specific person by ID person = Person.findById(personId); +// finding a specific person by ID via an Optional +Optional optional = Person.findByIdOptional(personId); +person = optional.orElseThrow(() -> NotFoundException()); + // finding all living persons List livingPersons = Person.list("status", Status.Alive); diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java index a4b97ebc6e9ba..7cc6b94713523 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import javax.json.bind.annotation.JsonbTransient; @@ -115,6 +116,29 @@ public static T findById(Object id, LockModeType l throw JpaOperations.implementationInjectionMissing(); } + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public static Optional findByIdOptional(Object id) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @param lockModeType the locking strategy to be used when retrieving the entity. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public static Optional findByIdOptional(Object id, LockModeType lockModeType) { + throw JpaOperations.implementationInjectionMissing(); + } + /** * Find entities using a query, with optional indexed parameters. * diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java index 3e1ac136ff607..81be409f43994 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheQuery.java @@ -1,6 +1,7 @@ package io.quarkus.hibernate.orm.panache; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import javax.persistence.LockModeType; @@ -172,6 +173,15 @@ public interface PanacheQuery { */ public T firstResult(); + /** + * Returns the first result of the current page index. This ignores the current page size to fetch + * a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @see #singleResultOptional() + */ + public Optional firstResultOptional(); + /** * Executes this query for the current page and return a single result. * @@ -181,4 +191,13 @@ public interface PanacheQuery { * @see #firstResult() */ public T singleResult(); + + /** + * Executes this query for the current page and return a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @throws NonUniqueResultException if there are more than one result + * @see #firstResultOptional() + */ + public Optional singleResultOptional(); } diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java index e40a45094e8be..55f8af2437b19 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import javax.persistence.LockModeType; @@ -113,6 +114,28 @@ public default Entity findById(Id id, LockModeType lockModeType) { throw JpaOperations.implementationInjectionMissing(); } + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public default Optional findByIdOptional(Id id) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public default Optional findByIdOptional(Id id, LockModeType lockModeType) { + throw JpaOperations.implementationInjectionMissing(); + } + /** * Find entities using a query, with optional indexed parameters. * diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java index 417f620f0f294..0f36c858ed93a 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.stream.Stream; import javax.persistence.EntityManager; @@ -201,6 +202,14 @@ public static Object findById(Class entityClass, Object id, LockModeType lock return getEntityManager().find(entityClass, id, lockModeType); } + public static Optional findByIdOptional(Class entityClass, Object id) { + return Optional.ofNullable(findById(entityClass, id)); + } + + public static Optional findByIdOptional(Class entityClass, Object id, LockModeType lockModeType) { + return Optional.ofNullable(findById(entityClass, id, lockModeType)); + } + public static PanacheQuery find(Class entityClass, String query, Object... params) { return find(entityClass, query, null, params); } diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java index 54ff38a33d257..7d7f0e344b63c 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java @@ -2,12 +2,16 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import javax.persistence.EntityManager; import javax.persistence.LockModeType; +import javax.persistence.NonUniqueResultException; import javax.persistence.Query; +import com.arjuna.ats.internal.jdbc.drivers.modifiers.list; + import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Page; @@ -145,10 +149,27 @@ public T firstResult() { return list.isEmpty() ? null : list.get(0); } + @Override + public Optional firstResultOptional() { + return Optional.ofNullable(firstResult()); + } + @Override @SuppressWarnings("unchecked") public T singleResult() { jpaQuery.setMaxResults(page.size); return (T) jpaQuery.getSingleResult(); } + + @Override + @SuppressWarnings("unchecked") + public Optional singleResultOptional() { + jpaQuery.setMaxResults(2); + List list = jpaQuery.getResultList(); + if (list.size() == 2) { + throw new NonUniqueResultException(); + } + + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + } } diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java index 353383ed6487f..36ce44d645651 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java @@ -144,10 +144,18 @@ public String testModel() { Assertions.assertEquals(person, byId); Assertions.assertEquals("Person<" + person.id + ">", byId.toString()); + byId = Person. findByIdOptional(person.id).get(); + Assertions.assertEquals(person, byId); + Assertions.assertEquals("Person<" + person.id + ">", byId.toString()); + byId = Person.findById(person.id, LockModeType.PESSIMISTIC_READ); Assertions.assertEquals(person, byId); Assertions.assertEquals("Person<" + person.id + ">", byId.toString()); + byId = Person. findByIdOptional(person.id, LockModeType.PESSIMISTIC_READ).get(); + Assertions.assertEquals(person, byId); + Assertions.assertEquals("Person<" + person.id + ">", byId.toString()); + person.delete(); Assertions.assertEquals(0, Person.count()); @@ -191,6 +199,8 @@ public String testModel() { Assertions.assertNotNull(Person.findAll().firstResult()); + Assertions.assertNotNull(Person.findAll().firstResultOptional().get()); + Assertions.assertEquals(7, Person.deleteAll()); // persistAndFlush @@ -339,8 +349,12 @@ public String testModelDao() { } catch (NoResultException x) { } + Assertions.assertFalse(personDao.findAll().singleResultOptional().isPresent()); + Assertions.assertNull(personDao.findAll().firstResult()); + Assertions.assertFalse(personDao.findAll().firstResultOptional().isPresent()); + Person person = makeSavedPersonDao(); Assertions.assertNotNull(person.id); @@ -369,6 +383,7 @@ public String testModelDao() { Assertions.assertEquals(person, personDao.findAll().firstResult()); Assertions.assertEquals(person, personDao.findAll().singleResult()); + Assertions.assertEquals(person, personDao.findAll().singleResultOptional().get()); persons = personDao.find("name = ?1", "stef").list(); Assertions.assertEquals(1, persons.size()); @@ -419,13 +434,20 @@ public String testModelDao() { Assertions.assertEquals(person, personDao.find("name", "stef").firstResult()); Assertions.assertEquals(person, personDao.find("name", "stef").singleResult()); + Assertions.assertEquals(person, personDao.find("name", "stef").singleResultOptional().get()); Person byId = personDao.findById(person.id); Assertions.assertEquals(person, byId); + byId = personDao.findByIdOptional(person.id).get(); + Assertions.assertEquals(person, byId); + byId = personDao.findById(person.id, LockModeType.PESSIMISTIC_READ); Assertions.assertEquals(person, byId); + byId = personDao.findByIdOptional(person.id, LockModeType.PESSIMISTIC_READ).get(); + Assertions.assertEquals(person, byId); + personDao.delete(person); Assertions.assertEquals(0, personDao.count()); diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookEntityResource.java index 9bd49d3544486..363045c4eaa2c 100644 --- a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookEntityResource.java +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookEntityResource.java @@ -69,6 +69,12 @@ public BookEntity getBook(@PathParam("id") String id) { return BookEntity.findById(new ObjectId(id)); } + @GET + @Path("/optional/{id}") + public BookEntity getBookOptional(@PathParam("id") String id) { + return BookEntity. findByIdOptional(new ObjectId(id)).orElseThrow(() -> new NotFoundException()); + } + @GET @Path("/search/{author}") public List getBooksByAuthor(@PathParam("author") String author) { @@ -86,7 +92,7 @@ public BookEntity search(@QueryParam("author") String author, @QueryParam("title return BookEntity .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), LocalDate.parse(dateTo)) - .firstResult(); + . firstResultOptional().orElseThrow(() -> new NotFoundException()); } @GET diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.java index 8f042e1358fc4..3c0ef6bf78e69 100644 --- a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.java +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.java @@ -72,6 +72,12 @@ public Book getBook(@PathParam("id") String id) { return bookRepository.findById(new ObjectId(id)); } + @GET + @Path("/optional/{id}") + public Book getBookOptional(@PathParam("id") String id) { + return bookRepository.findByIdOptional(new ObjectId(id)).orElseThrow(() -> new NotFoundException()); + } + @GET @Path("/search/{author}") public List getBooksByAuthor(@PathParam("author") String author) { @@ -89,7 +95,7 @@ public Book search(@QueryParam("author") String author, @QueryParam("title") Str return bookRepository .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), LocalDate.parse(dateTo)) - .firstResult(); + .firstResultOptional().orElseThrow(() -> new NotFoundException()); } @GET From ebdac2eca053f1cdefef0dee7abe8db7cbba4c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 20 Nov 2019 17:07:04 +0100 Subject: [PATCH 158/602] Provides Optional support on MongoDB with Panache --- .../main/asciidoc/hibernate-orm-panache.adoc | 2 +- docs/src/main/asciidoc/mongodb-panache.adoc | 4 ++++ .../orm/panache/runtime/PanacheQueryImpl.java | 2 -- .../panache/PanacheMongoEntityBase.java | 12 ++++++++++ .../panache/PanacheMongoRepositoryBase.java | 12 ++++++++++ .../quarkus/mongodb/panache/PanacheQuery.java | 22 ++++++++++++++++++- .../panache/runtime/MongoOperations.java | 5 +++++ .../panache/runtime/PanacheQueryImpl.java | 20 ++++++++++++++++- .../exception/PanacheQueryException.java | 7 ++++++ ...t.java => MongodbPanacheResourceTest.java} | 9 ++++++-- .../mongodb/panache/NativeBookResourceIT.java | 2 +- 11 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 extensions/panache/panache-common/runtime/src/main/java/io/quarkus/panache/common/exception/PanacheQueryException.java rename integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/{BookResourceTest.java => MongodbPanacheResourceTest.java} (97%) diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index dacec14eff3f5..dd8937addfdbb 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -163,7 +163,7 @@ person = Person.findById(personId); // finding a specific person by ID via an Optional Optional optional = Person.findByIdOptional(personId); -person = optional.orElseThrow(() -> NotFoundException()); +person = optional.orElseThrow(() -> new NotFoundException()); // finding all living persons List livingPersons = Person.list("status", Status.Alive); diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 8aca7ac46f222..ee2e29e79d54c 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -196,6 +196,10 @@ List allPersons = Person.listAll(); // finding a specific person by ID person = Person.findById(personId); +// finding a specific person by ID via an Optional +Optional optional = Person.findByIdOptional(personId); +person = optional.orElseThrow(() -> new NotFoundException()); + // finding all living persons List livingPersons = Person.list("status", Status.Alive); diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java index 7d7f0e344b63c..89d4982d08bfb 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java @@ -10,8 +10,6 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.Query; -import com.arjuna.ats.internal.jdbc.drivers.modifiers.list; - import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Page; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java index c4ed06d9573cf..f924fb22f09f4 100755 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import org.bson.Document; @@ -85,6 +86,17 @@ public static T findById(Object id) { throw MongoOperations.implementationInjectionMissing(); } + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public static Optional findByIdOptional(Object id) { + throw MongoOperations.implementationInjectionMissing(); + } + /** * Find entities using a query, with optional indexed parameters. * diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java index c99958fd3df43..532328334129c 100755 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; import org.bson.Document; @@ -91,6 +92,17 @@ public default Entity findById(Id id) { throw MongoOperations.implementationInjectionMissing(); } + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public default Optional findByIdOptional(Id id) { + throw MongoOperations.implementationInjectionMissing(); + } + /** * Find entities using a query, with optional indexed parameters. * diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheQuery.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheQuery.java index 3ba5f0dd381cd..4ce8a39d39905 100755 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheQuery.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheQuery.java @@ -1,6 +1,7 @@ package io.quarkus.mongodb.panache; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import io.quarkus.panache.common.Page; @@ -147,11 +148,30 @@ public interface PanacheQuery { */ public T firstResult(); + /** + * Returns the first result of the current page index. This ignores the current page size to fetch + * a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @see #singleResultOptional() + */ + public Optional firstResultOptional(); + /** * Executes this query for the current page and return a single result. * - * @return the single result (throws if there is not exactly one) + * @return the single result + * @throws PanacheQueryException if there is not exactly one result. * @see #firstResult() */ public T singleResult(); + + /** + * Executes this query for the current page and return a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @throws PanacheQueryException if there is more than one result. + * @see #firstResultOptional() + */ + public Optional singleResultOptional(); } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java index 4584a9cacdc04..a79618984bdd9 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -282,6 +283,10 @@ public static Object findById(Class entityClass, Object id) { return collection.find(new Document(ID, id)).first(); } + public static Optional findByIdOptional(Class entityClass, Object id) { + return Optional.ofNullable(findById(entityClass, id)); + } + public static PanacheQuery find(Class entityClass, String query, Object... params) { return find(entityClass, query, null, params); } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java index 0b3938f2475bb..c8e6e36026d21 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import org.bson.Document; @@ -12,6 +13,7 @@ import io.quarkus.mongodb.panache.PanacheQuery; import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.exception.PanacheQueryException; public class PanacheQueryImpl implements PanacheQuery { private MongoCollection collection; @@ -133,14 +135,30 @@ public T firstResult() { return list.isEmpty() ? null : list.get(0); } + @Override + public Optional firstResultOptional() { + return Optional.ofNullable(firstResult()); + } + @Override @SuppressWarnings("unchecked") public T singleResult() { List list = list(); if (list.isEmpty() || list.size() > 1) { - throw new RuntimeException("There should be only one result");//TODO use proper exception + throw new PanacheQueryException("There should be only one result"); } return list.get(0); } + + @Override + @SuppressWarnings("unchecked") + public Optional singleResultOptional() { + List list = list(); + if (list.size() > 1) { + throw new PanacheQueryException("There should be no more than one result"); + } + + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + } } diff --git a/extensions/panache/panache-common/runtime/src/main/java/io/quarkus/panache/common/exception/PanacheQueryException.java b/extensions/panache/panache-common/runtime/src/main/java/io/quarkus/panache/common/exception/PanacheQueryException.java new file mode 100644 index 0000000000000..5cfc1a925702e --- /dev/null +++ b/extensions/panache/panache-common/runtime/src/main/java/io/quarkus/panache/common/exception/PanacheQueryException.java @@ -0,0 +1,7 @@ +package io.quarkus.panache.common.exception; + +public class PanacheQueryException extends RuntimeException { + public PanacheQueryException(String s) { + super(s); + } +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/BookResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java similarity index 97% rename from integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/BookResourceTest.java rename to integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java index 56c22d4554b60..ff899be5d54a2 100644 --- a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/BookResourceTest.java +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java @@ -42,8 +42,8 @@ import io.restassured.response.Response; @QuarkusTest -class BookResourceTest { - private static final Logger LOGGER = Logger.getLogger(BookResourceTest.class); +class MongodbPanacheResourceTest { + private static final Logger LOGGER = Logger.getLogger(MongodbPanacheResourceTest.class); private static final TypeRef> LIST_OF_BOOK_TYPE_REF = new TypeRef>() { }; private static final TypeRef> LIST_OF_PERSON_TYPE_REF = new TypeRef>() { @@ -195,9 +195,14 @@ private void callBookEndpoint(String endpoint) { //check that the title has been updated and the transient description ignored book = get(endpoint + "/" + book.getId().toString()).as(BookDTO.class); + Assertions.assertNotNull(book); Assertions.assertEquals("Notre-Dame de Paris 2", book.getTitle()); Assertions.assertNull(book.getTransientDescription()); + //test findByIdOptional + book = get(endpoint + "/optional/" + book.getId().toString()).as(BookDTO.class); + Assertions.assertNotNull(book); + //delete a book response = RestAssured .given() diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/NativeBookResourceIT.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/NativeBookResourceIT.java index da98f6d461348..d202e80e19020 100644 --- a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/NativeBookResourceIT.java +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/NativeBookResourceIT.java @@ -3,6 +3,6 @@ import io.quarkus.test.junit.NativeImageTest; @NativeImageTest -class NativeBookResourceIT extends BookResourceTest { +class NativeBookResourceIT extends MongodbPanacheResourceTest { } From de320adadaefd7b137311250d97b5aa498af9021 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Wed, 20 Nov 2019 12:34:49 +0100 Subject: [PATCH 159/602] feat(core): add NativeEnableAllTimeZonesBuildItem build item --- .../builditem/NativeEnableAllCharsetsBuildItem.java | 4 ++++ .../NativeImageEnableAllCharsetsBuildItem.java | 7 +++++++ .../NativeImageEnableAllTimeZonesBuildItem.java | 7 +++++++ .../substrate/DeprecatedBuildItemProcessor.java | 7 +++++++ .../deployment/pkg/steps/NativeImageBuildStep.java | 6 ++++++ .../deployment/steps/NativeImageConfigBuildStep.java | 12 +++++++++--- docs/src/main/asciidoc/writing-extensions.adoc | 5 ++++- .../jdbc/mssql/deployment/MsSQLProcessor.java | 6 +++--- .../jdbc/mysql/deployment/JDBCMySQLProcessor.java | 6 +++--- 9 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllCharsetsBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllTimeZonesBuildItem.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeEnableAllCharsetsBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeEnableAllCharsetsBuildItem.java index 2e2426cf91adf..e04266e7b0818 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeEnableAllCharsetsBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeEnableAllCharsetsBuildItem.java @@ -2,6 +2,10 @@ import io.quarkus.builder.item.MultiBuildItem; +/** + * @deprecated use {@link NativeImageEnableAllCharsetsBuildItem} instead + */ +@Deprecated public final class NativeEnableAllCharsetsBuildItem extends MultiBuildItem { } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllCharsetsBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllCharsetsBuildItem.java new file mode 100644 index 0000000000000..36e6e50cd5a08 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllCharsetsBuildItem.java @@ -0,0 +1,7 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class NativeImageEnableAllCharsetsBuildItem extends MultiBuildItem { + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllTimeZonesBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllTimeZonesBuildItem.java new file mode 100644 index 0000000000000..4223eceba4d21 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/NativeImageEnableAllTimeZonesBuildItem.java @@ -0,0 +1,7 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class NativeImageEnableAllTimeZonesBuildItem extends MultiBuildItem { + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/DeprecatedBuildItemProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/DeprecatedBuildItemProcessor.java index 2fe553f189d55..b6576c76545a3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/DeprecatedBuildItemProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/DeprecatedBuildItemProcessor.java @@ -5,6 +5,8 @@ import java.util.Map; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.NativeEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; @@ -192,4 +194,9 @@ List substrateSystemProperties(List oldProps) { + return new NativeImageEnableAllCharsetsBuildItem(); + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index bf8769bb7598c..ad5d4be80a4e9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -145,6 +145,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa process.waitFor(); } Boolean enableSslNative = false; + boolean enableAllTimeZones = false; for (NativeImageSystemPropertyBuildItem prop : nativeImageProperties) { //todo: this should be specific build items if (prop.getKey().equals("quarkus.ssl.native") && prop.getValue() != null) { @@ -155,6 +156,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeConfig.enableAllSecurityServices |= Boolean.parseBoolean(prop.getValue()); } else if (prop.getKey().equals("quarkus.native.enable-all-charsets") && prop.getValue() != null) { nativeConfig.addAllCharsets |= Boolean.parseBoolean(prop.getValue()); + } else if (prop.getKey().equals("quarkus.native.enable-all-timezones") && prop.getValue() != null) { + enableAllTimeZones = Boolean.parseBoolean(prop.getValue()); } else { // todo maybe just -D is better than -J-D in this case if (prop.getValue() == null) { @@ -222,6 +225,9 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } else { command.add("-H:-AddAllCharsets"); } + if (enableAllTimeZones) { + command.add("-H:+IncludeAllTimeZones"); + } if (!protocols.isEmpty()) { command.add("-H:EnableURLProtocols=" + String.join(",", protocols)); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageConfigBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageConfigBuildStep.java index 05413732fca39..5e927b960982b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageConfigBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageConfigBuildStep.java @@ -18,7 +18,8 @@ import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem; import io.quarkus.deployment.builditem.JniBuildItem; -import io.quarkus.deployment.builditem.NativeEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllTimeZonesBuildItem; import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; @@ -43,7 +44,8 @@ void build(SslContextConfigurationRecorder sslContextConfigurationRecorder, List nativeImageConfigBuildItems, SslNativeConfigBuildItem sslNativeConfig, List jniBuildItems, - List nativeEnableAllCharsetsBuildItems, + List nativeImageEnableAllCharsetsBuildItems, + List nativeImageEnableAllTimeZonesBuildItems, List extensionSslNativeSupport, List enableAllSecurityServicesBuildItems, BuildProducer proxy, @@ -137,9 +139,13 @@ void build(SslContextConfigurationRecorder sslContextConfigurationRecorder, nativeImage.produce(new NativeImageSystemPropertyBuildItem("quarkus.jni.enable", "true")); } - if (!nativeEnableAllCharsetsBuildItems.isEmpty()) { + if (!nativeImageEnableAllCharsetsBuildItems.isEmpty()) { nativeImage.produce(new NativeImageSystemPropertyBuildItem("quarkus.native.enable-all-charsets", "true")); } + + if (!nativeImageEnableAllTimeZonesBuildItems.isEmpty()) { + nativeImage.produce(new NativeImageSystemPropertyBuildItem("quarkus.native.enable-all-timezones", "true")); + } } private Boolean isSslNativeEnabled(SslNativeConfigBuildItem sslNativeConfig, diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 7c368b3c20f57..4940bd430e058 100755 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -1663,9 +1663,12 @@ A class that will be initialized at runtime rather than build time. This will ca `io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem`:: A convenience feature that allows you to control most of the above features from a single build item. -`io.quarkus.deployment.builditem.NativeEnableAllCharsetsBuildItem`:: +`io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem`:: Indicates that all charsets should be enabled in native image. +`io.quarkus.deployment.builditem.NativeImageEnableAllTimeZonesBuildItem`:: +Indicates that all timezones should be enabled in native image. + `io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem`:: A convenient way to tell Quarkus that the extension requires SSL and it should be enabled during native image build. When using this feature, remember to add your extension to the list of extensions that offer SSL support automatically on the https://github.com/quarkusio/quarkus/blob/master/docs/src/main/asciidoc/native-and-ssl.adoc[native and ssl guide]. diff --git a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java index 9e33e50941aee..d74bd95cbfe0a 100644 --- a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java +++ b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java @@ -3,7 +3,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.NativeEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; public class MsSQLProcessor { @@ -15,9 +15,9 @@ FeatureBuildItem feature() { @BuildStep void nativeResources(BuildProducer resources, - BuildProducer nativeEnableAllCharsets) { + BuildProducer nativeEnableAllCharsets) { resources.produce(new NativeImageResourceBundleBuildItem("com.microsoft.sqlserver.jdbc.SQLServerResource")); - nativeEnableAllCharsets.produce(new NativeEnableAllCharsetsBuildItem()); + nativeEnableAllCharsets.produce(new NativeImageEnableAllCharsetsBuildItem()); } } diff --git a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java index 5103a7e49e7ce..8f29c9f3bd18c 100644 --- a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java +++ b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java @@ -2,7 +2,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.NativeEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; public class JDBCMySQLProcessor { @@ -17,7 +17,7 @@ NativeImageResourceBuildItem resource() { } @BuildStep - NativeEnableAllCharsetsBuildItem enableAllCharsets() { - return new NativeEnableAllCharsetsBuildItem(); + NativeImageEnableAllCharsetsBuildItem enableAllCharsets() { + return new NativeImageEnableAllCharsetsBuildItem(); } } From 8d4712a1f3ebb45f288ae93cb30531900099ad7b Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Wed, 20 Nov 2019 12:36:11 +0100 Subject: [PATCH 160/602] fix(mysql): Timezone mappings missing for Mysql JDBC driver in native mode This fixes the issue by enabling all timezones by default when we have mysql extension Fixes #5269 --- .../quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java | 6 ++++++ integration-tests/jpa-mysql/pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java index 8f29c9f3bd18c..176c4d561348d 100644 --- a/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java +++ b/extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java @@ -3,6 +3,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllTimeZonesBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; public class JDBCMySQLProcessor { @@ -20,4 +21,9 @@ NativeImageResourceBuildItem resource() { NativeImageEnableAllCharsetsBuildItem enableAllCharsets() { return new NativeImageEnableAllCharsetsBuildItem(); } + + @BuildStep + NativeImageEnableAllTimeZonesBuildItem enableAllTimeZones() { + return new NativeImageEnableAllTimeZonesBuildItem(); + } } diff --git a/integration-tests/jpa-mysql/pom.xml b/integration-tests/jpa-mysql/pom.xml index 6d747ecaefd67..8d9fe860e614f 100644 --- a/integration-tests/jpa-mysql/pom.xml +++ b/integration-tests/jpa-mysql/pom.xml @@ -15,7 +15,7 @@ Module that contains JPA related tests running with the MySQL database - jdbc:mysql://localhost:3306/hibernate_orm_test?serverTimezone=UTC + jdbc:mysql://localhost:3306/hibernate_orm_test From a842a73c9b246dd87e7064cd578f96d64004bb56 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2019 14:27:07 +0000 Subject: [PATCH 161/602] Bump mariadb-java-client from 2.4.4 to 2.5.2 Bumps [mariadb-java-client](https://github.com/MariaDB/mariadb-connector-j) from 2.4.4 to 2.5.2. - [Release notes](https://github.com/MariaDB/mariadb-connector-j/releases) - [Changelog](https://github.com/MariaDB/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/MariaDB/mariadb-connector-j/compare/2.4.4...2.5.2) Signed-off-by: dependabot-preview[bot] Replaced DefaultAuthenticationProvider with AuthenticationPluginLoader Co-authored-by: George Gastaldi --- bom/runtime/pom.xml | 2 +- .../deployment/MariaDBJDBCReflections.java | 8 +++++ ...enticationPluginLoader_Substitutions.java} | 32 ++++++++----------- 3 files changed, 22 insertions(+), 20 deletions(-) rename extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/{DefaultAuthenticationProvider_Substitutions.java => AuthenticationPluginLoader_Substitutions.java} (57%) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 32662d90bf29d..943d513f695af 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -108,7 +108,7 @@ 2.3.2 1.4.197 42.2.8 - 2.4.4 + 2.5.2 8.0.18 7.2.1.jre8 10.14.2.0 diff --git a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/MariaDBJDBCReflections.java b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/MariaDBJDBCReflections.java index 4fe0b587ffa91..2b2dd77803514 100644 --- a/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/MariaDBJDBCReflections.java +++ b/extensions/jdbc/jdbc-mariadb/deployment/src/main/java/io/quarkus/jdbc/mariadb/deployment/MariaDBJDBCReflections.java @@ -3,6 +3,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; public final class MariaDBJDBCReflections { @@ -17,4 +18,11 @@ void build(BuildProducer reflectiveClass) { //MariaDB's connection process requires reflective read to all fields of Options: reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, "org.mariadb.jdbc.internal.util.Options")); } + + @BuildStep + void runtimeInit(BuildProducer runtimeInitialized) { + //MastersSlavesListener starts threads in DynamicSizedSchedulerImpl which is disallowed during build time in GraalVM + runtimeInitialized + .produce(new RuntimeInitializedClassBuildItem("org.mariadb.jdbc.internal.failover.impl.MastersSlavesListener")); + } } diff --git a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java b/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/AuthenticationPluginLoader_Substitutions.java similarity index 57% rename from extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java rename to extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/AuthenticationPluginLoader_Substitutions.java index e8f9a350bc9d3..a486d4361947a 100644 --- a/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java +++ b/extensions/jdbc/jdbc-mariadb/runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/AuthenticationPluginLoader_Substitutions.java @@ -2,22 +2,20 @@ import java.sql.SQLException; -import org.mariadb.jdbc.internal.com.send.authentication.AuthenticationPlugin; +import org.mariadb.jdbc.authentication.AuthenticationPlugin; +import org.mariadb.jdbc.authentication.AuthenticationPluginLoader; import org.mariadb.jdbc.internal.com.send.authentication.ClearPasswordPlugin; import org.mariadb.jdbc.internal.com.send.authentication.Ed25519PasswordPlugin; import org.mariadb.jdbc.internal.com.send.authentication.NativePasswordPlugin; import org.mariadb.jdbc.internal.com.send.authentication.OldPasswordPlugin; import org.mariadb.jdbc.internal.com.send.authentication.SendGssApiAuthPacket; -// import org.mariadb.jdbc.internal.com.send.authentication.SendPamAuthPacket; -import org.mariadb.jdbc.internal.protocol.authentication.DefaultAuthenticationProvider; -import org.mariadb.jdbc.internal.util.Options; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -@TargetClass(DefaultAuthenticationProvider.class) +@TargetClass(AuthenticationPluginLoader.class) @Substitute -public final class DefaultAuthenticationProvider_Substitutions { +public final class AuthenticationPluginLoader_Substitutions { public static final String MYSQL_NATIVE_PASSWORD = "mysql_native_password"; public static final String MYSQL_OLD_PASSWORD = "mysql_old_password"; @@ -27,30 +25,26 @@ public final class DefaultAuthenticationProvider_Substitutions { private static final String DIALOG = "dialog"; @Substitute - public static AuthenticationPlugin processAuthPlugin(String plugin, - String password, - byte[] authData, - Options options) - throws SQLException { - switch (plugin) { + public static AuthenticationPlugin get(String type) throws SQLException { + switch (type) { case MYSQL_NATIVE_PASSWORD: - return new NativePasswordPlugin(password, authData, options.passwordCharacterEncoding); + return new NativePasswordPlugin(); case MYSQL_OLD_PASSWORD: - return new OldPasswordPlugin(password, authData); + return new OldPasswordPlugin(); case MYSQL_CLEAR_PASSWORD: - return new ClearPasswordPlugin(password, options.passwordCharacterEncoding); + return new ClearPasswordPlugin(); case DIALOG: throw new UnsupportedOperationException("Authentication strategy 'dialog' is not supported in GraalVM"); - //return new SendPamAuthPacket(password, authData, options.passwordCharacterEncoding); + //return new SendPamAuthPacket(); case GSSAPI_CLIENT: - return new SendGssApiAuthPacket(authData, options.servicePrincipalName); + return new SendGssApiAuthPacket(); case MYSQL_ED25519_PASSWORD: - return new Ed25519PasswordPlugin(password, authData, options.passwordCharacterEncoding); + return new Ed25519PasswordPlugin(); default: throw new SQLException( "Client does not support authentication protocol requested by server. " - + "Consider upgrading MariaDB client. plugin was = " + plugin, + + "Consider upgrading MariaDB client. plugin was = " + type, "08004", 1251); } } From 3aaa6acca46c27dd3da2c37f43679fe440ddcd97 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 2 Dec 2019 08:25:16 -0600 Subject: [PATCH 162/602] Introduce "housekeeping" issue type --- .github/ISSUE_TEMPLATE/housekeeping.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/housekeeping.md diff --git a/.github/ISSUE_TEMPLATE/housekeeping.md b/.github/ISSUE_TEMPLATE/housekeeping.md new file mode 100644 index 0000000000000..adc19d2b832b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/housekeeping.md @@ -0,0 +1,14 @@ +--- +name: Housekeeping +about: A generalized task or cleanup not associated with a bug report or enhancement +title: '' +labels: housekeeping +assignees: '' + +--- + +**Description** +(Describe the task here.) + +**Implementation ideas** +(If you have any implementation ideas, they can go here.) From b3a978894bda9195a6a185dbcf2db05525b49977 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 21 Nov 2019 16:26:49 +0100 Subject: [PATCH 163/602] Introduce Qute extensions - qute core as an independent project - qute core extension - qute resteasy extension - first version of guide --- bom/deployment/pom.xml | 11 + bom/runtime/pom.xml | 27 + .../builditem/FeatureBuildItem.java | 1 + docs/src/main/asciidoc/qute.adoc | 271 ++++++ extensions/pom.xml | 4 + extensions/qute-resteasy/deployment/pom.xml | 58 ++ .../deployment/QuteResteasyProcessor.java | 23 + .../resteasy/deployment/HelloResource.java | 25 + .../resteasy/deployment/ItemResource.java | 38 + .../TemplateResponseFilterTest.java | 28 + .../deployment/VariantTemplateTest.java | 31 + extensions/qute-resteasy/pom.xml | 23 + extensions/qute-resteasy/runtime/pom.xml | 55 ++ .../qute/resteasy/TemplateResponseFilter.java | 80 ++ extensions/qute/deployment/pom.xml | 56 ++ .../GeneratedValueResolverBuildItem.java | 17 + .../ImplicitValueResolverBuildItem.java | 19 + .../IncorrectExpressionBuildItem.java | 21 + .../qute/deployment/QuteProcessor.java | 837 ++++++++++++++++++ .../qute/deployment/TemplateException.java | 11 + .../TemplateExtensionMethodBuildItem.java | 41 + .../deployment/TemplatePathBuildItem.java | 39 + .../deployment/TemplateVariantsBuildItem.java | 20 + .../TemplatesAnalysisBuildItem.java | 35 + .../deployment/TypeCheckExcludeBuildItem.java | 21 + .../qute/deployment/TypeCheckInfo.java | 110 +++ .../io/quarkus/qute/deployment/Types.java | 92 ++ .../deployment/AlternativeBasePathTest.java | 42 + .../java/io/quarkus/qute/deployment/Foo.java | 30 + .../io/quarkus/qute/deployment/Hello.java | 14 + .../quarkus/qute/deployment/HelloReflect.java | 11 + .../InjectNamespaceResolverTest.java | 41 + .../qute/deployment/InjectionTest.java | 59 ++ .../deployment/NamedBeanNotFoundTest.java | 26 + .../NamedBeanPropertyNotFoundTest.java | 42 + .../qute/deployment/PropertyNotFoundTest.java | 36 + .../deployment/ReflectionResolverTest.java | 34 + .../deployment/TypeSafeLoopFailureTest.java | 30 + .../qute/deployment/TypeSafeLoopTest.java | 38 + .../qute/deployment/VariantTemplateTest.java | 48 + extensions/qute/pom.xml | 23 + extensions/qute/runtime/pom.xml | 55 ++ .../io/quarkus/qute/api/ResourcePath.java | 34 + .../java/io/quarkus/qute/api/Variant.java | 42 + .../io/quarkus/qute/api/VariantTemplate.java | 21 + .../runtime/DefaultTemplateExtensions.java | 39 + .../quarkus/qute/runtime/EngineProducer.java | 169 ++++ .../io/quarkus/qute/runtime/QuteConfig.java | 25 + .../io/quarkus/qute/runtime/QuteRecorder.java | 63 ++ .../qute/runtime/TemplateProducer.java | 106 +++ .../qute/runtime/VariantTemplateProducer.java | 200 +++++ .../resources/META-INF/quarkus-extension.yaml | 10 + independent-projects/qute/core/pom.xml | 32 + .../src/main/java/io/quarkus/qute/Engine.java | 64 ++ .../java/io/quarkus/qute/EngineBuilder.java | 128 +++ .../main/java/io/quarkus/qute/EngineImpl.java | 151 ++++ .../java/io/quarkus/qute/EvalContext.java | 36 + .../main/java/io/quarkus/qute/Evaluator.java | 18 + .../java/io/quarkus/qute/EvaluatorImpl.java | 153 ++++ .../main/java/io/quarkus/qute/Expression.java | 116 +++ .../java/io/quarkus/qute/ExpressionNode.java | 48 + .../java/io/quarkus/qute/Expressions.java | 107 +++ .../java/io/quarkus/qute/FieldWrapper.java | 40 + .../main/java/io/quarkus/qute/Futures.java | 46 + .../java/io/quarkus/qute/IfSectionHelper.java | 271 ++++++ .../java/io/quarkus/qute/ImmutableList.java | 288 ++++++ .../io/quarkus/qute/IncludeSectionHelper.java | 65 ++ .../io/quarkus/qute/InsertSectionHelper.java | 45 + .../java/io/quarkus/qute/LiteralSupport.java | 45 + .../io/quarkus/qute/LoopSectionHelper.java | 176 ++++ .../src/main/java/io/quarkus/qute/Mapper.java | 7 + .../main/java/io/quarkus/qute/MemberKey.java | 97 ++ .../java/io/quarkus/qute/MemberWrapper.java | 38 + .../java/io/quarkus/qute/MethodWrapper.java | 40 + .../java/io/quarkus/qute/MultiResultNode.java | 30 + .../io/quarkus/qute/NamespaceResolver.java | 64 ++ .../main/java/io/quarkus/qute/Parameter.java | 33 + .../src/main/java/io/quarkus/qute/Parser.java | 627 +++++++++++++ .../io/quarkus/qute/PublisherFactory.java | 12 + .../quarkus/qute/ReflectionValueResolver.java | 175 ++++ .../io/quarkus/qute/ResolutionContext.java | 66 ++ .../quarkus/qute/ResolutionContextImpl.java | 73 ++ .../main/java/io/quarkus/qute/Resolver.java | 15 + .../java/io/quarkus/qute/ResultMapper.java | 27 + .../main/java/io/quarkus/qute/ResultNode.java | 23 + .../main/java/io/quarkus/qute/Results.java | 15 + .../java/io/quarkus/qute/SectionBlock.java | 118 +++ .../java/io/quarkus/qute/SectionHelper.java | 55 ++ .../io/quarkus/qute/SectionHelperFactory.java | 171 ++++ .../quarkus/qute/SectionInitContextImpl.java | 55 ++ .../java/io/quarkus/qute/SectionNode.java | 133 +++ .../io/quarkus/qute/SetSectionHelper.java | 81 ++ .../io/quarkus/qute/SingleResultNode.java | 41 + .../main/java/io/quarkus/qute/Template.java | 57 ++ .../java/io/quarkus/qute/TemplateData.java | 47 + .../io/quarkus/qute/TemplateExtension.java | 35 + .../java/io/quarkus/qute/TemplateImpl.java | 112 +++ .../io/quarkus/qute/TemplateInstance.java | 83 ++ .../io/quarkus/qute/TemplateInstanceBase.java | 48 + .../java/io/quarkus/qute/TemplateNode.java | 37 + .../main/java/io/quarkus/qute/TextNode.java | 43 + .../io/quarkus/qute/UserTagSectionHelper.java | 90 ++ .../java/io/quarkus/qute/ValueResolver.java | 23 + .../java/io/quarkus/qute/ValueResolvers.java | 198 +++++ .../java/io/quarkus/qute/WithPriority.java | 14 + .../io/quarkus/qute/WithSectionHelper.java | 95 ++ .../quarkus/qute/CollectionResolverTest.java | 26 + .../java/io/quarkus/qute/ExpressionTest.java | 43 + .../qute/GlobalNamespaceResolverTest.java | 31 + .../java/io/quarkus/qute/IfSectionTest.java | 66 ++ .../java/io/quarkus/qute/IncludeTest.java | 53 ++ .../java/io/quarkus/qute/LoopSectionTest.java | 105 +++ .../java/io/quarkus/qute/MapResolverTest.java | 26 + .../test/java/io/quarkus/qute/ParserTest.java | 111 +++ .../java/io/quarkus/qute/SetSectionTest.java | 18 + .../test/java/io/quarkus/qute/SimpleTest.java | 177 ++++ .../java/io/quarkus/qute/UserTagTest.java | 33 + .../java/io/quarkus/qute/WithSectionTest.java | 43 + independent-projects/qute/generator/pom.xml | 35 + .../quarkus/qute/generator/Descriptors.java | 56 ++ .../generator/ExtensionMethodGenerator.java | 256 ++++++ .../generator/ValueResolverGenerator.java | 527 +++++++++++ .../io/quarkus/qute/generator/MyItem.java | 14 + .../io/quarkus/qute/generator/MyService.java | 53 ++ .../qute/generator/PublicMyService.java | 16 + .../qute/generator/SimpleGeneratorTest.java | 145 +++ .../qute/generator/TestClassOutput.java | 50 ++ .../qute/ide-config/eclipse-format.xml | 279 ++++++ .../qute/ide-config/eclipse.importorder | 6 + independent-projects/qute/pom.xml | 373 ++++++++ independent-projects/qute/rxjava/pom.xml | 37 + .../qute/rxjava/RxjavaPublisherFactory.java | 27 + .../services/io.quarkus.qute.PublisherFactory | 1 + .../qute/rxjava/SimplePublisherTest.java | 44 + pom.xml | 1 + 135 files changed, 10591 insertions(+) create mode 100644 docs/src/main/asciidoc/qute.adoc create mode 100644 extensions/qute-resteasy/deployment/pom.xml create mode 100644 extensions/qute-resteasy/deployment/src/main/java/io/quarkus/qute/resteasy/deployment/QuteResteasyProcessor.java create mode 100644 extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/HelloResource.java create mode 100644 extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/ItemResource.java create mode 100644 extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java create mode 100644 extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java create mode 100644 extensions/qute-resteasy/pom.xml create mode 100644 extensions/qute-resteasy/runtime/pom.xml create mode 100644 extensions/qute-resteasy/runtime/src/main/java/io/quarkus/qute/resteasy/TemplateResponseFilter.java create mode 100644 extensions/qute/deployment/pom.xml create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/GeneratedValueResolverBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/ImplicitValueResolverBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/IncorrectExpressionBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateException.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateExtensionMethodBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplateVariantsBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatesAnalysisBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TypeCheckExcludeBuildItem.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TypeCheckInfo.java create mode 100644 extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/Types.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/AlternativeBasePathTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/Foo.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/Hello.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/HelloReflect.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectNamespaceResolverTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectionTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanNotFoundTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanPropertyNotFoundTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/PropertyNotFoundTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopFailureTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java create mode 100644 extensions/qute/pom.xml create mode 100644 extensions/qute/runtime/pom.xml create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/api/ResourcePath.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/api/Variant.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/api/VariantTemplate.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/DefaultTemplateExtensions.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java create mode 100644 extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java create mode 100644 extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 independent-projects/qute/core/pom.xml create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/EvalContext.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Evaluator.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Expression.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ExpressionNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/FieldWrapper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Futures.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ImmutableList.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/IncludeSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/InsertSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/LiteralSupport.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Mapper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/MemberKey.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/MemberWrapper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/MethodWrapper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/MultiResultNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/NamespaceResolver.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Parameter.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/PublisherFactory.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContext.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ResolutionContextImpl.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Resolver.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultMapper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionBlock.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionInitContextImpl.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SetSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateData.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstance.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateInstanceBase.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TextNode.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/UserTagSectionHelper.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/WithPriority.java create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/WithSectionHelper.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/CollectionResolverTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/ExpressionTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/GlobalNamespaceResolverTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/IncludeTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/MapResolverTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/SetSectionTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/UserTagTest.java create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/WithSectionTest.java create mode 100644 independent-projects/qute/generator/pom.xml create mode 100644 independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/Descriptors.java create mode 100644 independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java create mode 100644 independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java create mode 100644 independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyItem.java create mode 100644 independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyService.java create mode 100644 independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/PublicMyService.java create mode 100644 independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java create mode 100644 independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/TestClassOutput.java create mode 100644 independent-projects/qute/ide-config/eclipse-format.xml create mode 100644 independent-projects/qute/ide-config/eclipse.importorder create mode 100644 independent-projects/qute/pom.xml create mode 100644 independent-projects/qute/rxjava/pom.xml create mode 100644 independent-projects/qute/rxjava/src/main/java/io/quarkus/qute/rxjava/RxjavaPublisherFactory.java create mode 100644 independent-projects/qute/rxjava/src/main/resources/META-INF/services/io.quarkus.qute.PublisherFactory create mode 100644 independent-projects/qute/rxjava/src/test/java/com/github/mkouba/qute/rxjava/SimplePublisherTest.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index d68c2b2084011..7184ea3e17c89 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -513,6 +513,17 @@ quarkus-scala-deployment ${project.version} + + + io.quarkus + quarkus-qute-deployment + ${project.version} + + + io.quarkus + quarkus-qute-resteasy-deployment + ${project.version} + diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 943d513f695af..1346b026682b6 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -737,6 +737,16 @@ quarkus-test-kubernetes-client ${project.version} + + io.quarkus + quarkus-qute + ${project.version} + + + io.quarkus + quarkus-qute-resteasy + ${project.version} + @@ -2458,6 +2468,23 @@ quarkus-vertx-graphql ${project.version} + + + + io.quarkus.qute + qute-core + ${project.version} + + + io.quarkus.qute + qute-generator + ${project.version} + + + io.quarkus.qute + qute-rxjava + ${project.version} + diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 7165b09c85d78..90e29932f0a4c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -46,6 +46,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String REACTIVE_MYSQL_CLIENT = "reactive-mysql-client"; public static final String NEO4J = "neo4j"; public static final String OIDC = "oidc"; + public static final String QUTE = "qute"; public static final String RESTEASY = "resteasy"; public static final String RESTEASY_JACKSON = "resteasy-jackson"; public static final String RESTEASY_JAXB = "resteasy-jaxb"; diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc new file mode 100644 index 0000000000000..d762c327d3ff8 --- /dev/null +++ b/docs/src/main/asciidoc/qute.adoc @@ -0,0 +1,271 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Qute Templating Engine + +include::./attributes.adoc[] + +Qute is a templating engine designed specifically to meet the Quarkus needs. +The usage of reflection is minimized to reduce the size of native images. +The API combines both the imperative and the non-blocking reactive style of coding. +In the development mode, all files located in `META-INF/resources/templates` are watched for changes and modifications are immediately visible. +Furthermore, we try to detect most of the template problems at build time. +In this guide, you will learn how to easily render templates in your application. + +[NOTE] +==== +This extension is considered `preview`. +API or configuration properties might change as the extension matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +==== + +== Hello World with JAX-RS + +If you want to use Qute in your JAX-RS application, you need to add the `quarkus-qute-resteasy` extension first. +In your `pom.xml` file, add: + +[source,xml] +---- + + io.quarkus + quarkus-qute-resteasy + +---- + +We'll start with a very simple template: + +.hello.txt +---- +Hello {name}! <1> +---- +<1> `{name}` is a value expression that is evaluated when the template is rendered. + +NOTE: By default, all files located in the `META-INF/resources/templates` directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode. + +Now let's inject the "compiled" template in the resource class. + +.HelloResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Template; + +@Path("hello") +public class HelloResource { + + @Inject + Template hello; <1> + + @GET + @Produces(MediaType.TEXT_PLAIN) + public TemplateInstance get(@QueryParam("name") String name) { + return hello.data("name", name); <2> <3> + } +} +---- +<1> If there is no `@ResourcePath` qualifier provided the field name is used to locate the template. In this particular case, we're injecting a template with path `META-INF/resources/templates/hello.txt`. +<2> `Template.data()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering. +<3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation. + +If running your application, you can request the endpoint: + +``` +$ curl -w "\n" http://localhost:8080/hello?name=Martin +Hello Martin! +``` + +== Parameter Declarations and Template Extension Methods + +Qute has many useful features. +In this example, we'll demonstrate two of them. +If you declare a *parameter declaration* in a template then Qute attempts to validate all expressions that reference this parameter and if an incorrect expression is found the build fails. +*Template extension methods* are used to extend the set of accessible properties of data objects. + +Let's suppose we have a simple class like this: + +.Item.java +[source,java] +---- +public class Item { + public String name; + public BigDecimal price; +} +---- + +And we'd like to render a simple HTML page that contains the item name, price and also a discounted price. +The discounted price is sometimes called a "computed property". +We will implement a template extension method to render this property easily. +Let's start again with the template: + +.item.html +[source,html] +---- +{@org.acme.Item item} <1> + + + + +{item.name} <2> + + +

{item.name}

+
Price: {item.price}
+ {#if item.price > 100} <3> +
Discounted Price: {item.discountedPrice}
<4> + {/if} + + +---- +<1> Optional parameter declaration. Qute attempts to validate all expressions that reference the parameter `item`. +<2> This expression is validated. Try to change the expression to `{item.nonSense}` and the build should fail. +<3> `if` is a basic control flow section. +<4> This expression is also validated against the `Item` class and obviously there is no such property declared. However, there is a template extension method declared on the `ItemResource` class - see below. + +Finally, let's create a resource class. + +.ItemResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Template; + +@Path("item") +public class ItemResource { + + @Inject + ItemService service; + + @Inject + Template item; <1> + + @GET + @Path("{id}") + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get(@PathParam("id") Integer id) { + return item.data("item", service.findItem(id)); <2> + } + + @TemplateExtension <3> + static BigDecimal discountedPrice(Item item) { + return item.price.multiply(new BigDecimal("0.9")); + } +} +---- +<1> Inject the template with path `META-INF/resources/templates/item.html`. +<2> Make the `Item` object accessible in the template. +<3> A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name. + +== Rendering Periodic Reports + +Templating engine could be also very useful when rendering periodic reports. +You'll need to add `quarkus-scheduler` and `quarkus-qute` extensions first. +In your `pom.xml` file, add: + +[source,xml] +---- + + io.quarkus + quarkus-qute + + + io.quarkus + quarkus-scheduler + +---- + +Let's suppose the have a `SampleService` bean whose `get()` method returns a list of samples. + +.Sample.java +[source,java] +---- +public class Sample { + public boolean valid; + public String name; + public String data; +} +---- + +The template is simple: + +.report.html +[source,html] +---- + + + + +Report {now} + + +

Report {now}

+ {#for sample in samples} <1> +

{sample.name ?: 'Unknown'}

<2> +

+ {#if sample.valid} + {sample.data} + {#else} + Invalid sample found. + {/if} +

+ {/for} + + +---- +<1> The loop section makes it possible to iterate over iterables, maps and streams. +<2> This value expression is using https://en.wikipedia.org/wiki/Elvis_operator[elvis operator] - if the name is null the default value is used. + +[source,java] +.ReportGenerator.java +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; + +import io.quarkus.qute.Template; +import io.quarkus.qute.api.ResourcePath; +import io.quarkus.scheduler.Scheduled; + +public class ReportGenerator { + + @Inject + SampleService service; + + @ResourcePath("reports/v1/report_01") <1> + Template report; + + @Scheduled(cron="0 30 * * * ?") <2> + void generate() { + String result = report + .data("samples", service.get()) + .data("now", java.time.LocalDateTime.now()) + .render(); <3> + // Write the result somewhere... + } +} +---- +<1> In this case, we use the `@ResourcePath` qualifier to specify the template path: `META-INF/resources/templates/reports/v1/report_01.html`. +<2> Use the `@Scheduled` annotation to instruct Quarkus to execute this method on the half hour. For more information see the link:scheduler[Scheduler] guide. +<3> The `TemplateInstance.render()` method triggers rendering. Note that this method blocks the current thread. + +[[qute-configuration-reference]] +== Qute Configuration Reference + +include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional] \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index bf2c42c061554..af049963f40cd 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -132,6 +132,10 @@ logging-json + + + qute + qute-resteasy + org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec + org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec + org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec + org.jboss.spec.javax.transaction:jboss-transaction-api_1.3_spec + org.jboss.spec.javax.servlet:jboss-servlet-api_4.0_spec + org.jboss.spec.javax.security.jacc:jboss-jacc-api_1.5_spec + org.jboss.spec.javax.security.auth.message:jboss-jaspi-api_1.1_spec + org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec + org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.2_spec + + jakarta.xml.bind:jakarta.xml.bind-api + jakarta.ws.rs:jakarta.ws.rs-api + + javax.activation:activation + javax.activation:javax.activation-api + javax.annotation:javax.annotation-api + javax.enterprise:cdi-api + javax.inject:javax.inject + javax.json:javax.json-api + javax.json.bind:javax.json.bind-api + org.glassfish:javax.json + org.glassfish:javax.el + javax.persistence:javax.persistence-api + javax.persistence:persistence-api + javax.security.enterprise:javax.security.enterprise-api + javax.servlet:servlet-api + javax.servlet:javax.servlet-api + javax.transaction:jta + javax.transaction:javax.transaction-api + javax.validation:validation-api + javax.xml.bind:jaxb-api + javax.websocket:javax.websocket-api + javax.ws.rs:javax.ws.rs-api + + org.jboss.logging:jboss-logmanager + + javax:javaee-api + + org.wildfly.client:wildfly-client-config + org.jboss.marshalling:jboss-marshalling-osgi + org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec + + jakarta.json:jakarta.json-api + + io.netty:netty-all + + log4j:log4j + org.apache.logging.log4j:log4j-api + org.apache.logging.log4j:log4j-core + org.apache.logging.log4j:log4j-slf4j-impl + + commons-logging:commons-logging + commons-logging:commons-logging-api + org.springframework:spring-jcl + org.slf4j:jcl-over-slf4j + + org.slf4j:slf4j-simple + org.slf4j:slf4j-nop + org.slf4j:slf4j-jdk14 + org.slf4j:slf4j-log4j12 + org.slf4j:slf4j-log4j13 + + + + + + enforce + + + + + + net.revelc.code.formatter + formatter-maven-plugin + 2.8.1 + + ${maven.multiModuleProjectDirectory}/ide-config/eclipse-format.xml + ${format.skip} + + + + net.revelc.code + impsort-maven-plugin + 1.2.0 + + true + + + + +
+ + + + sonatype-nexus-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + + sonatype-nexus-releases + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + https://oss.sonatype.org/ + ossrh + false + true + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + format + + true + + !no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + process-sources + + format + + + + + + net.revelc.code + impsort-maven-plugin + + + sort-imports + + sort + + + + + true + + + + + + + validate + + true + + no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + process-sources + + validate + + + + + + net.revelc.code + impsort-maven-plugin + + true + + + + check-imports + + check + + + + + + + + + + diff --git a/independent-projects/qute/rxjava/pom.xml b/independent-projects/qute/rxjava/pom.xml new file mode 100644 index 0000000000000..749ed516c5646 --- /dev/null +++ b/independent-projects/qute/rxjava/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + io.quarkus.qute + qute-parent + 999-SNAPSHOT + ../ + + + qute-rxjava + + + + io.quarkus.qute + qute-core + + + io.reactivex.rxjava2 + rxjava + + + io.smallrye.reactive + smallrye-reactive-streams-operators-1.0 + test + + + org.junit.jupiter + junit-jupiter + test + + + + diff --git a/independent-projects/qute/rxjava/src/main/java/io/quarkus/qute/rxjava/RxjavaPublisherFactory.java b/independent-projects/qute/rxjava/src/main/java/io/quarkus/qute/rxjava/RxjavaPublisherFactory.java new file mode 100644 index 0000000000000..273634dfed756 --- /dev/null +++ b/independent-projects/qute/rxjava/src/main/java/io/quarkus/qute/rxjava/RxjavaPublisherFactory.java @@ -0,0 +1,27 @@ +package io.quarkus.qute.rxjava; + +import io.quarkus.qute.PublisherFactory; +import io.quarkus.qute.TemplateInstance; +import io.reactivex.Flowable; +import io.reactivex.processors.UnicastProcessor; +import org.reactivestreams.Publisher; + +public class RxjavaPublisherFactory implements PublisherFactory { + + @Override + public Publisher createPublisher(TemplateInstance rendering) { + return Flowable.defer(() -> { + UnicastProcessor processor = UnicastProcessor.create(); + rendering.consume(s -> processor.onNext(s)) + .whenComplete((v, t) -> { + if (t == null) { + processor.onComplete(); + } else { + processor.onError(t); + } + }); + return processor; + }); + } + +} diff --git a/independent-projects/qute/rxjava/src/main/resources/META-INF/services/io.quarkus.qute.PublisherFactory b/independent-projects/qute/rxjava/src/main/resources/META-INF/services/io.quarkus.qute.PublisherFactory new file mode 100644 index 0000000000000..01814fefefd8e --- /dev/null +++ b/independent-projects/qute/rxjava/src/main/resources/META-INF/services/io.quarkus.qute.PublisherFactory @@ -0,0 +1 @@ +io.quarkus.qute.rxjava.RxjavaPublisherFactory \ No newline at end of file diff --git a/independent-projects/qute/rxjava/src/test/java/com/github/mkouba/qute/rxjava/SimplePublisherTest.java b/independent-projects/qute/rxjava/src/test/java/com/github/mkouba/qute/rxjava/SimplePublisherTest.java new file mode 100644 index 0000000000000..db59bcd5f8309 --- /dev/null +++ b/independent-projects/qute/rxjava/src/test/java/com/github/mkouba/qute/rxjava/SimplePublisherTest.java @@ -0,0 +1,44 @@ +package com.github.mkouba.qute.rxjava; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import io.quarkus.qute.Engine; +import io.quarkus.qute.Template; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; + +public class SimplePublisherTest { + + @Test + public void test() throws InterruptedException { + Engine engine = Engine.builder().addDefaultSectionHelpers().addDefaultValueResolvers().build(); + Template template = engine.parse("{#each}{it}{/}"); + List data = Arrays.asList("foo", "foo", "alpha"); + Publisher publisher = template.instance().data(data).publisher(); + + assertPublisher((sb, l) -> ReactiveStreams.fromPublisher(publisher).forEach(sb::append).run() + .whenComplete((r, t) -> l.countDown()), "foofooalpha"); + + assertPublisher((sb, l) -> ReactiveStreams.fromPublisher(publisher).distinct().forEach(sb::append).run() + .whenComplete((r, t) -> l.countDown()), "fooalpha"); + } + + private void assertPublisher(BiConsumer test, String expected) throws InterruptedException { + StringBuilder builder = new StringBuilder(); + CountDownLatch latch = new CountDownLatch(1); + test.accept(builder, latch); + if (latch.await(2, TimeUnit.SECONDS)) { + assertEquals(expected, builder.toString()); + } else { + fail(); + } + } + +} diff --git a/pom.xml b/pom.xml index bbf2bd58af841..0d96c9da7ccc1 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ independent-projects/arc independent-projects/bootstrap independent-projects/tools + independent-projects/qute bom/runtime From 9ea76e1d912be13a0949c25f4015903147d2d6b4 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 28 Nov 2019 09:46:14 +0100 Subject: [PATCH 164/602] Move templates under src/main/resources/templates --- docs/src/main/asciidoc/qute.adoc | 10 ++--- .../TemplateResponseFilterTest.java | 2 +- .../deployment/VariantTemplateTest.java | 4 +- .../qute/deployment/QuteProcessor.java | 4 +- .../deployment/AlternativeBasePathTest.java | 42 ------------------- .../InjectNamespaceResolverTest.java | 2 +- .../qute/deployment/InjectionTest.java | 6 +-- .../deployment/NamedBeanNotFoundTest.java | 2 +- .../NamedBeanPropertyNotFoundTest.java | 2 +- .../qute/deployment/PropertyNotFoundTest.java | 2 +- .../deployment/ReflectionResolverTest.java | 2 +- .../deployment/TypeSafeLoopFailureTest.java | 2 +- .../qute/deployment/TypeSafeLoopTest.java | 2 +- .../qute/deployment/VariantTemplateTest.java | 4 +- .../quarkus/qute/runtime/EngineProducer.java | 5 ++- .../io/quarkus/qute/runtime/QuteConfig.java | 7 ---- .../io/quarkus/qute/runtime/QuteRecorder.java | 4 +- .../main/java/io/quarkus/qute/Template.java | 2 +- 18 files changed, 29 insertions(+), 75 deletions(-) delete mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/AlternativeBasePathTest.java diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index d762c327d3ff8..b3e1962d8c602 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -10,7 +10,7 @@ include::./attributes.adoc[] Qute is a templating engine designed specifically to meet the Quarkus needs. The usage of reflection is minimized to reduce the size of native images. The API combines both the imperative and the non-blocking reactive style of coding. -In the development mode, all files located in `META-INF/resources/templates` are watched for changes and modifications are immediately visible. +In the development mode, all files located in `src/main/resources/templates` are watched for changes and modifications are immediately visible. Furthermore, we try to detect most of the template problems at build time. In this guide, you will learn how to easily render templates in your application. @@ -42,7 +42,7 @@ Hello {name}! <1> ---- <1> `{name}` is a value expression that is evaluated when the template is rendered. -NOTE: By default, all files located in the `META-INF/resources/templates` directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode. +NOTE: By default, all files located in the `src/main/resources/templates` directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode. Now let's inject the "compiled" template in the resource class. @@ -72,7 +72,7 @@ public class HelloResource { } } ---- -<1> If there is no `@ResourcePath` qualifier provided the field name is used to locate the template. In this particular case, we're injecting a template with path `META-INF/resources/templates/hello.txt`. +<1> If there is no `@ResourcePath` qualifier provided the field name is used to locate the template. In this particular case, we're injecting a template with path `templates/hello.txt`. <2> `Template.data()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering. <3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation. @@ -169,7 +169,7 @@ public class ItemResource { } } ---- -<1> Inject the template with path `META-INF/resources/templates/item.html`. +<1> Inject the template with path `templates/item.html`. <2> Make the `Item` object accessible in the template. <3> A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name. @@ -261,7 +261,7 @@ public class ReportGenerator { } } ---- -<1> In this case, we use the `@ResourcePath` qualifier to specify the template path: `META-INF/resources/templates/reports/v1/report_01.html`. +<1> In this case, we use the `@ResourcePath` qualifier to specify the template path: `templates/reports/v1/report_01.html`. <2> Use the `@Scheduled` annotation to instruct Quarkus to execute this method on the half hour. For more information see the link:scheduler[Scheduler] guide. <3> The `TemplateInstance.render()` method triggers rendering. Note that this method blocks the current thread. diff --git a/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java b/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java index d44ed2a4c2a29..a5bef18d4b9f4 100644 --- a/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java +++ b/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java @@ -17,7 +17,7 @@ public class TemplateResponseFilterTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClass(HelloResource.class) - .addAsResource(new StringAsset("Hello {name}!"), "META-INF/resources/templates/hello.txt")); + .addAsResource(new StringAsset("Hello {name}!"), "templates/hello.txt")); @Test public void testFilter() { diff --git a/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java b/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java index 2a5af85f47440..d6398f1025e6f 100644 --- a/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java +++ b/extensions/qute-resteasy/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java @@ -18,9 +18,9 @@ public class VariantTemplateTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(ItemResource.class, Item.class) - .addAsResource(new StringAsset("Item {name}: {price}"), "META-INF/resources/templates/item.txt") + .addAsResource(new StringAsset("Item {name}: {price}"), "templates/item.txt") .addAsResource(new StringAsset("Item {name}: {price}"), - "META-INF/resources/templates/item.html")); + "templates/item.html")); @Test public void testVariant() { diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 6ed95d965b724..226ae6f9b2f2a 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -466,7 +466,7 @@ void collectTemplates(QuteConfig config, ApplicationArchivesBuildItem applicatio BuildProducer nativeImageResources) throws IOException { ApplicationArchive applicationArchive = applicationArchivesBuildItem.getRootArchive(); - String basePath = "META-INF/resources/" + config.basePath + "/"; + String basePath = "templates/"; Path templatesPath = applicationArchive.getChildPath(basePath); if (templatesPath != null) { @@ -495,7 +495,7 @@ void processInjectionPoints(QuteConfig config, ApplicationArchivesBuildItem appl throws IOException { ApplicationArchive applicationArchive = applicationArchivesBuildItem.getRootArchive(); - String basePath = "META-INF/resources/" + config.basePath + "/"; + String basePath = "templates/"; Path templatesPath = applicationArchive.getChildPath(basePath); // Remove suffix from the path; e.g. "items.html" becomes "items" diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/AlternativeBasePathTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/AlternativeBasePathTest.java deleted file mode 100644 index 3d1bd1002962c..0000000000000 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/AlternativeBasePathTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.quarkus.qute.deployment; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import javax.enterprise.context.Dependent; -import javax.inject.Inject; - -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.qute.Template; -import io.quarkus.test.QuarkusUnitTest; - -public class AlternativeBasePathTest { - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(SimpleBean.class) - .addAsResource(new StringAsset("quarkus.qute.base-path=myPath"), "application.properties") - .addAsResource(new StringAsset("{this}"), "META-INF/resources/myPath/foo.txt")); - - @Inject - SimpleBean simpleBean; - - @Test - public void testInjection() { - assertEquals("bar", simpleBean.foo.render("bar")); - } - - @Dependent - public static class SimpleBean { - - @Inject - Template foo; - - } - -} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectNamespaceResolverTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectNamespaceResolverTest.java index febe0d8452e02..e91b2185e1f5a 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectNamespaceResolverTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectNamespaceResolverTest.java @@ -20,7 +20,7 @@ public class InjectNamespaceResolverTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(SimpleBean.class, Hello.class) - .addAsResource(new StringAsset("{inject:hello.ping}"), "META-INF/resources/templates/foo.html")); + .addAsResource(new StringAsset("{inject:hello.ping}"), "templates/foo.html")); @Inject SimpleBean simpleBean; diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectionTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectionTest.java index 91ff2b5ce624d..b2306f5764837 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectionTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/InjectionTest.java @@ -24,9 +24,9 @@ public class InjectionTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(SimpleBean.class) .addAsResource(new StringAsset("quarkus.qute.suffixes=txt"), "application.properties") - .addAsResource(new StringAsset("{this}"), "META-INF/resources/templates/foo.txt") - .addAsResource(new StringAsset("{this}"), "META-INF/resources/templates/foo.html") - .addAsResource(new StringAsset("{this}"), "META-INF/resources/templates/bars/bar.txt")); + .addAsResource(new StringAsset("{this}"), "templates/foo.txt") + .addAsResource(new StringAsset("{this}"), "templates/foo.html") + .addAsResource(new StringAsset("{this}"), "templates/bars/bar.txt")); @Inject SimpleBean simpleBean; diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanNotFoundTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanNotFoundTest.java index 901acad1f0533..858fadfd51c2d 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanNotFoundTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanNotFoundTest.java @@ -15,7 +15,7 @@ public class NamedBeanNotFoundTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addAsResource(new StringAsset("{inject:bing.ping}"), "META-INF/resources/templates/bing.html")) + .addAsResource(new StringAsset("{inject:bing.ping}"), "templates/bing.html")) .setExpectedException(TemplateException.class); @Test diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanPropertyNotFoundTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanPropertyNotFoundTest.java index 369199910d8f7..71e9a20ec2bf6 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanPropertyNotFoundTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/NamedBeanPropertyNotFoundTest.java @@ -21,7 +21,7 @@ public class NamedBeanPropertyNotFoundTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClass(NamedFoo.class) - .addAsResource(new StringAsset("{inject:foo.list.ping}"), "META-INF/resources/templates/fooping.html")) + .addAsResource(new StringAsset("{inject:foo.list.ping}"), "templates/fooping.html")) .setExpectedException(TemplateException.class); @Test diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/PropertyNotFoundTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/PropertyNotFoundTest.java index f8fa63face006..950d05283c147 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/PropertyNotFoundTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/PropertyNotFoundTest.java @@ -17,7 +17,7 @@ public class PropertyNotFoundTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClass(Foo.class) .addAsResource(new StringAsset("{@io.quarkus.qute.deployment.PropertyNotFoundTest$Foo foo}" - + "{foo.surname}"), "META-INF/resources/templates/foo.html")) + + "{foo.surname}"), "templates/foo.html")) .setExpectedException(TemplateException.class); @Test diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java index ec1e5134d61b2..e26a6bf8d748e 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java @@ -21,7 +21,7 @@ public class ReflectionResolverTest { .addClasses(HelloReflect.class) // Make sure we do not detect the template data .addAsResource(new StringAsset("quarkus.qute.detect-template-data=false"), "application.properties") - .addAsResource(new StringAsset("{age}:{ping}:{noMatch}"), "META-INF/resources/templates/reflect.txt")); + .addAsResource(new StringAsset("{age}:{ping}:{noMatch}"), "templates/reflect.txt")); @Inject Template reflect; diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopFailureTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopFailureTest.java index 2a4300449f70f..92e2062de7ca4 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopFailureTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopFailureTest.java @@ -19,7 +19,7 @@ public class TypeSafeLoopFailureTest { .addAsResource(new StringAsset("{@java.util.List list}" + "{#for foo in list}" + "{foo.name}={foo.ages}" - + "{/}"), "META-INF/resources/templates/foo.html")) + + "{/}"), "templates/foo.html")) .setExpectedException(TemplateException.class); @Test diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopTest.java index efcb9fbc1454c..f6c6473608ccb 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TypeSafeLoopTest.java @@ -24,7 +24,7 @@ public class TypeSafeLoopTest { .addAsResource(new StringAsset("{@java.util.List list}" + "{#for foo in list}" + "{foo.name}={foo.age}={foo.charlie.name}" - + "{/}"), "META-INF/resources/templates/foo.html")); + + "{/}"), "templates/foo.html")); @Inject Template foo; diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java index b4ad5d27f1775..fbaa9cc710330 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java @@ -22,8 +22,8 @@ public class VariantTemplateTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(SimpleBean.class) - .addAsResource(new StringAsset("{this}"), "META-INF/resources/templates/foo.txt") - .addAsResource(new StringAsset("{this}"), "META-INF/resources/templates/foo.html")); + .addAsResource(new StringAsset("{this}"), "templates/foo.txt") + .addAsResource(new StringAsset("{this}"), "templates/foo.html")); @Inject SimpleBean simpleBean; diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java index 0a6b36e3eccd5..813f30baf7f3b 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java @@ -52,7 +52,7 @@ void init(QuteConfig config, List resolverClasses, List template LOGGER.debug("Initializing Qute with: {}", resolverClasses); suffixes = config.suffixes; - basePath = "META-INF/resources/" + (config.basePath.endsWith("/") ? config.basePath : config.basePath + "/"); + basePath = "templates/"; tagPath = basePath + "tags/"; EngineBuilder builder = Engine.builder() @@ -134,6 +134,9 @@ private ValueResolver createResolver(String resolverClassName) { * @return the optional reader */ private Optional locate(String path) { + // Use the system separator + // TODO String path = path.replace('/', File.separatorChar); + InputStream in = null; // First try to locate a tag template if (tags.stream().anyMatch(tag -> tag.startsWith(path))) { diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java index 4aba45097dd9b..ecb5b3bfa82dc 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java @@ -9,13 +9,6 @@ @ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public class QuteConfig { - /** - * A path relative from {@code META-INF/resources/}. All files in the base directory and its subdirectories are considered - * templates and watched for changes in the development mode. - */ - @ConfigItem(defaultValue = "templates") - public String basePath; - /** * The set of suffixes used when attempting to locate a template. */ diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java index 5b0d716a15b24..9b8fc1c7fe7c7 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteRecorder.java @@ -34,10 +34,10 @@ public static void clearTemplates(Set paths) { Set pathIds = paths.stream().map(path -> { String id = path; if (path.startsWith(engineProducer.getTagPath())) { - // ["META-INF/resources/templates/tags/item.html"] -> ["item.html"] + // ["templates/tags/item.html"] -> ["item.html"] id = path.substring(engineProducer.getTagPath().length()); } else if (path.startsWith(engineProducer.getBasePath())) { - // ["META-INF/resources/templates/items.html"] -> ["items.html"] + // ["templates/items.html"] -> ["items.html"] id = path.substring(engineProducer.getBasePath().length()); } return id; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java index 13e706ef959bc..86e1660c98d89 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java @@ -3,7 +3,7 @@ import java.util.Set; /** - * Compiled template. + * Represents a template definition. */ public interface Template { From a5e3b2164baf5087a43d58949408f6be734464f3 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Thu, 28 Nov 2019 18:53:44 +0530 Subject: [PATCH 165/602] Use filesystem specific name separator while trying to resolve a resource in RuntimeClassLoader --- .../java/io/quarkus/runner/RuntimeClassLoader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index fdd2240918091..baba2152d26d8 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -462,7 +462,16 @@ private URL findApplicationResource(String name) { Path resourcePath = null; for (Path i : applicationClassDirectories) { - resourcePath = i.resolve(name); + // Resource names are always separated by the "/" character. + // Here we are trying to resolve those resources using a filesystem + // Path, so we replace the "/" character with the filesystem + // specific separator before resolving + String path = name; + final String pathSeparator = i.getFileSystem().getSeparator(); + if (!pathSeparator.equals("/")) { + path = name.replace("/", pathSeparator); + } + resourcePath = i.resolve(path); if (Files.exists(resourcePath)) { break; } From 9102e4653be13f4330589a80589e44f0cacf3822 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 29 Nov 2019 10:46:09 +0100 Subject: [PATCH 166/602] QuteProcessor - fix validation of template injection points on windows - also fix RuntimeClassLoader.createDefaultProtectionDomain(Path) --- .../io/quarkus/runner/RuntimeClassLoader.java | 32 ++++++++++--------- .../qute/deployment/QuteProcessor.java | 11 +++++-- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index baba2152d26d8..0e58dabb7dadc 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -460,25 +460,21 @@ private Path getClassInApplicationClassPaths(String name) { private URL findApplicationResource(String name) { Path resourcePath = null; - + // Resource names are always separated by the "/" character. + // Here we are trying to resolve those resources using a filesystem + // Path, so we replace the "/" character with the filesystem + // specific separator before resolving + if (File.separatorChar != '/') { + name = name.replace('/', File.separatorChar); + } for (Path i : applicationClassDirectories) { - // Resource names are always separated by the "/" character. - // Here we are trying to resolve those resources using a filesystem - // Path, so we replace the "/" character with the filesystem - // specific separator before resolving - String path = name; - final String pathSeparator = i.getFileSystem().getSeparator(); - if (!pathSeparator.equals("/")) { - path = name.replace("/", pathSeparator); - } - resourcePath = i.resolve(path); + resourcePath = i.resolve(name); if (Files.exists(resourcePath)) { break; } } try { - return resourcePath != null && Files.exists(resourcePath) ? resourcePath.toUri() - .toURL() : null; + return resourcePath != null && Files.exists(resourcePath) ? resourcePath.toUri().toURL() : null; } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -531,14 +527,20 @@ private ProtectionDomain createDefaultProtectionDomain(Path applicationClasspath URL url = null; if (applicationClasspath != null) { try { - URI uri = new URI("file", null, applicationClasspath.toString(), null); + String path = applicationClasspath.toString(); + if (File.separatorChar != '/') { + // Note that windows separator is always quoted in the URI constructor + path = path.replace('/', File.separatorChar); + } + URI uri = new URI("file", null, path, null); url = uri.toURL(); } catch (URISyntaxException | MalformedURLException e) { - log.error("URL codeSource location for path " + applicationClasspath + " could not be created."); + log.error("URL codeSource location for path " + applicationClasspath + " could not be created.", e); } } CodeSource codesource = new CodeSource(url, (Certificate[]) null); ProtectionDomain protectionDomain = new ProtectionDomain(codesource, null, this, null); return protectionDomain; } + } diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 226ae6f9b2f2a..9e4da4fad7b19 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -3,6 +3,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static java.util.stream.Collectors.toMap; +import java.io.File; import java.io.IOException; import java.lang.reflect.Modifier; import java.nio.file.Files; @@ -501,10 +502,14 @@ void processInjectionPoints(QuteConfig config, ApplicationArchivesBuildItem appl // Remove suffix from the path; e.g. "items.html" becomes "items" Set filePaths = new HashSet(); for (TemplatePathBuildItem templatePath : templatePaths) { - filePaths.add(templatePath.getPath()); - int idx = templatePath.getPath().lastIndexOf('.'); + String filePath = templatePath.getPath(); + if (File.separatorChar != '/') { + filePath = filePath.replace(File.separatorChar, '/'); + } + filePaths.add(filePath); + int idx = filePath.lastIndexOf('.'); if (idx != -1) { - filePaths.add(templatePath.getPath().substring(0, idx)); + filePaths.add(filePath.substring(0, idx)); } } From 081e646f87369c0c6b3f8ac9dcbedd20bf4e9d24 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 29 Nov 2019 16:53:02 +0100 Subject: [PATCH 167/602] Rename quarkus-qute-resteasy to quarkus-resteasy-qute - plus other fixes --- bom/deployment/pom.xml | 2 +- bom/runtime/pom.xml | 2 +- .../builditem/FeatureBuildItem.java | 1 + .../io/quarkus/runner/RuntimeClassLoader.java | 12 +-- docs/src/main/asciidoc/qute.adoc | 11 ++- extensions/pom.xml | 2 +- .../GeneratedValueResolverBuildItem.java | 3 + .../qute/deployment/QuteProcessor.java | 97 ++++++++++--------- .../TemplateExtensionMethodBuildItem.java | 5 + .../deployment/TemplatePathBuildItem.java | 3 + .../deployment/TemplateVariantsBuildItem.java | 3 + .../TemplatesAnalysisBuildItem.java | 3 + .../deployment/TypeCheckExcludeBuildItem.java | 3 + .../qute/deployment/TypeCheckInfo.java | 5 +- .../io/quarkus/qute/api/ResourcePath.java | 9 +- .../quarkus/qute/runtime/EngineProducer.java | 18 ++-- .../io/quarkus/qute/runtime/QuteRecorder.java | 40 -------- .../qute/runtime/TemplateProducer.java | 35 ++++--- .../qute/runtime/VariantTemplateProducer.java | 11 +-- .../deployment/pom.xml | 8 +- .../deployment/ResteasyQuteProcessor.java} | 12 ++- .../resteasy/deployment/HelloResource.java | 0 .../resteasy/deployment/ItemResource.java | 0 .../TemplateResponseFilterTest.java | 0 .../deployment/VariantTemplateTest.java | 0 .../{qute-resteasy => resteasy-qute}/pom.xml | 4 +- .../runtime/pom.xml | 6 +- .../qute/runtime}/TemplateResponseFilter.java | 2 +- .../java/io/quarkus/qute/EngineBuilder.java | 2 +- .../main/java/io/quarkus/qute/EngineImpl.java | 19 ++-- .../java/io/quarkus/qute/EvaluatorImpl.java | 2 +- .../java/io/quarkus/qute/Expressions.java | 2 +- .../java/io/quarkus/qute/FieldWrapper.java | 15 --- .../main/java/io/quarkus/qute/MemberKey.java | 15 --- .../java/io/quarkus/qute/MemberWrapper.java | 15 --- .../java/io/quarkus/qute/MethodWrapper.java | 15 --- .../src/main/java/io/quarkus/qute/Parser.java | 8 -- .../java/io/quarkus/test/QuarkusUnitTest.java | 8 +- 38 files changed, 158 insertions(+), 240 deletions(-) rename extensions/{qute-resteasy => resteasy-qute}/deployment/pom.xml (88%) rename extensions/{qute-resteasy/deployment/src/main/java/io/quarkus/qute/resteasy/deployment/QuteResteasyProcessor.java => resteasy-qute/deployment/src/main/java/io/quarkus/resteasy/qute/deployment/ResteasyQuteProcessor.java} (67%) rename extensions/{qute-resteasy => resteasy-qute}/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/HelloResource.java (100%) rename extensions/{qute-resteasy => resteasy-qute}/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/ItemResource.java (100%) rename extensions/{qute-resteasy => resteasy-qute}/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/TemplateResponseFilterTest.java (100%) rename extensions/{qute-resteasy => resteasy-qute}/deployment/src/test/java/io/quarkus/qute/resteasy/deployment/VariantTemplateTest.java (100%) rename extensions/{qute-resteasy => resteasy-qute}/pom.xml (86%) rename extensions/{qute-resteasy => resteasy-qute}/runtime/pom.xml (91%) rename extensions/{qute-resteasy/runtime/src/main/java/io/quarkus/qute/resteasy => resteasy-qute/runtime/src/main/java/io/quarkus/resteasy/qute/runtime}/TemplateResponseFilter.java (98%) diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index 7184ea3e17c89..b2bfe1fc085c7 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -521,7 +521,7 @@ io.quarkus - quarkus-qute-resteasy-deployment + quarkus-resteasy-qute-deployment ${project.version} diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 1346b026682b6..41e1555e98e13 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -744,7 +744,7 @@ io.quarkus - quarkus-qute-resteasy + quarkus-resteasy-qute ${project.version} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 90e29932f0a4c..50ad08daee567 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -51,6 +51,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String RESTEASY_JACKSON = "resteasy-jackson"; public static final String RESTEASY_JAXB = "resteasy-jaxb"; public static final String RESTEASY_JSONB = "resteasy-jsonb"; + public static final String RESTEASY_QUTE = "resteasy-qute"; public static final String REST_CLIENT = "rest-client"; public static final String SCALA = "scala"; public static final String SCHEDULER = "scheduler"; diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index 0e58dabb7dadc..c323f8ba4de00 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -12,7 +12,6 @@ import java.io.Writer; import java.net.MalformedURLException; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; @@ -527,14 +526,9 @@ private ProtectionDomain createDefaultProtectionDomain(Path applicationClasspath URL url = null; if (applicationClasspath != null) { try { - String path = applicationClasspath.toString(); - if (File.separatorChar != '/') { - // Note that windows separator is always quoted in the URI constructor - path = path.replace('/', File.separatorChar); - } - URI uri = new URI("file", null, path, null); + URI uri = applicationClasspath.toUri(); url = uri.toURL(); - } catch (URISyntaxException | MalformedURLException e) { + } catch (MalformedURLException e) { log.error("URL codeSource location for path " + applicationClasspath + " could not be created.", e); } } @@ -542,5 +536,5 @@ private ProtectionDomain createDefaultProtectionDomain(Path applicationClasspath ProtectionDomain protectionDomain = new ProtectionDomain(codesource, null, this, null); return protectionDomain; } - + } diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index b3e1962d8c602..ec7c22fa72db2 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -30,7 +30,7 @@ In your `pom.xml` file, add: ---- io.quarkus - quarkus-qute-resteasy + quarkus-resteasy-qute ---- @@ -72,12 +72,13 @@ public class HelloResource { } } ---- -<1> If there is no `@ResourcePath` qualifier provided the field name is used to locate the template. In this particular case, we're injecting a template with path `templates/hello.txt`. +<1> If there is no `@ResourcePath` qualifier provided, the field name is used to locate the template. In this particular case, we're injecting a template with path `templates/hello.txt`. <2> `Template.data()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering. <3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation. -If running your application, you can request the endpoint: +If your application is running, you can request the endpoint: +[source, shell] ``` $ curl -w "\n" http://localhost:8080/hello?name=Martin Hello Martin! @@ -176,7 +177,7 @@ public class ItemResource { == Rendering Periodic Reports Templating engine could be also very useful when rendering periodic reports. -You'll need to add `quarkus-scheduler` and `quarkus-qute` extensions first. +You'll need to add the `quarkus-scheduler` and `quarkus-qute` extensions first. In your `pom.xml` file, add: [source,xml] @@ -230,7 +231,7 @@ The template is simple: ---- <1> The loop section makes it possible to iterate over iterables, maps and streams. -<2> This value expression is using https://en.wikipedia.org/wiki/Elvis_operator[elvis operator] - if the name is null the default value is used. +<2> This value expression is using the https://en.wikipedia.org/wiki/Elvis_operator[elvis operator] - if the name is null the default value is used. [source,java] .ReportGenerator.java diff --git a/extensions/pom.xml b/extensions/pom.xml index af049963f40cd..29ed60c98436c 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -45,6 +45,7 @@ resteasy-jsonb resteasy-jackson resteasy-jaxb + resteasy-qute rest-client smallrye-openapi-common smallrye-openapi @@ -135,7 +136,6 @@ qute - qute-resteasy + + io.quarkus + quarkus-resteasy + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + + + + native + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + native-image + + + true + + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/native-image-app/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/native-image-app/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..30a1efa26ccfd --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/native-image-app/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,24 @@ +package org.acme; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class HelloResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @GET + @Path("/javaLibraryPath") + @Produces(MediaType.TEXT_PLAIN) + public String javaLibraryPath() { + return System.getProperty("java.library.path"); + } + +} From 8141fd52f1a36112404a97714242284b037d8e6b Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Tue, 3 Dec 2019 10:47:15 +0100 Subject: [PATCH 169/602] Do not use latest in the Dockerfile.jvm Avoid issues such as https://github.com/quarkusio/quarkus/issues/5848 --- .../src/main/resources/templates/dockerfile-jvm.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl index c36cbd78d8652..314bfe8f159e9 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl @@ -14,7 +14,7 @@ # docker run -i --rm -p 8080:8080 quarkus/${project_artifactId}-jvm # ### -FROM fabric8/java-alpine-openjdk8-jre +FROM fabric8/java-alpine-openjdk8-jre:1.6.5 ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV AB_ENABLED=jmx_exporter COPY ${build_dir}/lib/* /deployments/lib/ From 5662c06e5fba93e6151c518d63c0bb8756c07226 Mon Sep 17 00:00:00 2001 From: Kevin Viet Date: Mon, 2 Dec 2019 15:55:44 +0100 Subject: [PATCH 170/602] Ensure that type of annotation is on a Class when resolving the stereotype annotations usage --- .../spring/di/deployment/SpringDIProcessor.java | 3 ++- .../io/quarkus/it/spring/AppConfiguration.java | 10 ++++++++++ .../io/quarkus/it/spring/CustomPrototype.java | 16 ++++++++++++++++ .../it/spring/InjectedSpringBeansResource.java | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 integration-tests/spring-di/src/main/java/io/quarkus/it/spring/CustomPrototype.java diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index 666a3f65704c3..3ffe605bdc2e1 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -129,7 +129,8 @@ AnnotationsTransformerBuildItem beanTransformer( for (final DotName name : stereotypeScopes.keySet()) { instances.put(name, index.getAnnotations(name) .stream() - .filter(it -> isAnnotation(it.target().asClass().flags())) + .filter(it -> it.target().kind() == AnnotationTarget.Kind.CLASS + && isAnnotation(it.target().asClass().flags())) .collect(Collectors.toSet())); } additionalStereotypeBuildItemBuildProducer.produce(new AdditionalStereotypeBuildItem(instances)); diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java index 7e423e530d824..5fef2202c950f 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java @@ -33,6 +33,12 @@ public AnotherRequestBean requestBean() { return new AnotherRequestBean(); } + @Bean + @CustomPrototype + public CustomPrototypeBean beanWithCustomPrototype() { + return new CustomPrototypeBean(); + } + private static class SingletonBean { } @@ -40,4 +46,8 @@ private static class SingletonBean { private static class AnotherRequestBean { } + + public static class CustomPrototypeBean { + + } } diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/CustomPrototype.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/CustomPrototype.java new file mode 100644 index 0000000000000..5bf7c8177780e --- /dev/null +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/CustomPrototype.java @@ -0,0 +1,16 @@ +package io.quarkus.it.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Scope("prototype") +public @interface CustomPrototype { +} \ No newline at end of file diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java index 6cf80fd105862..f0ceb74896841 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java @@ -1,5 +1,7 @@ package io.quarkus.it.spring; +import static io.quarkus.it.spring.AppConfiguration.CustomPrototypeBean; + import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; @@ -18,6 +20,8 @@ public class InjectedSpringBeansResource { RequestBean requestBean; @Inject SessionBean sessionBean; + @Inject + CustomPrototypeBean anotherRequestBean; @GET @Produces(MediaType.TEXT_PLAIN) From 1cb40ee10189ccb64a55efc47242e4592e953a63 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 29 Nov 2019 11:22:25 +0100 Subject: [PATCH 171/602] Logging Sentry Fixes #3985 --- bom/deployment/pom.xml | 5 ++ bom/runtime/pom.xml | 12 +++- extensions/logging-sentry/deployment/pom.xml | 65 +++++++++++++++++++ .../sentry/deployment/SentryProcessor.java | 33 ++++++++++ .../sentry/SentryLoggerCustomTest.java | 50 ++++++++++++++ .../logging/sentry/SentryLoggerTest.java | 50 ++++++++++++++ .../java/io/sentry/jvmti/ResetFrameCache.java | 8 +++ ...pplication-sentry-logger-custom.properties | 4 ++ ...plication-sentry-logger-default.properties | 2 + extensions/logging-sentry/pom.xml | 21 ++++++ extensions/logging-sentry/runtime/pom.xml | 48 ++++++++++++++ .../quarkus/logging/sentry/SentryConfig.java | 50 ++++++++++++++ .../logging/sentry/SentryConfigProvider.java | 29 +++++++++ .../sentry/SentryHandlerValueFactory.java | 33 ++++++++++ .../resources/META-INF/quarkus-extension.yaml | 10 +++ extensions/pom.xml | 3 +- 16 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 extensions/logging-sentry/deployment/pom.xml create mode 100644 extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties create mode 100644 extensions/logging-sentry/pom.xml create mode 100644 extensions/logging-sentry/runtime/pom.xml create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java create mode 100644 extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index b2bfe1fc085c7..afca6dec90c28 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -501,6 +501,11 @@ quarkus-logging-json-deployment ${project.version} + + io.quarkus + quarkus-logging-sentry-deployment + ${project.version} + io.quarkus diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 41e1555e98e13..3ba0a6b3add6b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -153,6 +153,7 @@ 1.11.0 2.10.1 3.12.6 + 1.7.28 3.1.0 3.1.7 @@ -710,6 +711,11 @@ quarkus-logging-json ${project.version} + + io.quarkus + quarkus-logging-sentry + ${project.version} + @@ -1345,7 +1351,11 @@ okhttp ${okhttp.version} - + + io.sentry + sentry + ${sentry.version} + org.apache.maven.plugin-tools maven-plugin-annotations diff --git a/extensions/logging-sentry/deployment/pom.xml b/extensions/logging-sentry/deployment/pom.xml new file mode 100644 index 0000000000000..df0b5a712d4e0 --- /dev/null +++ b/extensions/logging-sentry/deployment/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-sentry-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-sentry-deployment + Quarkus - Logging - Sentry - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-logging-sentry + + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-arc-deployment + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java b/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java new file mode 100644 index 0000000000000..f301eb67b2a86 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java @@ -0,0 +1,33 @@ +package io.quarkus.logging.sentry.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.LogHandlerBuildItem; +import io.quarkus.logging.sentry.SentryConfig; +import io.quarkus.logging.sentry.SentryHandlerValueFactory; + +class SentryProcessor { + + private static final String FEATURE = "sentry"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + LogHandlerBuildItem addSentryLogHandler(final SentryConfig sentryConfig, + final SentryHandlerValueFactory sentryHandlerValueFactory) { + return new LogHandlerBuildItem(sentryHandlerValueFactory.create(sentryConfig)); + } + + @BuildStep + ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { + return new ExtensionSslNativeSupportBuildItem(FEATURE); + } + +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java new file mode 100644 index 0000000000000..43a0d912caef3 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; +import io.sentry.jvmti.FrameCache; + +public class SentryLoggerCustomTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-custom.properties"); + + @Test + public void sentryLoggerCustomTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof SentryHandler)) + .findFirst().orElse(null); + SentryHandler sentryHandler = (SentryHandler) handler; + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); + assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isTrue(); + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java new file mode 100644 index 0000000000000..805a9bb7c83a1 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; +import io.sentry.jvmti.FrameCache; + +public class SentryLoggerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-default.properties"); + + @Test + public void sentryLoggerDefaultTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof SentryHandler)) + .findFirst().orElse(null); + SentryHandler sentryHandler = (SentryHandler) handler; + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); + assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isFalse(); + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java b/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java new file mode 100644 index 0000000000000..5b8156ddf6f33 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java @@ -0,0 +1,8 @@ +package io.sentry.jvmti; + +public class ResetFrameCache { + + public static void resetFrameCache() { + FrameCache.reset(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties new file mode 100644 index 0000000000000..ebe3085388d2b --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties @@ -0,0 +1,4 @@ +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://123@test.io/22222 +quarkus.log.sentry.level=TRACE +quarkus.log.sentry.in-app-packages=io.quarkus.logging.sentry,org.test \ No newline at end of file diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties new file mode 100644 index 0000000000000..1ead8dc2a6294 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties @@ -0,0 +1,2 @@ +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://123@test.io/22222 \ No newline at end of file diff --git a/extensions/logging-sentry/pom.xml b/extensions/logging-sentry/pom.xml new file mode 100644 index 0000000000000..c73a89369c484 --- /dev/null +++ b/extensions/logging-sentry/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + quarkus-logging-sentry-parent + Quarkus - Logging - Sentry + + pom + + + deployment + runtime + + diff --git a/extensions/logging-sentry/runtime/pom.xml b/extensions/logging-sentry/runtime/pom.xml new file mode 100644 index 0000000000000..81072784e1424 --- /dev/null +++ b/extensions/logging-sentry/runtime/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-sentry-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-sentry + Quarkus - Logging - Sentry - Runtime + Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. + + + + io.quarkus + quarkus-core + + + io.sentry + sentry + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java new file mode 100644 index 0000000000000..fb70a20f78819 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration for Sentry logging. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "log.sentry") +public class SentryConfig { + + /** + * Determine whether to enable the Sentry logging extension. + */ + @ConfigItem(name = ConfigItem.PARENT) + boolean enable; + + /** + * Sentry DSN + * + * The DSN is the first and most important thing to configure because it tells the SDK where to send events. You can find + * your project’s DSN in the “Client Keys” section of your “Project Settings” in Sentry. + */ + @ConfigItem + public String dsn; + + /** + * The sentry log level. + */ + @ConfigItem(defaultValue = "WARN") + public Level level; + + /** + * Sentry differentiates stack frames that are directly related to your application (“in application”) from stack frames + * that come from other packages such as the standard library, frameworks, or other dependencies. The difference is visible + * in the Sentry web interface where only the “in application” frames are displayed by default. + * + * You can configure which package prefixes your application uses with this option. + * + * This option is highly recommended as it affects stacktrace grouping and display on Sentry. See documentation: + * https://docs.sentry.io/clients/java/config/#in-application-stack-frames + */ + @ConfigItem + public Optional> inAppPackages; +} diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java new file mode 100644 index 0000000000000..17b854eaafa45 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java @@ -0,0 +1,29 @@ +package io.quarkus.logging.sentry; + +import static java.lang.String.join; + +import io.sentry.DefaultSentryClientFactory; +import io.sentry.config.provider.ConfigurationProvider; + +/** + * Mapping between the SentryConfig and the Sentry options {@link io.sentry.DefaultSentryClientFactory} + */ +class SentryConfigProvider implements ConfigurationProvider { + + private final SentryConfig config; + + SentryConfigProvider(SentryConfig config) { + this.config = config; + } + + @Override + public String getProperty(String key) { + switch (key) { + case DefaultSentryClientFactory.IN_APP_FRAMES_OPTION: + return config.inAppPackages.map(p -> join(",", p)).orElse(""); + // New SentryConfig options should be mapped here + default: + return null; + } + } +} diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java new file mode 100644 index 0000000000000..f415ae7820f62 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java @@ -0,0 +1,33 @@ +package io.quarkus.logging.sentry; + +import java.util.Optional; +import java.util.logging.Handler; + +import org.jboss.logging.Logger; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.config.Lookup; +import io.sentry.jul.SentryHandler; + +@Recorder +public class SentryHandlerValueFactory { + private static final Logger LOG = Logger.getLogger(SentryConfigProvider.class); + + public RuntimeValue> create(final SentryConfig config) { + if (!config.enable) { + return new RuntimeValue<>(Optional.empty()); + } + if (!config.inAppPackages.isPresent()) { + LOG.warn( + "No 'quarkus.sentry.in-app-packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry."); + } + final SentryConfigProvider provider = new SentryConfigProvider(config); + Sentry.init(SentryOptions.from(new Lookup(provider, provider), config.dsn)); + SentryHandler handler = new SentryHandler(); + handler.setLevel(config.level); + return new RuntimeValue<>(Optional.of(handler)); + } +} diff --git a/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..a92de8ea4fc95 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,10 @@ +--- +name: "Logging Sentry" +metadata: + keywords: + - "logging" + - "sentry" + - "cloud" + categories: + - "core" + status: "preview" \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index 29ed60c98436c..6370143a8565f 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -133,7 +133,8 @@ logging-json - + logging-sentry + qute From e9a60806234d77b3867701734a458d824d7b533c Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 2 Dec 2019 16:25:29 +0100 Subject: [PATCH 172/602] Add Sentry Logging guide --- docs/src/main/asciidoc/logging-sentry.adoc | 63 +++++++++++++++++++ .../resources/META-INF/quarkus-extension.yaml | 3 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 docs/src/main/asciidoc/logging-sentry.adoc diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc new file mode 100644 index 0000000000000..33eaf588b86ee --- /dev/null +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -0,0 +1,63 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Configuring Sentry Logging + +include::./attributes.adoc[] + +This guide explains sentry logging and how to configure it. + +== Run Time Configuration + +Run time configuration of logging is done through the normal `application.properties` file. + +include::{generated-dir}/config/quarkus-logging-sentry.adoc[opts=optional, leveloffset=+1] + +== Description + +Sentry is a really easy way to be notified of errors happening on you Quarkus application. + +It is a Open Source, Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. + +They offer a free starter price for cloud-based or you can self host it for free. + +== Configuration + +To start of, you need to get a Sentry DSN either by https://sentry.io/signup/[creating a Sentry account] or https://docs.sentry.io/server/[installing your self-hosted Sentry]. + +In order to configure Sentry logging, the `quarkus-logging-sentry` extension should be employed. Add this extension to your +application POM as the following snippet illustrates. + +.Modifications to POM file to add the Sentry logging extension +[source,xml] +---- + + + + + io.quarkus + quarkus-logging-sentry + + + +---- + +== Example + +.All errors and warnings occuring in any the packages will be sent to Sentry with DSN `https://abcd@sentry.io/1234` +[source, properties] +---- +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://abcd@sentry.io/1234 +---- + +.All errors occuring in the package `org.example` will be sent to Sentry with DSN `https://abcd@sentry.io/1234` +[source, properties] +---- +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://abcd@sentry.io/1234 +quarkus.log.sentry.level=ERROR +quarkus.log.sentry.in-app-packages=org.example +---- diff --git a/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml index a92de8ea4fc95..a33c51adf5586 100644 --- a/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -7,4 +7,5 @@ metadata: - "cloud" categories: - "core" - status: "preview" \ No newline at end of file + status: "preview" + guide: "https://quarkus.io/guides/logging-sentry" \ No newline at end of file From 7c2b85b6e920271ae1d89f4652910af5ec3779fd Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 3 Dec 2019 14:54:18 +0100 Subject: [PATCH 173/602] Handle Sentry in-app-packages=* and doc --- docs/src/main/asciidoc/logging-sentry.adoc | 19 ++++++++++++++++++- ...plication-sentry-logger-default.properties | 3 ++- .../quarkus/logging/sentry/SentryConfig.java | 2 +- .../logging/sentry/SentryConfigProvider.java | 6 +++++- .../sentry/SentryHandlerValueFactory.java | 2 +- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc index 33eaf588b86ee..f6475be0da5b1 100644 --- a/docs/src/main/asciidoc/logging-sentry.adoc +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -44,13 +44,30 @@ application POM as the following snippet illustrates. ---- +[id="in-app-packages"] +=== “In Application” Stack Frames +Sentry differentiates stack frames that are directly related to your application (“in application”) from stack frames that come from other packages such as the standard library, frameworks, or other dependencies. The difference is visible in the Sentry web interface where only the “in application” frames are displayed by default. + +You can configure which package prefixes your application uses with the stacktrace.app.packages option, which takes a comma separated list. +[source, properties] +---- +quarkus.log.sentry.in-app-packages=com.mycompany,com.other.name +---- +If you don’t want to use this feature but want to disable the warning, simply set it to "*": + +[source, properties] +---- +quarkus.log.sentry.in-app-packages=* +---- + == Example .All errors and warnings occuring in any the packages will be sent to Sentry with DSN `https://abcd@sentry.io/1234` [source, properties] ----- +` quarkus.log.sentry=true quarkus.log.sentry.dsn=https://abcd@sentry.io/1234 +quarkus.log.sentry.in-app-packages=* ---- .All errors occuring in the package `org.example` will be sent to Sentry with DSN `https://abcd@sentry.io/1234` diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties index 1ead8dc2a6294..cf1755dd7a2de 100644 --- a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties @@ -1,2 +1,3 @@ quarkus.log.sentry=true -quarkus.log.sentry.dsn=https://123@test.io/22222 \ No newline at end of file +quarkus.log.sentry.dsn=https://123@test.io/22222 +quarkus.log.sentry.in-app-packages=* \ No newline at end of file diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java index fb70a20f78819..0ecbfa9905129 100644 --- a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java @@ -43,7 +43,7 @@ public class SentryConfig { * You can configure which package prefixes your application uses with this option. * * This option is highly recommended as it affects stacktrace grouping and display on Sentry. See documentation: - * https://docs.sentry.io/clients/java/config/#in-application-stack-frames + * https://quarkus.io/guides/logging-sentry#in-app-packages */ @ConfigItem public Optional> inAppPackages; diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java index 17b854eaafa45..5a3f8b39c1619 100644 --- a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java @@ -2,6 +2,8 @@ import static java.lang.String.join; +import java.util.Objects; + import io.sentry.DefaultSentryClientFactory; import io.sentry.config.provider.ConfigurationProvider; @@ -20,7 +22,9 @@ class SentryConfigProvider implements ConfigurationProvider { public String getProperty(String key) { switch (key) { case DefaultSentryClientFactory.IN_APP_FRAMES_OPTION: - return config.inAppPackages.map(p -> join(",", p)).orElse(""); + return config.inAppPackages.map(p -> join(",", p)) + .filter(s -> !Objects.equals(s, "*")) + .orElse(""); // New SentryConfig options should be mapped here default: return null; diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java index f415ae7820f62..14b0eac807988 100644 --- a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java @@ -22,7 +22,7 @@ public RuntimeValue> create(final SentryConfig config) { } if (!config.inAppPackages.isPresent()) { LOG.warn( - "No 'quarkus.sentry.in-app-packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry."); + "No 'quarkus.sentry.in-app-packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry. See https://quarkus.io/guides/logging-sentry#in-app-packages"); } final SentryConfigProvider provider = new SentryConfigProvider(config); Sentry.init(SentryOptions.from(new Lookup(provider, provider), config.dsn)); From 588706679f5e90da8eb58a730c84b9d6544de5ab Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 3 Dec 2019 14:05:05 +0000 Subject: [PATCH 174/602] Update keycloak-authorization and oidc pom descriptions --- extensions/keycloak-authorization/runtime/pom.xml | 1 + extensions/oidc/runtime/pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/keycloak-authorization/runtime/pom.xml b/extensions/keycloak-authorization/runtime/pom.xml index 23d49a7f4f158..9c3f164056aa0 100644 --- a/extensions/keycloak-authorization/runtime/pom.xml +++ b/extensions/keycloak-authorization/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-keycloak-authorization Quarkus - Keycloak Authorization - Runtime + Policy enforcer using Keycloak-managed permissions to control access to protected resources diff --git a/extensions/oidc/runtime/pom.xml b/extensions/oidc/runtime/pom.xml index 9e01a62e36ec5..df7ee0169517d 100644 --- a/extensions/oidc/runtime/pom.xml +++ b/extensions/oidc/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-oidc Quarkus - OpenID Connect Adapter - Runtime - Secure your applications with OpenID Connect and Keycloak + Secure your applications with OpenID Connect Adapter and IDP such as Keycloak io.quarkus From 69f6e5cf68ae7bb82bb34649c76dc883e7045021 Mon Sep 17 00:00:00 2001 From: Cristiano Nicolai Date: Mon, 2 Dec 2019 16:07:47 -0300 Subject: [PATCH 175/602] Vertx Graphql test enhancement for websocket based connections --- .../vertx/graphql/it/VertxGraphqlTest.java | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java index 4ee948d76791e..21f6e30c73ec1 100644 --- a/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java +++ b/integration-tests/vertx-graphql/src/test/java/io/quarkus/vertx/graphql/it/VertxGraphqlTest.java @@ -1,7 +1,10 @@ package io.quarkus.vertx.graphql.it; import static io.restassured.RestAssured.given; +import static java.lang.String.format; import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -9,24 +12,27 @@ import org.eclipse.microprofile.config.ConfigProvider; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClient; +import io.vertx.core.http.WebSocket; import io.vertx.core.http.WebSocketConnectOptions; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.handler.graphql.ApolloWSMessageType; @QuarkusTest class VertxGraphqlTest { + private static Vertx vertx; + public static int getPortFromConfig() { return ConfigProvider.getConfig().getOptionalValue("quarkus.http.test-port", Integer.class).orElse(8081); } - private static Vertx vertx; - @BeforeAll public static void initializeVertx() { vertx = Vertx.vertx(); @@ -51,9 +57,34 @@ public void testWebSocketSubProtocol() throws Exception { HttpClient httpClient = vertx.createHttpClient(); WebSocketConnectOptions options = new WebSocketConnectOptions().setPort(getPortFromConfig()) .addSubProtocol("graphql-ws").setURI("/graphql"); - CompletableFuture wsFuture = new CompletableFuture<>(); - httpClient.webSocket(options, event -> wsFuture.complete(event.succeeded())); - Assertions.assertTrue(wsFuture.get(1, TimeUnit.MINUTES)); + String graphql = "{\"id\" : \"2\", \"type\" : \"start\", \"payload\" : { \"query\" : \"{ hello }\" } }"; + CompletableFuture wsFuture = new CompletableFuture<>(); + wsFuture.whenComplete((r, t) -> httpClient.close()); + httpClient.webSocket(options, ws -> { + if (ws.succeeded()) { + WebSocket webSocket = ws.result(); + webSocket.handler(message -> { + JsonObject json = message.toJsonObject(); + String type = json.getString("type"); + if (ApolloWSMessageType.DATA.getText().equals(type)) { + wsFuture.complete(message.toJsonObject()); + } else { + wsFuture.completeExceptionally(new RuntimeException( + format("Unexpected message type: %s\nMessage: %s", type, message.toString()))); + } + }); + + webSocket.write(Buffer.buffer(graphql)); + } else { + wsFuture.completeExceptionally(ws.cause()); + } + }); + + JsonObject json = wsFuture.get(1, TimeUnit.MINUTES); + assertNotNull(json); + assertEquals("2", json.getString("id")); + assertEquals("data", json.getString("type")); + assertEquals("world", json.getJsonObject("payload").getJsonObject("data").getString("hello")); } } From cf151736e2ac2f57af8a4c99ed4f304be0fd4efe Mon Sep 17 00:00:00 2001 From: Simon Bengtsson Date: Thu, 28 Nov 2019 09:31:57 +0100 Subject: [PATCH 176/602] Update keycloak version to 8.0.1 --- bom/runtime/pom.xml | 2 +- build-parent/pom.xml | 2 +- ci-templates/native-build-steps.yaml | 2 +- .../keycloak/pep/deployment/KeycloakReflectionBuildStep.java | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 3ba0a6b3add6b..87f7bbb45b20b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -171,7 +171,7 @@ 5.3.1 4.7.2 1.0.0.Final - 7.0.1 + 8.0.1 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ea0cc715df9d4..ccb5edafdc8da 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -79,7 +79,7 @@ - quay.io/keycloak/keycloak:7.0.1 + quay.io/keycloak/keycloak:8.0.1 diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 68c9206056df9..5e686718167a1 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -36,7 +36,7 @@ jobs: - script: docker run --rm --publish 8000:8000 --name build-dynamodb -d amazon/dynamodb-local:1.11.477 displayName: 'start dynamodb' - ${{ if eq(parameters.keycloak, 'true') }}: - - script: docker run --rm --publish 8180:8080 --name build-keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e JAVA_OPTS="-server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dkeycloak.profile.feature.upload_scripts=enabled" -d quay.io/keycloak/keycloak:7.0.1 + - script: docker run --rm --publish 8180:8080 --name build-keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e JAVA_OPTS="-server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dkeycloak.profile.feature.upload_scripts=enabled" -d quay.io/keycloak/keycloak:8.0.1 displayName: 'start keycloak' - ${{ if eq(parameters.mysql, 'true') }}: - bash: | diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java index 9bc5340dc1786..8ac327b114ba6 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java @@ -13,6 +13,7 @@ import org.keycloak.jose.jws.JWSHeader; import org.keycloak.json.StringListMapDeserializer; import org.keycloak.json.StringOrArrayDeserializer; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.IDToken; @@ -60,7 +61,8 @@ public void registerReflectionItems(BuildProducer refl ScopeRepresentation.class.getName(), ResourceOwnerRepresentation.class.getName(), StringListMapDeserializer.class.getName(), - StringOrArrayDeserializer.class.getName())); + StringOrArrayDeserializer.class.getName(), + OIDCConfigurationRepresentation.class.getName())); } @BuildStep From 4de70bee842adc960ee1818b4337c8f1a137849c Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 2 Dec 2019 12:49:34 -0600 Subject: [PATCH 177/602] Remove unused class --- .../java/io/quarkus/runtime/ConfigHelper.java | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/ConfigHelper.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ConfigHelper.java b/core/runtime/src/main/java/io/quarkus/runtime/ConfigHelper.java deleted file mode 100644 index 015e77867c987..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/ConfigHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.quarkus.runtime; - -import java.util.Optional; - -import org.eclipse.microprofile.config.ConfigProvider; - -public class ConfigHelper { - - private ConfigHelper() { - - } - - public static String getString(String key, String defaultValue) { - Optional val = ConfigProvider.getConfig().getOptionalValue(key, String.class); - return val.orElse(defaultValue); - } - - public static Integer getInteger(String key, int defaultValue) { - Optional val = ConfigProvider.getConfig().getOptionalValue(key, Integer.class); - return val.orElse(defaultValue); - } - - public static Boolean getBoolean(String key, boolean defaultValue) { - Optional val = ConfigProvider.getConfig().getOptionalValue(key, Boolean.class); - return val.orElse(defaultValue); - } -} From ad3eb8d18253bbcc35407929ec351ae3943b6f7c Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 2 Dec 2019 12:52:07 -0600 Subject: [PATCH 178/602] Remove now-unneeded workaround --- .../ElytronPropertiesProcessor.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java b/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java index 2a31a8c8d0986..2f77784698364 100644 --- a/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java +++ b/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java @@ -1,11 +1,8 @@ package io.quarkus.elytron.security.properties.deployment; -import java.util.Set; - import org.jboss.logging.Logger; import org.wildfly.security.auth.server.SecurityRealm; -import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -106,24 +103,6 @@ void configureMPRealmConfig(ElytronPropertiesFileRecorder recorder, if (propertiesConfig.embedded.enabled) { MPRealmConfig realmConfig = propertiesConfig.embedded; log.info("Configuring from MPRealmConfig"); - // These are not being populated correctly by the core config Map logic for some reason, so reparse them here - log.debugf("MPRealmConfig.users: %s", realmConfig.users); - log.debugf("MPRealmConfig.roles: %s", realmConfig.roles); - Set userKeys = QuarkusConfig.getNames(USERS_PREFIX); - - log.debugf("userKeys: %s", userKeys); - for (String key : userKeys) { - String pass = QuarkusConfig.getString(USERS_PREFIX + '.' + key, null, false); - log.debugf("%s.pass = %s", key, pass); - realmConfig.users.put(key, pass); - } - Set roleKeys = QuarkusConfig.getNames(ROLES_PREFIX); - log.debugf("roleKeys: %s", roleKeys); - for (String key : roleKeys) { - String roles = QuarkusConfig.getString(ROLES_PREFIX + '.' + key, null, false); - log.debugf("%s.roles = %s", key, roles); - realmConfig.roles.put(key, roles); - } RuntimeValue realm = recorder.createRealm(realmConfig); securityRealm From 48f61eccada7de1578d99dd7fc49246a39d56104 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 2 Dec 2019 12:55:32 -0600 Subject: [PATCH 179/602] Discontinue use of deprecated QuarkusConfig class --- .../phase/generateconfig/GenerateConfigTask.java | 3 --- .../java/io/quarkus/deployment/QuarkusAugmentor.java | 2 -- .../deployment/SmallRyeFaultToleranceProcessor.java | 11 +++++++---- .../smallrye/jwt/deployment/SmallRyeJwtProcessor.java | 9 ++++++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java b/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java index f8e75ce1b156a..2b80246882d0d 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/generateconfig/GenerateConfigTask.java @@ -32,7 +32,6 @@ import io.quarkus.creator.CuratedTask; import io.quarkus.creator.curator.CurateOutcome; import io.quarkus.deployment.ExtensionLoader; -import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; import io.quarkus.deployment.builditem.ExtensionClassLoaderBuildItem; @@ -115,7 +114,6 @@ public Path run(CurateOutcome appState, CuratedApplicationCreator creator) throw chainBuilder.loadProviders(runnerClassLoader); chainBuilder - .addInitial(QuarkusConfig.class) .addInitial(ShutdownContextBuildItem.class) .addInitial(LaunchModeBuildItem.class) .addInitial(ArchiveRootBuildItem.class) @@ -126,7 +124,6 @@ public Path run(CurateOutcome appState, CuratedApplicationCreator creator) throw BuildChain chain = chainBuilder .build(); BuildExecutionBuilder execBuilder = chain.createExecutionBuilder("main") - .produce(QuarkusConfig.INSTANCE) .produce(new LaunchModeBuildItem(LaunchMode.NORMAL)) .produce(new ShutdownContextBuildItem()) .produce(new LiveReloadBuildItem()) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index c4e9f2990bcee..224aad8b9a9d2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -90,7 +90,6 @@ public BuildResult run() throws Exception { chainBuilder.loadProviders(classLoader); chainBuilder - .addInitial(QuarkusConfig.class) .addInitial(ArchiveRootBuildItem.class) .addInitial(ShutdownContextBuildItem.class) .addInitial(LaunchModeBuildItem.class) @@ -115,7 +114,6 @@ public BuildResult run() throws Exception { rootFs = FileSystems.newFileSystem(root, null); } BuildExecutionBuilder execBuilder = chain.createExecutionBuilder("main") - .produce(QuarkusConfig.INSTANCE) .produce(liveReloadBuildItem) .produce(new ArchiveRootBuildItem(root, rootFs == null ? root : rootFs.getPath("/"), excludedFromIndexing)) .produce(new ShutdownContextBuildItem()) diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index d5216ecf64fed..fdf2493bc33af 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -4,12 +4,15 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.OptionalInt; import java.util.Set; import javax.annotation.Priority; import javax.inject.Inject; import javax.inject.Singleton; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; @@ -36,7 +39,6 @@ import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -172,12 +174,13 @@ public void transform(TransformationContext ctx) { .equals("io.smallrye.faulttolerance.HystrixCommandInterceptor")) { return; } + final Config config = ConfigProvider.getConfig(); - Integer priority = QuarkusConfig.getBoxedInt("mp.fault.tolerance.interceptor.priority", null, true); - if (priority != null) { + OptionalInt priority = config.getValue("mp.fault.tolerance.interceptor.priority", OptionalInt.class); + if (priority.isPresent()) { ctx.transform() .remove(ann -> ann.name().toString().equals(Priority.class.getName())) - .add(Priority.class, AnnotationValue.createIntegerValue("value", priority)) + .add(Priority.class, AnnotationValue.createIntegerValue("value", priority.getAsInt())) .done(); } } diff --git a/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java b/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java index 4a98085a44d04..9e28f285f0eaa 100644 --- a/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java +++ b/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java @@ -4,6 +4,8 @@ import java.util.Optional; import java.util.Set; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.jwt.Claim; import org.eclipse.microprofile.jwt.Claims; import org.jboss.jandex.AnnotationInstance; @@ -21,7 +23,6 @@ import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CapabilityBuildItem; @@ -96,8 +97,10 @@ FeatureBuildItem feature() { */ @BuildStep NativeImageResourceBuildItem registerNativeImageResources() { - String publicKeyLocation = QuarkusConfig.getString("mp.jwt.verify.publickey.location", null, true); - if (publicKeyLocation != null) { + final Config config = ConfigProvider.getConfig(); + Optional publicKeyLocationOpt = config.getOptionalValue("mp.jwt.verify.publickey.location", String.class); + if (publicKeyLocationOpt.isPresent()) { + final String publicKeyLocation = publicKeyLocationOpt.get(); if (publicKeyLocation.indexOf(':') < 0 || publicKeyLocation.startsWith("classpath:")) { log.infof("Adding %s to native image", publicKeyLocation); return new NativeImageResourceBuildItem(publicKeyLocation); From a65e4853e55792c0c2290388251776252cd2fb2f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 3 Dec 2019 23:50:22 +0200 Subject: [PATCH 180/602] Ensure that Transformations always happen in a safe CL Fixes #5878 Note: This is a temporary solution in any case since ClassLoaders will be dealt with in a more holistic manner soon Co-authored-by: stuartwdouglas --- .../deployment/QuarkusClassWriter.java | 19 +++---------------- .../java/io/quarkus/runner/RuntimeRunner.java | 8 ++++++++ .../test/junit/QuarkusTestExtension.java | 3 ++- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusClassWriter.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusClassWriter.java index 1b32a46a504a0..f65a543c9dac1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusClassWriter.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusClassWriter.java @@ -20,21 +20,8 @@ public QuarkusClassWriter(final int flags) { } @Override - protected String getCommonSuperClass(String type1, String type2) { - ClassLoader cl = getClassLoader(); - Class c1 = null, c2 = null; - try { - c1 = cl.loadClass(type1.replace('/', '.')); - } catch (ClassNotFoundException e) { - } - try { - c2 = cl.loadClass(type2.replace('/', '.')); - } catch (ClassNotFoundException e) { - } - if (c1 != null && c2 != null) { - return super.getCommonSuperClass(type1, type2); - } - return Object.class.getName().replace('.', '/'); + protected ClassLoader getClassLoader() { + // the TCCL is safe for transformations when this ClassWriter runs + return Thread.currentThread().getContextClassLoader(); } - } diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java index 5c1b299086664..cbb42ff10b7fc 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java @@ -29,6 +29,7 @@ import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; @@ -118,7 +119,14 @@ public void run() { functions.computeIfAbsent(i.getClassToTransform(), (f) -> new ArrayList<>()).add(i.getVisitorFunction()); } + DeploymentClassLoaderBuildItem deploymentClassLoaderBuildItem = result + .consume(DeploymentClassLoaderBuildItem.class); + ClassLoader previous = Thread.currentThread().getContextClassLoader(); + + // make sure we use the DeploymentClassLoader for executing transformers since this is the only safe CL for transformations at this point + Thread.currentThread().setContextClassLoader(deploymentClassLoaderBuildItem.getClassLoader()); transformerTarget.setTransformers(functions); + Thread.currentThread().setContextClassLoader(previous); } if (loader instanceof RuntimeClassLoader) { 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 8f09768fd54f4..db7ab598f26c9 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 @@ -190,7 +190,8 @@ public Enumeration getResources(String name) throws IOException { @Override protected ClassLoader getClassLoader() { - return temp; + // this has been previously set to a safe for transformations CL + return main; } }; ClassLoader old = Thread.currentThread().getContextClassLoader(); From 17ab864777ba7b029ba7817920c35a5794e29a5a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 4 Dec 2019 00:14:22 +0100 Subject: [PATCH 181/602] Fix minor formatting issue in Qute documentation --- docs/src/main/asciidoc/qute.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index ec7c22fa72db2..036ee26a5a9f4 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -79,10 +79,10 @@ public class HelloResource { If your application is running, you can request the endpoint: [source, shell] -``` +---- $ curl -w "\n" http://localhost:8080/hello?name=Martin Hello Martin! -``` +---- == Parameter Declarations and Template Extension Methods @@ -269,4 +269,4 @@ public class ReportGenerator { [[qute-configuration-reference]] == Qute Configuration Reference -include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional] \ No newline at end of file +include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional] From 838ad7c4fc64320fadc13360f8e8f54aa02deb1b Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 12:00:14 +1100 Subject: [PATCH 182/602] Make it easier to debug recorder issues --- .../deployment/recording/BytecodeRecorderImpl.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index 321358c01e610..d7274a26c0de5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -237,6 +237,18 @@ public T getRecordingProxy(Class theClass) { T recordingProxy = factory.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (staticInit) { + for (int i = 0; i < args.length; ++i) { + if (args[i] instanceof ReturnedProxy) { + ReturnedProxy p = (ReturnedProxy) args[i]; + if (!p.__static$$init()) { + throw new RuntimeException("Invalid proxy passed to recorder. Parameter " + i + " of type " + + method.getParameterTypes()[i] + + " was created in a runtime recorder method, while this recorder is for a static init method. The object will not have been created at the time this method is run."); + } + } + } + } StoredMethodCall storedMethodCall = new StoredMethodCall(theClass, method, args); storedMethodCalls.add(storedMethodCall); Class returnType = method.getReturnType(); From b3d9b9fe2890d1a40e36efd8fe8a536c7b07ce01 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 14:01:26 +1100 Subject: [PATCH 183/602] Increase mongo timeout There have been intermittentent failures on CI --- .../java/io/quarkus/mongodb/MongoWithReplicasTestBase.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/MongoWithReplicasTestBase.java b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/MongoWithReplicasTestBase.java index 179172320ccdf..4c3a49bba58f6 100644 --- a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/MongoWithReplicasTestBase.java +++ b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/MongoWithReplicasTestBase.java @@ -100,7 +100,8 @@ private static void initializeReplicaSet(final List mongodConfigL // Check replica set status before to proceed await() - .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) .until(() -> { Document result = mongoAdminDB.runCommand(new Document("replSetGetStatus", 1)); LOGGER.infof("replSetGetStatus: %s", result); From fd751920b7551b268bb9fa311f9961fde6420cdc Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 15:16:21 +1100 Subject: [PATCH 184/602] Make Lambda register shutdown hook Fixes #5921 --- .../io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java index d9fe96653c07a..1edbb680e66e2 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java @@ -30,7 +30,7 @@ public class QuarkusStreamHandler implements RequestStreamHandler { Class appClass = Class.forName("io.quarkus.runner.ApplicationImpl"); String[] args = {}; Application app = (Application) appClass.newInstance(); - app.start(args); + app.run(args); errorWriter.println("Quarkus bootstrapped successfully."); started = true; } catch (Exception ex) { From 4ee1d865b06e29f1841fe4439a10a5250c41289c Mon Sep 17 00:00:00 2001 From: Hebert Coelho Date: Tue, 3 Dec 2019 23:56:25 +0100 Subject: [PATCH 185/602] add artemis test module --- bom/runtime/pom.xml | 5 ++ integration-tests/artemis-core/pom.xml | 4 ++ .../it/artemis/ArtemisConsumerTest.java | 1 + .../it/artemis/ArtemisHealthCheckTest.java | 1 + .../it/artemis/ArtemisProducerTest.java | 1 + integration-tests/artemis-jms/pom.xml | 5 ++ .../it/artemis/ArtemisConsumerTest.java | 1 + .../it/artemis/ArtemisHealthCheckTest.java | 1 + .../it/artemis/ArtemisProducerTest.java | 1 + test-framework/artemis-test/pom.xml | 65 +++++++++++++++++++ .../artemis/test/ArtemisTestResource.java | 36 ++++++++++ test-framework/pom.xml | 1 + 12 files changed, 122 insertions(+) create mode 100644 test-framework/artemis-test/pom.xml create mode 100644 test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 87f7bbb45b20b..9f0fda032b0a9 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -300,6 +300,11 @@ quarkus-artemis-core ${project.version} + + io.quarkus + quarkus-test-artemis + ${project.version} + io.quarkus quarkus-artemis-jms diff --git a/integration-tests/artemis-core/pom.xml b/integration-tests/artemis-core/pom.xml index 9e0624186b2de..547d2e5356624 100644 --- a/integration-tests/artemis-core/pom.xml +++ b/integration-tests/artemis-core/pom.xml @@ -29,6 +29,10 @@ io.quarkus quarkus-artemis-core + + io.quarkus + quarkus-test-artemis + diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java index 58455bcbc93be..05bfa017e8e78 100644 --- a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java index 5f43b5e05aea7..c641e781346c4 100644 --- a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java index 058d962ff41e9..90e39a2140412 100644 --- a/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java +++ b/integration-tests/artemis-core/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/integration-tests/artemis-jms/pom.xml b/integration-tests/artemis-jms/pom.xml index 1d58cb92c961d..a5059704239b8 100644 --- a/integration-tests/artemis-jms/pom.xml +++ b/integration-tests/artemis-jms/pom.xml @@ -29,6 +29,11 @@ io.quarkus quarkus-artemis-jms + + io.quarkus + quarkus-test-artemis + test + diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java index 8a2f78c0b3b83..e05003e60c9a3 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java index 2841fedde0b46..ba46719e31f66 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHealthCheckTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java index f2fda27b2771c..e4705cdf2c903 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.artemis.test.ArtemisTestResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; diff --git a/test-framework/artemis-test/pom.xml b/test-framework/artemis-test/pom.xml new file mode 100644 index 0000000000000..73dc2f11492d6 --- /dev/null +++ b/test-framework/artemis-test/pom.xml @@ -0,0 +1,65 @@ + + + + io.quarkus + quarkus-test-framework + 999-SNAPSHOT + ../pom.xml + + + 4.0.0 + quarkus-test-artemis + Quarkus - Artemis - Test + Module that will hold all tests resources that can will be usable by Quarkus developers and users of the framework + + + + + io.quarkus + quarkus-test-common + + + org.apache.activemq + artemis-server + ${artemis.version} + + + org.apache.geronimo.specs + geronimo-json_1.0_spec + + + org.apache.johnzon + johnzon-core + + + org.jboss.logmanager + jboss-logmanager + + + commons-logging + commons-logging + + + + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java b/test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java new file mode 100644 index 0000000000000..fa94fe95cb05f --- /dev/null +++ b/test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java @@ -0,0 +1,36 @@ +package io.quarkus.artemis.test; + +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Map; + +import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ; +import org.apache.commons.io.FileUtils; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class ArtemisTestResource implements QuarkusTestResourceLifecycleManager { + + private EmbeddedActiveMQ embedded; + + @Override + public Map start() { + try { + FileUtils.deleteDirectory(Paths.get("./target/artemis").toFile()); + embedded = new EmbeddedActiveMQ(); + embedded.start(); + } catch (Exception e) { + throw new RuntimeException("Could not start embedded ActiveMQ server", e); + } + return Collections.emptyMap(); + } + + @Override + public void stop() { + try { + embedded.stop(); + } catch (Exception e) { + throw new RuntimeException("Could not stop embedded ActiveMQ server", e); + } + } +} diff --git a/test-framework/pom.xml b/test-framework/pom.xml index c4a590f4a1683..8198712d1e6b5 100644 --- a/test-framework/pom.xml +++ b/test-framework/pom.xml @@ -14,6 +14,7 @@ Quarkus - Test Framework pom + artemis-test common h2 derby From 6855d5e92e82b1f58c7e5c4ebd6b3b72f6636f0e Mon Sep 17 00:00:00 2001 From: Maciej Swiderski Date: Thu, 28 Nov 2019 14:02:45 +0100 Subject: [PATCH 186/602] kogito-ext: Upgrade of kogito to 0.6.0 and updated jandex proto generator to allow exclusion of static and transient, support simple types for collections --- bom/runtime/pom.xml | 4 ++-- .../deployment/JandexProtoGenerator.java | 20 +++++++++++++------ .../deployment/KogitoAssetsProcessor.java | 10 +++++----- .../deployment/KogitoCompilationProvider.java | 6 +++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 9f0fda032b0a9..67f5f53e184c1 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -158,7 +158,7 @@ 3.1.0 3.1.7 0.1.0 - 0.5.1 + 0.6.0 4.6.4 0.19.1 2.2.0 @@ -2483,7 +2483,7 @@ quarkus-vertx-graphql ${project.version} - + io.quarkus.qute diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java index d5f95d91e849a..e3269fa22c410 100644 --- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java +++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/JandexProtoGenerator.java @@ -1,5 +1,6 @@ package io.quarkus.kogito.deployment; +import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -71,6 +72,11 @@ protected ProtoMessage messageFromClass(Proto proto, ClassInfo clazz, IndexView for (FieldInfo pd : clazz.fields()) { + // ignore static and/or transient fields + if (Modifier.isStatic(pd.flags()) || Modifier.isTransient(pd.flags())) { + continue; + } + String fieldTypeString = pd.type().name().toString(); DotName fieldType = pd.type().name(); @@ -90,7 +96,11 @@ protected ProtoMessage messageFromClass(Proto proto, ClassInfo clazz, IndexView } if (protoType == null) { - ProtoMessage another = messageFromClass(proto, index.getClassByName(fieldType), index, packageName, + ClassInfo classInfo = index.getClassByName(fieldType); + if (classInfo == null) { + throw new IllegalStateException("Cannot find class info in jandex index for " + fieldType); + } + ProtoMessage another = messageFromClass(proto, classInfo, index, packageName, messageComment, fieldComment); protoType = another.getName(); } @@ -134,7 +144,7 @@ protected void generateModelClassProto(ClassInfo modelClazz, String targetDirect if (processId != null) { Proto modelProto = generate("@Indexed", - "@Field(store = Store.YES, analyze = Analyze.YES)", + INDEX_COMMENT, modelClazz.name().prefix().toString() + "." + processId, modelClazz, "import \"kogito-index.proto\";", "import \"kogito-types.proto\";", @@ -143,10 +153,8 @@ protected void generateModelClassProto(ClassInfo modelClazz, String targetDirect ProtoMessage modelMessage = modelProto.getMessages().stream().filter(msg -> msg.getName().equals(name)).findFirst() .orElseThrow(() -> new IllegalStateException("Unable to find model message")); - modelMessage.addField("repeated", "org.kie.kogito.index.model.ProcessInstanceMeta", "processInstances") - .setComment("@Field(store = Store.YES, analyze = Analyze.YES)"); - modelMessage.addField("repeated", "org.kie.kogito.index.model.UserTaskInstanceMeta", "userTasks") - .setComment("@Field(store = Store.YES, analyze = Analyze.YES)"); + modelMessage.addField("optional", "org.kie.kogito.index.model.KogitoMetadata", "metadata") + .setComment(INDEX_COMMENT); Path protoFilePath = Paths.get(targetDirectory, "classes", "/persistence/" + processId + ".proto"); diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java index d775ff1414db2..7e031553e4c48 100644 --- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java +++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java @@ -20,6 +20,7 @@ import org.drools.compiler.commons.jci.compilers.JavaCompilerSettings; import org.drools.compiler.compiler.io.memory.MemoryFileSystem; import org.drools.compiler.kproject.models.KieModuleModelImpl; +import org.drools.modelcompiler.builder.GeneratedFile; import org.drools.modelcompiler.builder.JavaParserCompiler; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.CompositeIndex; @@ -32,7 +33,6 @@ import org.kie.internal.kogito.codegen.Generated; import org.kie.kogito.Model; import org.kie.kogito.codegen.ApplicationGenerator; -import org.kie.kogito.codegen.GeneratedFile; import org.kie.kogito.codegen.decision.DecisionCodegen; import org.kie.kogito.codegen.di.CDIDependencyInjectionAnnotator; import org.kie.kogito.codegen.process.ProcessCodegen; @@ -227,11 +227,11 @@ private CompilationResult compile(ArchiveRootBuildItem root, MemoryFileSystem tr String[] sources = new String[generatedFiles.size()]; int index = 0; for (GeneratedFile entry : generatedFiles) { - String generatedClassFile = entry.relativePath().replace("src/main/java/", ""); + String generatedClassFile = entry.getPath().replace("src/main/java/", ""); String fileName = toRuntimeSource(toClassName(generatedClassFile)); sources[index++] = fileName; - srcMfs.write(fileName, entry.contents()); + srcMfs.write(fileName, entry.getData()); String location = generatedClassesDir; if (launchMode == LaunchMode.DEVELOPMENT) { @@ -351,10 +351,10 @@ private void writeGeneratedFile(GeneratedFile f, String location) throws IOExcep if (location == null) { return; } - String generatedClassFile = f.relativePath().replace("src/main/java", ""); + String generatedClassFile = f.getPath().replace("src/main/java", ""); Files.write( pathOf(location, generatedClassFile), - f.contents()); + f.getData()); } private Path pathOf(String location, String end) { diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java index 341317d4e2449..10a83fdc53670 100644 --- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java +++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoCompilationProvider.java @@ -12,8 +12,8 @@ import java.util.Map; import java.util.Set; +import org.drools.modelcompiler.builder.GeneratedFile; import org.kie.kogito.codegen.ApplicationGenerator; -import org.kie.kogito.codegen.GeneratedFile; import org.kie.kogito.codegen.Generator; import org.kie.kogito.codegen.di.CDIDependencyInjectionAnnotator; @@ -44,8 +44,8 @@ public final void compile(Set filesToCompile, Context context) { HashSet generatedSourceFiles = new HashSet<>(); for (GeneratedFile file : generatedFiles) { - Path path = pathOf(outputDirectory.getPath(), file.relativePath()); - Files.write(path, file.contents()); + Path path = pathOf(outputDirectory.getPath(), file.getPath()); + Files.write(path, file.getData()); generatedSourceFiles.add(path.toFile()); } super.compile(generatedSourceFiles, context); From 579a66f791db44ca6af9b3badf7be674238cf5cc Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 3 Dec 2019 16:43:50 +0100 Subject: [PATCH 187/602] First version of Qute Reference Guide - also add TemplateDataTest and fix some bugs in ValueResolverGenerator - also add ValueResolver to support ternary operators --- docs/src/main/asciidoc/cdi-reference.adoc | 4 - docs/src/main/asciidoc/qute-reference.adoc | 699 ++++++++++++++++++ .../qute/deployment/TemplateDataTest.java | 50 ++ .../quarkus/qute/runtime/EngineProducer.java | 2 +- .../io/quarkus/qute/runtime/QuteConfig.java | 7 +- .../java/io/quarkus/qute/EngineBuilder.java | 7 +- .../java/io/quarkus/qute/Expressions.java | 18 +- .../java/io/quarkus/qute/ValueResolvers.java | 54 +- .../java/io/quarkus/qute/ExpressionTest.java | 1 + .../test/java/io/quarkus/qute/SimpleTest.java | 14 + .../generator/ValueResolverGenerator.java | 452 ++++++++--- 11 files changed, 1174 insertions(+), 134 deletions(-) create mode 100644 docs/src/main/asciidoc/qute-reference.adoc create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateDataTest.java diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 393ddcd55340f..4116fcdcd3fe6 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -11,10 +11,6 @@ include::./attributes.adoc[] :sectnumlevels: 4 :toc: -:numbered: -:sectnums: -:sectnumlevels: 4 - Quarkus DI solution is based on the http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html[Contexts and Dependency Injection for Java 2.0, window="_blank"] specification. However, it is not a full CDI implementation verified by the TCK. Only a subset of the CDI features is implemented - see also <> and <>. diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc new file mode 100644 index 0000000000000..23de48001463f --- /dev/null +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -0,0 +1,699 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Qute Reference Guide + +include::./attributes.adoc[] + +:numbered: +:sectnums: +:sectnumlevels: 4 +:toc: + +== Hello World Example + +In this example, we'd like to demonstrate the basic workflow when working with Qute templates. +Let's start with a simple hello world example. +We will always need some *template contents*: + +.hello.html +[source,html] +---- + +

Hello {name}! <1> + +---- +<1> `{name}` is a value expression that is evaluated when the template is rendered. + +Then, we will need to parse the contents into a *template definition* Java object. +A template definition is an instance of `io.quarkus.qute.Template`. + +If using Qute "standalone" you'll need to create an instance of `io.quarkus.qute.Engine` first. +The `Engine` represents a central point for template management with dedicated configuration. +Let's use the convenient builder: + +[source,java] +---- +Engine engine = Engine.builder().addDefaults().build(); +---- + +TIP: In Quarkus, there is a preconfigured `Engine` available for injection - see <>. + +If we have an `Engine` instance we could parse the template contents: + +[source,java] +---- +Template helloTemplate = engine.parse(helloHtmlContent); +---- + +TIP: In Quarkus, you can simply inject the template definition. The template is automatically parsed and cached - see <>. + +Finally, we will create a *template instance*, set the data and render the output: + +[source,java] +---- +// Renders

Hello Jim!

+helloTemplate.data("name", "Jim").render(); <1> +---- +<1> `Template.data(String, Object)` is a convenient method that creates a template instance and sets the data in one step. + +So the workflow is simple: + +1. Create template contents (`hello.html`), +2. Parse template definition (`io.quarkus.qute.Template`), +3. Create template instance (`io.quarkus.qute.TemplateInstance`), +4. Render output. + +The `Engine` is able to cache the definitions so that it's not necessary to parse the contents again and again. + +NOTE: In Quarkus, the caching is done automatically. + +== Core Features + +=== Syntax and Building Blocks + +The dynamic parts of a template include: + +* *comment* +** `{! This is a comment !}`, +** could be multi-line, +** may contain expressions and sections: `{! {#if true} !}`. +* *expression* +** outputs the evaluated value, +** simple properties: `{foo}`, `{item.name}`, +** virtual methods: `{item.get(name)}`, `{name ?: 'John'}`, +** with namespace: `{inject:colors}`. +* *section tag* +** may contain expressions and sections: `{#if foo}{foo.name}{/if}`, +** the name in the closing tag is optional: `{#if active}ACTIVE!{/}`, +** can be empty: `{#myTag image=true /}`, +** may declare nested section blocks: `{#if item.valid} Valid. {#else} Invalid. {/if}` and decide which block to render. + +==== Expressions + +An expression consists of: + +* an optional namespace followed by a colon `:`, +* parts separated by dot `.`. + +The first part of the expression is always resolved against the <>. +If no result is found for the first part it's resolved against the parent context object (if available). +For an expression that starts with a namespace the current context object is found using the available ``NamespaceResolver``s. +For an expression that does not start with a namespace the current context object is *derived from the position* of the tag. +All other parts are resolved using `ValueResolver` s against the result of the previous resolution. + +For example, expression `{name}` has no namespace and single part - "name". +The "name" will be resolved using all available value resolvers against the current context object. +However, the expression `{global:colors}` has the namespace "global" and single part - "colors". +First, all available `NamespaceResolver` s will be used to find the current context object. +And afterwards value resolvers will be used to resolve "colors" against the context object found. + +[source] +---- +{name} <1> +{item.name} <2> +{global:colors} <3> +---- +<1> no namespace, one part -`name` +<2> no namespace, two parts - `item`, `name` +<3> namespace `global`, one part - `colors` + +An expression part could be a "virtual method" in which case the name can be followed by parameters in parentheses. + +[source] +---- +{item.getLabels(1)} <1> +{name or 'John'} <2> +---- +<1> no namespace, two parts - `item`, `getLabels(1)`, the second part is a virtual method with name `getLabels` and params `1` +<2> infix notation, translated to `name.or('John')`; no namespace, two parts - `name`, `or('John')` + +[[current_context_object]] +===== Current Context + +If an expression does not specify a namespace the current context object is derived from the position of the tag. +By default, the current context object represents the data passed to the template instance. +However, sections may change the current context object. +A typical example is the for/each loop - during iteration the content of the section is rendered with each element as the current context object: + +[source] +---- +{#each items} + {count}. {it.name} <1> +{/each} + +{! Another form of iteration... !} +{#for item in items} + {count}. {item.name} <2> +{/for} +---- +<1> `it` is an implicit alias. `name` is resolved against the current iteration element. +<2> Loop with an explicit alias `item`. + +[TIP] +==== +Data passed to the template instance are always accessible using the `data` namespace. +This could be useful to access data for which the key is overriden: + +[source,html] +---- + +{item.name} <1> +
    +{#for item in item.getDerivedItems()} <2> +
  • + {item.name} <3> + is derived from + {data:item.name} <4> +
  • +{/for} +
+ +---- +<1> `item` is passed to the template instance as a data object. +<2> Iterate over the list of derived items. +<3> `item` is an alias for the iterated element. +<4> Use the `data` namespace to access the `item` data object. + +==== + +==== Sections + +A section: + +* has a start tag +** starts with `#`, followed by the name of the section such as `{#if}` and `{#each}`, +* may be empty +** tag ends with `/`, ie. `{#emptySection /}` +* may contain other expression, sections, etc. +** the end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`, + +The start tag can also define parameters. +The parameters have optional names. +A section may contain several content *blocks*. +The "main" block is always present. +Additional/nested blocks also start with `#` and can have parameters too - `{#else if item.isActive}`. +A section helper that defines the logic of a section can "execute" any of the blocks and evaluate the parameters. + +[source] +---- +{#if item.name is 'sword'} + It's a sword! +{#else if item.name is 'shield'} + It's a shield! +{#else} + Item is neither a sword nor a shield. +{/if} +---- + +===== Loop Section + +The loop section makes it possible to iterate over an instance of `Iterable`, `Map` 's entry set and `Stream`. +It has two flavors. +The first one is using the `each` name alias. + +[source] +---- +{#each items} + {it.name} <1> +{/each} +---- +<1> `it` is an implicit alias. `name` is resolved against the current iteration element. + +The other form is using the `for` name alias and can specify the alias used to reference the iteration element: + +[source] +---- +{#for item in items} + {item.name} +{/for} +---- + +It's also possible to access the iteration metadata inside the loop: + +[source] +---- +{#each items} + {count}. {it.name} <1> +{/each} +---- +<1> `count` represents one-based index. Metadata also include zero-based `index`, `hasNext`, `odd`, `even`. + +===== If Section + +A basic control flow section. +The simplest possible version accepts a single parameter and renders the content if it's evaluated to `true` (or `Boolean.TRUE`). + +[source] +---- +{#if item.active} + This item is active. +{/if} +---- + +You can also use the following operators: + +|=== +|Operator |Aliases + +|equals +|`eq`, `==`, `is` + +|not equals +|`ne`, `!=` + +|greater than +|`gt`, `>` + +|greater than or equal to +|`ge`, `>=` + +|less than +|`lt`, `<` + +|less than or equal to +|`le`, `\<=` + +|=== + +[source] +---- +{#if item.age > 10} + This item is very old. +{/if} +---- + +NOTE: Multiple conditions are not supported. + +You can add any number of "else" blocks: + +[source] +---- +{#if item.age > 10} + This item is very old. +{#else if item.age > 5} + This item is quite old. +{#else if item.age > 2} + This item is old. +{#else} + This item is not old at all! +{/if} +---- + +===== With Section + +This section can be used to set the current context object. +This could be useful to simplify the template structure. + +[source] +---- +{#with item.parent} + {name} <1> +{/with} +---- +<1> The name will be resolved against the `item.parent`. + +It's also possible to specify an alias for the context object: + +[source] +---- +{#with item.parent as myParent} + {myParent.name} +{/with} +---- + +[[include_helper]] +===== Include/Insert Sections + +These sections can be used to include another template and possibly override some parts of the template (template inheritance). + +.Template "base" +[source,html] +---- + + + +{#insert title}Default Title{/} <1> + + + {#insert body}No body!{/} <2> + + +---- +<1> `insert` sections are used to specify parts that could be overriden by a template that includes the given template. +<2> An `insert` section may define the default content that is rendered if not overriden. + +.Template "detail" +[source,html] +---- +{#include base} <1> + {#title}My Title{/title} <2> + {#body} +
+ My body. +
+{/include} +---- +<1> `include` section is used to specify the extended template. +<2> Nested blocks are used to specify the parts that should be overriden. + +NOTE: Section blocks can also define an optional end tag - `{/title}`. + +[[user_tags]] +===== User-defined Tags + +User-defined tags can be used to include a template and optionally pass some parameters. +Let's suppose we have a template called `item.html`: + +[source] +---- +{#if showImage} <1> + {it.image} <2> +{/if} +---- +<1> `showImage` is a named parameter. +<2> `it` is a special key that is replaced with the first unnamed param of the tag. + +Now if we register this template under the name `item` and if we add a `UserTagSectionHelper` to the engine: + +[source,java] +---- +Engine engine = Engine.builder() + .addSectionHelper(new UserTagSectionHelper.Factory("item")) + .build(); +---- + +NOTE: In Quarkus, all files from the `src/main/resources/templates/tags` are registered and monitored automatically. + +We can include the tag like this: + +[source,html] +---- +
    +{#each items} +
  • + {#item this showImage=true /} <1> +
  • +{/each} +
+---- +<1> `this` is resolved to an iteration element and can be referenced using the `it` key in the tag template. + +=== Engine Configuration + +==== Template Locator + +Manual registration is sometimes handy but it's also possible to register a template locator using `EngineBuilder.addLocator(Function>)`. +This locator is used whenever the `Engine.getTemplate()` method is called and the engine has no template for a given id stored in the cache. + +NOTE: In Quarkus, all templates from the `src/main/resources/templates` are located automatically. + +[[quarkus_integration]] +== Quarkus Integration + +If you want to use Qute in your Quarkus application add the following dependency to your project: + +[source,xml] +---- + + io.quarkus + quarkus-qute + +---- + +In Quarkus, a preconfigured engine instance is provided and available for injection - a bean with scope `@Singleton`, bean type `io.quarkus.qute.Engine` and qualifier `@Default` is registered automatically. +Moreover, all templates located in the `src/main/resources/templates` directory are validated and can be easily injected. + +[source,java] +---- +import io.quarkus.qute.Engine; +import io.quarkus.qute.Template; +import io.quarkus.qute.api.ResourcePath; + +class MyBean { + + @Inject + Template items; <1> + + @ResourcePath("detail/items2_v1.html") <2> + Template items2; + + @Inject + Engine engine; <3> + +} +---- +<1> If there is no `ResourcePath` qualifier provided, the field name is used to locate the template. In this particular case, the container will attempt to locate a template with path `src/main/resources/templates/items.html`. +<2> The `ResourcePath` qualifier instructs the container to inject a template from a path relative from `src/main/resources/templates`. In this case, the full path is `src/main/resources/templates/detail/items2_v1.html`. +<3> Inject the configured `Engine` instance. + +=== Injecting Beans Directly In Templates + +A CDI bean annotated with `@Named` can be referenced in any template through the `inject` namespace: + +[source,html] +---- +{inject:foo.price} <1> +---- +<1> First, a bean with name `foo` is found and then used as the base object. + +All expressions using the `inject` namespace are validated during build. +For the expression `inject:foo.price` the implementation class of the injected bean must either have the `price` property (e.g. a `getPrice()` method) or a matching <> must exist. + +NOTE: A `ValueResolver` is also generated for all beans annotated with `@Named` so that it's possible to access its properties without reflection. + +=== Parameter Declarations + +It is possible to specify optional parameter declarations in a template. +Quarkus attempts to validate all expressions that reference such parameters. +If an invalid/incorrect expression is found the build fails. + +NOTE: Only properties are currently validated in expressions; virtual methods are currently ignored. + +[source,html] +---- +{@org.acme.Foo foo} <1> + + + + +Qute Hello + + +

{title}

<2> + Hello {foo.message}! <3> + + +---- +<1> Parameter declaration - maps `foo` to `org.acme.Foo`. +<2> Not validated - not matching a param declaration. +<3> This expression is validated. `org.acme.Foo` must have a property `message` or a matching template extension method must exist. + +NOTE: A value resolver is also generated for all types used in parameter declarations so that it's possible to access its properties without reflection. + +==== Overriding Parameter Declarations + +[source,html] +---- +{@org.acme.Foo foo} + + + + +Qute Hello + + +

{foo.message}

<1> + {#for foo in baz.foos} +

Hello {foo.message}!

<2> + {/for} + + +---- +<1> Validated against `org.acme.Foo`. +<2> Not validated - `foo` is overriden in the loop section. + +[[template_extension_methods]] +=== Template Extension Methods + +A value resolver is automatically generated for a template extension method annotated with `@TemplateExtension`. +The method must be static, must not return `void` and must accept at least one parameter. +The class of the first parameter is used to match the base object and the method name is used to match the property name. + +[source,java] +---- +package org.acme; + +class Item { + + public final BigDecimal price; + + public Item(BigDecimal price) { + this.price = price; + } +} + +class MyExtensions { + + @TemplateExtension + static BigDecimal discountedPrice(Item item) { <1> + return item.getPrice().multiply(new BigDecimal("0.9")); + } +} +---- +<1> The method matches `Item.class` and `discountedPrice` property name. + +This template extension method makes it possible to render the following template: + +[source,html] +---- +{#each items} <1> + {it.discountedPrice} +{/each} +---- +<1> `items` is resolved to a list of `org.acme.Item` instances. + +=== @TemplateData + +A value resolver is automatically generated for a type annotated with `@TemplateData`. +This allows Quarkus to avoid using reflection to access the data at runtime. + +NOTE: Non-public members, constructors, static initializers, static, synthetic and void methods are always ignored. + +[source,java] +---- +package org.acme; + +@TemplateData +class Item { + + public final BigDecimal price; + + public Item(BigDecimal price) { + this.price = price; + } + + public BigDecimal getDiscountedPrice() { + return price.multiply(new BigDecimal("0.9")); + } +} +---- + +Any instance of `Item` can be used directly in the template: + +[source,html] +---- +{#each items} <1> + {it.price} / {it.discountedPrice} +{/each} +---- +<1> `items` is resolved to a list of `org.acme.Item` instances. + +Furthermore, `@TemplateData.properties()` and `@TemplateData.ignore()` can be used to fine-tune the generated resolver. +Finally, it is also possible to specify the "target" of the annotation - this could be useful for third-party classes not controlled by the application: + +[source,java] +---- +@TemplateData(target = BigDecimal.class) +@TemplateData +class Item { + + public final BigDecimal price; + + public Item(BigDecimal price) { + this.price = price; + } +} +---- + +[source,html] +---- +{#each items} <1> + {it.price.setScale(2, rounding)} <1> +{/each} +---- +<1> The generated value resolver knows how to invoke the `BigDecimal.setScale()` method. + +=== RESTEasy Integration + +If you want to use Qute in your JAX-RS application, you'll need to add the `quarkus-resteasy-qute` extension first. +In your `pom.xml` file, add: + +[source,xml] +---- + + io.quarkus + quarkus-resteasy-qute + +---- + +This extension registers a special `ContainerResponseFilter` implementation so that a resource method can return a `TemplateInstance` and the filter takes care of all necessary steps. +A simple JAX-RS resource may look like this: + +.HelloResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Template; + +@Path("hello") +public class HelloResource { + + @Inject + Template hello; <1> + + @GET + @Produces(MediaType.TEXT_PLAIN) + public TemplateInstance get(@QueryParam("name") String name) { + return hello.data("name", name); <2> <3> + } +} +---- +<1> If there is no `@ResourcePath` qualifier provided, the field name is used to locate the template. In this particular case, we're injecting a template with path `templates/hello.txt`. +<2> `Template.data()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering. +<3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation. + +==== Variant Templates + +Sometimes it could be useful to render a specific variant of the template based on the content negotiation. +`VariantTemplate` is a perfect match for this use case: + +[source,java] +---- +@Path("/detail") +class DetailResource { + + @Inject + VariantTemplate item; <1> + + @GET + @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN }) + public Rendering item() { + return item.data(new Item("Alpha", 1000)); <2> + } +} +---- +<1> Inject a variant template with base path derived from the injected field - `src/main/resources/templates/item`. +<2> The resulting output depends on the `Accept` header received from the client. For `text/plain` the `src/main/resources/templates/item.txt` template is used. For `text/html` the `META-INF/resources/templates/item.html` template is used. + + +=== Development Mode + +In the development mode, all files located in `src/main/resources/templates` are watched for changes and modifications are immediately visible. + +=== Configuration Reference + +include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional] + +== Extension Points + +TODO \ No newline at end of file diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateDataTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateDataTest.java new file mode 100644 index 0000000000000..c5e8e569d9045 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateDataTest.java @@ -0,0 +1,50 @@ +package io.quarkus.qute.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateData; +import io.quarkus.test.QuarkusUnitTest; + +public class TemplateDataTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(Foo.class) + .addAsResource(new StringAsset("{foo.val} is not {foo.val.setScale(2,roundingMode)}"), + "templates/foo.txt")); + + @Inject + Template foo; + + @Test + public void testTemplateData() { + assertEquals("123.4563 is not 123.46", + foo.data("roundingMode", RoundingMode.HALF_UP).data("foo", new Foo(new BigDecimal("123.4563"))).render()); + } + + @TemplateData + @TemplateData(target = BigDecimal.class) + public static class Foo { + + public final BigDecimal val; + + public Foo(BigDecimal val) { + this.val = val; + } + + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java index 77a8ab61e40f9..2beda61a1de49 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java @@ -59,7 +59,7 @@ void init(QuteConfig config, List resolverClasses, List template // We don't register the map resolver because of param declaration validation // See DefaultTemplateExtensions - builder.addValueResolvers(ValueResolvers.thisResolver(), ValueResolvers.orResolver(), + builder.addValueResolvers(ValueResolvers.thisResolver(), ValueResolvers.orResolver(), ValueResolvers.trueResolver(), ValueResolvers.collectionResolver(), ValueResolvers.mapperResolver(), ValueResolvers.mapEntryResolver()); // Fallback reflection resolver diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java index ecb5b3bfa82dc..bfb9137f0accc 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java @@ -10,7 +10,12 @@ public class QuteConfig { /** - * The set of suffixes used when attempting to locate a template. + * The set of suffixes used when attempting to locate a template file. + * + * By default, `engine.getTemplate("foo")` would result in several lookups: `src/main/resources/templates/foo`, + * `src/main/resources/templates/foo.html` and `src/main/resources/templates/foo.txt`. + * + * @asciidoclet */ @ConfigItem(defaultValue = "html,txt") public List suffixes; diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java index 4bed70bd36138..04446fa3d07a9 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java @@ -6,6 +6,7 @@ import static io.quarkus.qute.ValueResolvers.mapperResolver; import static io.quarkus.qute.ValueResolvers.orResolver; import static io.quarkus.qute.ValueResolvers.thisResolver; +import static io.quarkus.qute.ValueResolvers.trueResolver; import java.io.Reader; import java.util.ArrayList; @@ -85,7 +86,11 @@ public EngineBuilder addValueResolver(ValueResolver resolver) { */ public EngineBuilder addDefaultValueResolvers() { return addValueResolvers(mapResolver(), mapperResolver(), mapEntryResolver(), collectionResolver(), - thisResolver(), orResolver()); + thisResolver(), orResolver(), trueResolver()); + } + + public EngineBuilder addDefaults() { + return addDefaultSectionHelpers().addDefaultValueResolvers(); } public EngineBuilder addNamespaceResolver(NamespaceResolver resolver) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java index b6d5d615d362d..a36b313bba405 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Expressions.java @@ -42,7 +42,7 @@ public static List splitParts(String value, Predicate separat } boolean stringLiteral = false; boolean separator = false; - boolean infix = false; + char infix = 0; boolean brackets = false; ImmutableList.Builder builder = ImmutableList.builder(); StringBuilder buffer = new StringBuilder(); @@ -68,11 +68,19 @@ public static List splitParts(String value, Predicate separat // Non-separator char if (!stringLiteral) { if (!brackets && c == ' ') { - if (infix) { + if (infix == 1) { + // The second space after the infix method buffer.append(LEFT_BRACKET); + infix++; + } else if (infix == 2) { + // Next infix method + infix = 1; + buffer.append(RIGHT_BRACKET); + builder.add(buffer.toString()); + buffer = new StringBuilder(); } else { - // Separator - infix = true; + // First space - start infix method + infix++; if (buffer.length() > 0) { builder.add(buffer.toString()); buffer = new StringBuilder(); @@ -91,7 +99,7 @@ public static List splitParts(String value, Predicate separat } } } - if (infix) { + if (infix > 0) { buffer.append(RIGHT_BRACKET); } if (buffer.length() > 0) { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java index cb9c92a3ddc25..462ed4951c4d7 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java @@ -32,7 +32,7 @@ public static ValueResolver thisResolver() { return new ValueResolver() { public boolean appliesTo(EvalContext context) { - return ValueResolver.matchClass(context, Object.class) && thisAppliesTo(context); + return context.getBase() != null && THIS.equals(context.getName()); } @Override @@ -43,18 +43,48 @@ public CompletionStage resolve(EvalContext context) { } /** - * {@code foo.or(bar)},{@code foo or true},{@code name ?: 'elvis'} + * Returns the default value if the base object is null or {@link Result#NOT_FOUND}. + * + * {@code foo.or(bar)}, {@code foo or true}, {@code name ?: 'elvis'} */ public static ValueResolver orResolver() { return new ValueResolver() { public boolean appliesTo(EvalContext context) { - return ValueResolver.matchClass(context, Object.class) && orAppliesTo(context); + return context.getParams().size() == 1 + && ("?:".equals(context.getName()) || "or".equals(context.getName()) || ":".equals(context.getName())); } @Override public CompletionStage resolve(EvalContext context) { - return orResolveAsync(context); + if (context.getBase() == null || Results.Result.NOT_FOUND.equals(context.getBase())) { + return context.evaluate(context.getParams().get(0)); + } + return CompletableFuture.completedFuture(context.getBase()); + } + + }; + } + + /** + * Can be used together with {@link #orResolver()} to form a ternary operator. + * + * {@code person.isElvis ? 'elvis' : notElvis} + */ + public static ValueResolver trueResolver() { + return new ValueResolver() { + + public boolean appliesTo(EvalContext context) { + return context.getParams().size() == 1 + && ("?".equals(context.getName())); + } + + @Override + public CompletionStage resolve(EvalContext context) { + if (Boolean.TRUE.equals(context.getBase())) { + return context.evaluate(context.getParams().get(0)); + } + return Results.NOT_FOUND; } }; @@ -132,22 +162,6 @@ private static CompletionStage collectionResolveAsync(EvalContext contex } } - private static boolean thisAppliesTo(EvalContext context) { - return THIS.equals(context.getName()); - } - - private static boolean orAppliesTo(EvalContext context) { - return context.getParams().size() == 1 - && ("?:".equals(context.getName()) || "or".equals(context.getName())); - } - - private static CompletionStage orResolveAsync(EvalContext context) { - if (context.getBase() == null || Results.Result.NOT_FOUND.equals(context.getBase())) { - return context.evaluate(context.getParams().get(0)); - } - return CompletableFuture.completedFuture(context.getBase()); - } - private static Object entryResolve(Entry entry, String name) { switch (name) { case "key": diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ExpressionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ExpressionTest.java index 1d534d01490e0..b13872616bbf5 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ExpressionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ExpressionTest.java @@ -24,6 +24,7 @@ public void testNamespace() throws InterruptedException, ExecutionException { verify("item.name or 'John'", null, ImmutableList.of("item", "name", "or('John')"), null); verify("name.func('John', 1)", null, ImmutableList.of("name", "func('John', 1)"), null); verify("name ?: 'John Bug'", null, ImmutableList.of("name", "?:('John Bug')"), null); + verify("name ? 'John' : 'Bug'", null, ImmutableList.of("name", "?('John')", ":('Bug')"), null); verify("name.func(data:foo)", null, ImmutableList.of("name", "func(data:foo)"), null); } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java index ab80d4faf7edc..aa18925e48560 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java @@ -73,9 +73,23 @@ public void testOrElseResolver() { .build(); Map data = new HashMap<>(); data.put("surname", "Bug"); + data.put("foo", null); assertEquals("John Bug", engine.parse("{name.or('John')} {surname.or('John')}").render(data)); assertEquals("John Bug", engine.parse("{name ?: 'John'} {surname or 'John'}").render(data)); assertEquals("John Bug", engine.parse("{name ?: \"John Bug\"}").render(data)); + assertEquals("Is null", engine.parse("{foo ?: 'Is null'}").render(data)); + } + + @Test + public void testTernaryOperator() { + Engine engine = Engine.builder() + .addValueResolvers(ValueResolvers.mapResolver(), ValueResolvers.trueResolver(), + ValueResolvers.orResolver()) + .build(); + + Template template = engine + .parse("{name ? 'Name true' : 'Name false'}. {surname ? 'Surname true' : foo}."); + assertEquals("Name true. baz.", template.data("name", true).data("foo", "baz").render()); } @Test diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index eb8f3048f5003..4f2903b8bbf1f 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -5,6 +5,7 @@ import io.quarkus.gizmo.AssignableResultHandle; import io.quarkus.gizmo.BranchResult; import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldDescriptor; @@ -12,15 +13,20 @@ import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.qute.EvalContext; import io.quarkus.qute.TemplateData; import io.quarkus.qute.ValueResolver; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -146,7 +152,6 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas ResultHandle evalContext = resolve.getMethodParam(0); ResultHandle base = resolve.invokeInterfaceMethod(Descriptors.GET_BASE, evalContext); - ResultHandle name = resolve.invokeInterfaceMethod(Descriptors.GET_NAME, evalContext); ResultHandle params = resolve.invokeInterfaceMethod(Descriptors.GET_PARAMS, evalContext); ResultHandle paramsCount = resolve.invokeInterfaceMethod(Descriptors.COLLECTION_SIZE, params); @@ -176,127 +181,338 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas } } - // Methods - for (MethodInfo method : clazz.methods().stream().filter(filter::test).collect(Collectors.toList())) { - LOGGER.debugf("Method added %s", method); - List methodParams = method.parameters(); - - BytecodeCreator matchScope = resolve.createScope(); - // Match name - BytecodeCreator notMatched = matchScope.ifNonZero(matchScope.invokeVirtualMethod(Descriptors.EQUALS, - matchScope.load(method.name()), - name)) - .falseBranch(); - // Match the property name for getters, ie. "foo" for "getFoo" - if (methodParams.size() == 0 && isGetterName(method.name())) { - notMatched.ifNonZero(notMatched.invokeVirtualMethod(Descriptors.EQUALS, - notMatched.load(getPropertyName(method.name())), - name)).falseBranch().breakScope(matchScope); - } else { - notMatched.breakScope(matchScope); - } - // Match number of params - matchScope.ifNonZero(matchScope.invokeStaticMethod(Descriptors.INTEGER_COMPARE, - matchScope.load(methodParams.size()), paramsCount)).trueBranch().breakScope(matchScope); - - // Invoke the method - ResultHandle ret; - boolean hasCompletionStage = !skipMemberType(method.returnType()) - && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); - if (method.parameters().size() > 0) { - // We need to evaluate the params - ret = matchScope - .newInstance(MethodDescriptor.ofConstructor(CompletableFuture.class)); - - ResultHandle resultsArray = matchScope.newArray(CompletableFuture.class, - matchScope.load(methodParams.size())); - for (int i = 0; i < methodParams.size(); i++) { - ResultHandle evalResult = matchScope.invokeInterfaceMethod( - Descriptors.EVALUATE, evalContext, - matchScope.invokeInterfaceMethod(Descriptors.LIST_GET, params, - matchScope.load(i))); - matchScope.writeArrayValue(resultsArray, i, - matchScope.invokeInterfaceMethod(Descriptors.CF_TO_COMPLETABLE_FUTURE, evalResult)); - } - ResultHandle allOf = matchScope.invokeStaticMethod(Descriptors.COMPLETABLE_FUTURE_ALL_OF, - resultsArray); + List methods = clazz.methods().stream().filter(filter::test).collect(Collectors.toList()); + if (!methods.isEmpty()) { - FunctionCreator whenCompleteFun = matchScope.createFunction(BiConsumer.class); - matchScope.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, allOf, whenCompleteFun.getInstance()); + Map> matches = new HashMap<>(); - BytecodeCreator whenComplete = whenCompleteFun.getBytecode(); + for (MethodInfo method : methods) { - // TODO workaround for https://github.com/quarkusio/gizmo/issues/6 - AssignableResultHandle whenBase = whenComplete.createVariable(Object.class); - whenComplete.assign(whenBase, base); - AssignableResultHandle whenRet = whenComplete.createVariable(CompletableFuture.class); - whenComplete.assign(whenRet, ret); - AssignableResultHandle whenResults = whenComplete.createVariable(CompletableFuture[].class); - whenComplete.assign(whenResults, resultsArray); + List methodParams = method.parameters(); + if (methodParams.isEmpty()) { - BranchResult throwableIsNull = whenComplete.ifNull(whenComplete.getMethodParam(1)); + LOGGER.debugf("Method added %s", method); + BytecodeCreator matchScope = createMatchScope(resolve, method.name(), methodParams.size(), name, params, + paramsCount); - // complete - BytecodeCreator success = throwableIsNull.trueBranch(); + // Invoke the method - no params + ResultHandle ret; + boolean hasCompletionStage = !skipMemberType(method.returnType()) + && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); + + ResultHandle invokeRet; + if (Modifier.isInterface(clazz.flags())) { + invokeRet = matchScope.invokeInterfaceMethod(MethodDescriptor.of(method), base); + } else { + invokeRet = matchScope.invokeVirtualMethod(MethodDescriptor.of(method), base); + } + if (hasCompletionStage) { + ret = invokeRet; + } else { + ret = matchScope.invokeStaticMethod(Descriptors.COMPLETED_FUTURE, invokeRet); + } + matchScope.returnValue(ret); - ResultHandle[] paramsHandle = new ResultHandle[methodParams.size()]; - for (int i = 0; i < methodParams.size(); i++) { - ResultHandle paramResult = success.readArrayValue(whenResults, i); - paramsHandle[i] = success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_GET, paramResult); - } - ResultHandle invokeRet; - if (Modifier.isInterface(clazz.flags())) { - invokeRet = success.invokeInterfaceMethod(MethodDescriptor.of(method), whenBase, paramsHandle); } else { - invokeRet = success.invokeVirtualMethod(MethodDescriptor.of(method), whenBase, paramsHandle); + // Collect methods with params + Match match = new Match(method.name(), method.parameters().size()); + List infos = matches.get(match); + if (infos == null) { + infos = new ArrayList<>(); + matches.put(match, infos); + } + infos.add(method); } + } + + for (Entry> entry : matches.entrySet()) { + + if (entry.getValue().size() == 1) { + // Single method matches the name and number of params + MethodInfo method = entry.getValue().get(0); + List methodParams = method.parameters(); + + LOGGER.debugf("Method added %s", method); + + BytecodeCreator matchScope = createMatchScope(resolve, method.name(), methodParams.size(), name, params, + paramsCount); + + // Invoke the method + ResultHandle ret; + boolean hasCompletionStage = !skipMemberType(method.returnType()) + && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); + // Evaluate the params first + ret = matchScope + .newInstance(MethodDescriptor.ofConstructor(CompletableFuture.class)); + + ResultHandle resultsArray = matchScope.newArray(CompletableFuture.class, + matchScope.load(methodParams.size())); + for (int i = 0; i < methodParams.size(); i++) { + ResultHandle evalResult = matchScope.invokeInterfaceMethod( + Descriptors.EVALUATE, evalContext, + matchScope.invokeInterfaceMethod(Descriptors.LIST_GET, params, + matchScope.load(i))); + matchScope.writeArrayValue(resultsArray, i, + matchScope.invokeInterfaceMethod(Descriptors.CF_TO_COMPLETABLE_FUTURE, evalResult)); + } + ResultHandle allOf = matchScope.invokeStaticMethod(Descriptors.COMPLETABLE_FUTURE_ALL_OF, + resultsArray); + + FunctionCreator whenCompleteFun = matchScope.createFunction(BiConsumer.class); + matchScope.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, allOf, whenCompleteFun.getInstance()); - if (hasCompletionStage) { - FunctionCreator invokeWhenCompleteFun = success.createFunction(BiConsumer.class); - success.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, invokeRet, invokeWhenCompleteFun.getInstance()); - BytecodeCreator invokeWhenComplete = invokeWhenCompleteFun.getBytecode(); + BytecodeCreator whenComplete = whenCompleteFun.getBytecode(); // TODO workaround for https://github.com/quarkusio/gizmo/issues/6 - AssignableResultHandle invokeWhenRet = invokeWhenComplete.createVariable(CompletableFuture.class); - invokeWhenComplete.assign(invokeWhenRet, whenRet); - - BranchResult invokeThrowableIsNull = invokeWhenComplete.ifNull(invokeWhenComplete.getMethodParam(1)); - BytecodeCreator invokeSuccess = invokeThrowableIsNull.trueBranch(); - invokeSuccess.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, invokeWhenRet, - invokeWhenComplete.getMethodParam(0)); - BytecodeCreator invokeFailure = invokeThrowableIsNull.falseBranch(); - invokeFailure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, invokeWhenRet, - invokeWhenComplete.getMethodParam(1)); - invokeWhenComplete.returnValue(null); - } else { - success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet); - } + AssignableResultHandle whenBase = whenComplete.createVariable(Object.class); + whenComplete.assign(whenBase, base); + AssignableResultHandle whenRet = whenComplete.createVariable(CompletableFuture.class); + whenComplete.assign(whenRet, ret); + AssignableResultHandle whenResults = whenComplete.createVariable(CompletableFuture[].class); + whenComplete.assign(whenResults, resultsArray); + + BranchResult throwableIsNull = whenComplete.ifNull(whenComplete.getMethodParam(1)); + + // complete + BytecodeCreator success = throwableIsNull.trueBranch(); + + ResultHandle[] paramsHandle = new ResultHandle[methodParams.size()]; + for (int i = 0; i < methodParams.size(); i++) { + ResultHandle paramResult = success.readArrayValue(whenResults, i); + paramsHandle[i] = success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_GET, paramResult); + } - // completeExceptionally - BytecodeCreator failure = throwableIsNull.falseBranch(); - failure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, - whenComplete.getMethodParam(1)); - whenComplete.returnValue(null); + AssignableResultHandle invokeRet = success.createVariable(Object.class); + // try + TryBlock tryCatch = success.tryBlock(); + // catch (Throwable e) + CatchBlockCreator exception = tryCatch.addCatch(Throwable.class); + // CompletableFuture.completeExceptionally(Throwable) + exception.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + exception.getCaughtException()); + + if (Modifier.isInterface(clazz.flags())) { + tryCatch.assign(invokeRet, + tryCatch.invokeInterfaceMethod(MethodDescriptor.of(method), whenBase, paramsHandle)); + } else { + tryCatch.assign(invokeRet, + tryCatch.invokeVirtualMethod(MethodDescriptor.of(method), whenBase, paramsHandle)); + } + + if (hasCompletionStage) { + FunctionCreator invokeWhenCompleteFun = tryCatch.createFunction(BiConsumer.class); + tryCatch.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, invokeRet, + invokeWhenCompleteFun.getInstance()); + BytecodeCreator invokeWhenComplete = invokeWhenCompleteFun.getBytecode(); + + // TODO workaround for https://github.com/quarkusio/gizmo/issues/6 + AssignableResultHandle invokeWhenRet = invokeWhenComplete.createVariable(CompletableFuture.class); + invokeWhenComplete.assign(invokeWhenRet, whenRet); + + BranchResult invokeThrowableIsNull = invokeWhenComplete.ifNull(invokeWhenComplete.getMethodParam(1)); + BytecodeCreator invokeSuccess = invokeThrowableIsNull.trueBranch(); + invokeSuccess.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, invokeWhenRet, + invokeWhenComplete.getMethodParam(0)); + + BytecodeCreator invokeFailure = invokeThrowableIsNull.falseBranch(); + invokeFailure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, invokeWhenRet, + invokeWhenComplete.getMethodParam(1)); + invokeWhenComplete.returnValue(null); + } else { + tryCatch.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet); + } + // CompletableFuture.completeExceptionally(Throwable) + BytecodeCreator failure = throwableIsNull.falseBranch(); + failure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + whenComplete.getMethodParam(1)); + whenComplete.returnValue(null); + + matchScope.returnValue(ret); - } else { - // No params - ResultHandle invokeRet; - if (Modifier.isInterface(clazz.flags())) { - invokeRet = matchScope.invokeInterfaceMethod(MethodDescriptor.of(method), base); - } else { - invokeRet = matchScope.invokeVirtualMethod(MethodDescriptor.of(method), base); - } - if (hasCompletionStage) { - ret = invokeRet; } else { - ret = matchScope.invokeStaticMethod(Descriptors.COMPLETED_FUTURE, invokeRet); + // Multiple methods match the name and number of params + LOGGER.debugf("Methods added %s", entry.getValue()); + BytecodeCreator matchScope = createMatchScope(resolve, entry.getKey().name, entry.getKey().paramsCount, + name, params, + paramsCount); + + // Evaluate the params first + ResultHandle ret = matchScope + .newInstance(MethodDescriptor.ofConstructor(CompletableFuture.class)); + + ResultHandle resultsArray = matchScope.newArray(CompletableFuture.class, + matchScope.load(entry.getKey().paramsCount)); + for (int i = 0; i < entry.getKey().paramsCount; i++) { + ResultHandle evalResult = matchScope.invokeInterfaceMethod( + Descriptors.EVALUATE, evalContext, + matchScope.invokeInterfaceMethod(Descriptors.LIST_GET, params, + matchScope.load(i))); + matchScope.writeArrayValue(resultsArray, i, + matchScope.invokeInterfaceMethod(Descriptors.CF_TO_COMPLETABLE_FUTURE, evalResult)); + } + ResultHandle allOf = matchScope.invokeStaticMethod(Descriptors.COMPLETABLE_FUTURE_ALL_OF, + resultsArray); + + FunctionCreator whenCompleteFun = matchScope.createFunction(BiConsumer.class); + matchScope.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, allOf, whenCompleteFun.getInstance()); + + BytecodeCreator whenComplete = whenCompleteFun.getBytecode(); + + // TODO workaround for https://github.com/quarkusio/gizmo/issues/6 + AssignableResultHandle whenBase = whenComplete.createVariable(Object.class); + whenComplete.assign(whenBase, base); + AssignableResultHandle whenRet = whenComplete.createVariable(CompletableFuture.class); + whenComplete.assign(whenRet, ret); + AssignableResultHandle whenResults = whenComplete.createVariable(CompletableFuture[].class); + whenComplete.assign(whenResults, resultsArray); + + BranchResult throwableIsNull = whenComplete.ifNull(whenComplete.getMethodParam(1)); + // complete + BytecodeCreator success = throwableIsNull.trueBranch(); + + ResultHandle[] paramsHandle = new ResultHandle[entry.getKey().paramsCount]; + for (int i = 0; i < entry.getKey().paramsCount; i++) { + ResultHandle paramResult = success.readArrayValue(whenResults, i); + paramsHandle[i] = success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_GET, paramResult); + } + + ResultHandle paramClasses = success.newArray(Class.class, success.load(entry.getKey().paramsCount)); + for (int i = 0; i < entry.getKey().paramsCount; i++) { + success.writeArrayValue(paramClasses, i, success.invokeVirtualMethod(Descriptors.GET_CLASS, + paramsHandle[i])); + } + + for (MethodInfo method : entry.getValue()) { + // Try to match parameter types + BytecodeCreator paramMatchScope = success.createScope(); + int idx = 0; + for (Type paramType : method.parameters()) { + ResultHandle paramHandleClass = paramMatchScope.readArrayValue(paramClasses, idx++); + ResultHandle testClass = loadParamType(paramMatchScope, paramType); + ResultHandle baseClassTest = paramMatchScope.invokeVirtualMethod(Descriptors.IS_ASSIGNABLE_FROM, + testClass, + paramHandleClass); + paramMatchScope.ifNonZero(baseClassTest).falseBranch().breakScope(paramMatchScope); + } + boolean hasCompletionStage = !skipMemberType(method.returnType()) + && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); + + AssignableResultHandle invokeRet = paramMatchScope.createVariable(Object.class); + // try + TryBlock tryCatch = paramMatchScope.tryBlock(); + // catch (Throwable e) + CatchBlockCreator exception = tryCatch.addCatch(Throwable.class); + // CompletableFuture.completeExceptionally(Throwable) + exception.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + exception.getCaughtException()); + + if (Modifier.isInterface(clazz.flags())) { + tryCatch.assign(invokeRet, + tryCatch.invokeInterfaceMethod(MethodDescriptor.of(method), whenBase, paramsHandle)); + } else { + tryCatch.assign(invokeRet, + tryCatch.invokeVirtualMethod(MethodDescriptor.of(method), whenBase, paramsHandle)); + } + + if (hasCompletionStage) { + FunctionCreator invokeWhenCompleteFun = tryCatch.createFunction(BiConsumer.class); + tryCatch.invokeInterfaceMethod(Descriptors.CF_WHEN_COMPLETE, invokeRet, + invokeWhenCompleteFun.getInstance()); + BytecodeCreator invokeWhenComplete = invokeWhenCompleteFun.getBytecode(); + + // TODO workaround for https://github.com/quarkusio/gizmo/issues/6 + AssignableResultHandle invokeWhenRet = invokeWhenComplete.createVariable(CompletableFuture.class); + invokeWhenComplete.assign(invokeWhenRet, whenRet); + + BranchResult invokeThrowableIsNull = invokeWhenComplete + .ifNull(invokeWhenComplete.getMethodParam(1)); + BytecodeCreator invokeSuccess = invokeThrowableIsNull.trueBranch(); + invokeSuccess.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, invokeWhenRet, + invokeWhenComplete.getMethodParam(0)); + BytecodeCreator invokeFailure = invokeThrowableIsNull.falseBranch(); + invokeFailure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, + invokeWhenRet, + invokeWhenComplete.getMethodParam(1)); + invokeWhenComplete.returnValue(null); + } else { + tryCatch.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet); + } + } + + // CompletableFuture.completeExceptionally(Throwable) + BytecodeCreator failure = throwableIsNull.falseBranch(); + failure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + whenComplete.getMethodParam(1)); + + // No method matches + ResultHandle exc = whenComplete.newInstance( + MethodDescriptor.ofConstructor(IllegalStateException.class, String.class), + whenComplete.load("No method matches")); + whenComplete.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + exc); + whenComplete.returnValue(null); + + matchScope.returnValue(ret); } + } - matchScope.returnValue(ret); + } + resolve.returnValue(resolve.readStaticField(Descriptors.RESULT_NOT_FOUND)); } + private ResultHandle loadParamType(BytecodeCreator creator, Type paramType) { + if (org.jboss.jandex.Type.Kind.PRIMITIVE.equals(paramType.kind())) { + switch (paramType.asPrimitiveType().primitive()) { + case INT: + return creator.loadClass(Integer.class); + case LONG: + return creator.loadClass(Long.class); + case BOOLEAN: + return creator.loadClass(Boolean.class); + case BYTE: + return creator.loadClass(Byte.class); + case CHAR: + return creator.loadClass(Character.class); + case DOUBLE: + return creator.loadClass(Double.class); + case FLOAT: + return creator.loadClass(Float.class); + case SHORT: + return creator.loadClass(Short.class); + default: + throw new IllegalArgumentException("Unsupported primitive type: " + paramType); + } + } + // TODO: we should probably use the TCCL to load the param type + return creator.loadClass(paramType.name().toString()); + } + + private BytecodeCreator createMatchScope(BytecodeCreator bytecodeCreator, String methodName, int methodParams, + ResultHandle name, ResultHandle params, ResultHandle paramsCount) { + + BytecodeCreator matchScope = bytecodeCreator.createScope(); + // Match name + BytecodeCreator notMatched = matchScope.ifNonZero(matchScope.invokeVirtualMethod(Descriptors.EQUALS, + matchScope.load(methodName), + name)) + .falseBranch(); + // Match the property name for getters, ie. "foo" for "getFoo" + if (methodParams == 0 && isGetterName(methodName)) { + notMatched.ifNonZero(notMatched.invokeVirtualMethod(Descriptors.EQUALS, + notMatched.load(getPropertyName(methodName)), + name)).falseBranch().breakScope(matchScope); + } else { + notMatched.breakScope(matchScope); + } + // Match number of params + matchScope.ifNonZero(matchScope.invokeStaticMethod(Descriptors.INTEGER_COMPARE, + matchScope.load(methodParams), paramsCount)).trueBranch().breakScope(matchScope); + + return matchScope; + } + private void implementAppliesTo(ClassCreator valueResolver, ClassInfo clazz) { MethodCreator appliesTo = valueResolver.getMethodCreator("appliesTo", boolean.class, EvalContext.class) .setModifiers(ACC_PUBLIC); @@ -524,4 +740,36 @@ static boolean hasCompletionStageInTypeClosure(ClassInfo classInfo, return false; } + private static class Match { + + final String name; + final int paramsCount; + + public Match(String name, int paramsCount) { + this.name = name; + this.paramsCount = paramsCount; + } + + @Override + public int hashCode() { + return Objects.hash(name, paramsCount); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Match other = (Match) obj; + return Objects.equals(name, other.name) && paramsCount == other.paramsCount; + } + + } + } From 032cd304163941639706bc02aaaa339dac8f6cdd Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Wed, 4 Dec 2019 11:18:26 -0500 Subject: [PATCH 188/602] added some documentation about jax-rs applications and @OpenAPIDefinition --- docs/src/main/asciidoc/openapi-swaggerui.adoc | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/openapi-swaggerui.adoc b/docs/src/main/asciidoc/openapi-swaggerui.adoc index 9189b1e68d22b..73cd6a70d5c08 100644 --- a/docs/src/main/asciidoc/openapi-swaggerui.adoc +++ b/docs/src/main/asciidoc/openapi-swaggerui.adoc @@ -250,6 +250,44 @@ quarkus.smallrye-openapi.path=/swagger Hit `CTRL+C` to stop the application. +== Providing Application Level OpenAPI Annotations + +There are some MicroProfile OpenAPI annotations which describe global API information, such as the following: + +* API Title +* API Description +* Version +* Contact Information +* License + +All of this information (and more) can be included in your Java code by using appropriate OpenAPI annotations +on a JAX-RS `Application` class. Because a JAX-RS `Application` class is not required in Quarkus, you will +likely have to create one. It can simply be an empty class that extends `javax.ws.rs.core.Application`. This +empty class can then be annotated with various OpenAPI annotations such as `@OpenAPIDefinition`. For example: + +[source, java] +---- +@OpenAPIDefinition( + tags = { + @Tag(name="widget", description="Widget operations."), + @Tag(name="gasket", description="Operations related to gaskets") + }, + info = @Info( + title="Example API", + version = "1.0.1", + contact = @Contact( + name = "Example API Support", + url = "http://exampleurl.com/contact", + email = "techsupport@example.com"), + license = @License( + name = "Apache 2.0", + url = "http://www.apache.org/licenses/LICENSE-2.0.html")) +) +public class ExampleApiApplication extends Application { +} +---- + + == Loading OpenAPI Schema From Static Files Instead of dynamically creating OpenAPI schemas from annotation scanning, Quarkus also supports serving static OpenAPI documents. @@ -398,4 +436,3 @@ include::{generated-dir}/config/quarkus-smallrye-openapi.adoc[opts=optional, lev === Swagger UI include::{generated-dir}/config/quarkus-swaggerui.adoc[opts=optional, leveloffset=+1] - From cd2f068aa93808f5f3110ddd03eeb2421ecf5f91 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 29 Nov 2019 12:09:27 +1100 Subject: [PATCH 189/602] Fix exception mapper when root path is set --- .../deployment/ResteasyBuiltinsProcessor.java | 7 +++- ...tFoundExceptionMapperHttpRootTestCase.java | 37 +++++++++++++++++++ ...onMapperHttpRootTrailingSlashTestCase.java | 37 +++++++++++++++++++ .../test/NotFoundExceptionMapperTestCase.java | 2 +- .../runtime/ExceptionMapperRecorder.java | 3 ++ .../runtime/NotFoundExceptionMapper.java | 35 ++++++++++++++++-- 6 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTestCase.java create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTrailingSlashTestCase.java diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java index c396b671f3b3d..f5ea622ab6646 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyBuiltinsProcessor.java @@ -66,9 +66,12 @@ void setUpSecurityExceptionMappers(BuildProducer providers.produce(new ResteasyJaxrsProviderBuildItem(ForbiddenExceptionMapper.class.getName())); } + @Record(STATIC_INIT) @BuildStep(onlyIf = IsDevelopment.class) - void setupExceptionMapper(BuildProducer providers) { + void setupExceptionMapper(BuildProducer providers, HttpRootPathBuildItem httpRoot, + ExceptionMapperRecorder recorder) { providers.produce(new ResteasyJaxrsProviderBuildItem(NotFoundExceptionMapper.class.getName())); + recorder.setHttpRoot(httpRoot.getRootPath()); } @Record(STATIC_INIT) @@ -92,7 +95,7 @@ void addAdditionalEndpointsExceptionMapper(List endpoints = displayableEndpoints .stream() - .map(displayableAdditionalBuildItem -> httpRoot.adjustPath(displayableAdditionalBuildItem.getEndpoint()) + .map(displayableAdditionalBuildItem -> displayableAdditionalBuildItem.getEndpoint() .substring(1)) .sorted() .collect(Collectors.toList()); diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTestCase.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTestCase.java new file mode 100644 index 0000000000000..112fa7d182b54 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTestCase.java @@ -0,0 +1,37 @@ +package io.quarkus.resteasy.test; + +import static org.hamcrest.CoreMatchers.containsString; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +public class NotFoundExceptionMapperHttpRootTestCase { + private static final String META_INF_RESOURCES = "META-INF/resources/"; + + @RegisterExtension + static QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(RootResource.class) + .addAsResource(new StringAsset("quarkus.http.root-path=/abc"), "application.properties") + .addAsResource(new StringAsset("index content"), META_INF_RESOURCES + "index.html")); + + @Test + public void testHtmlResourceNotFound() { + // test the exception mapper provided in dev mode, if no accept, will just return a plain 404 + RestAssured.given().accept(ContentType.HTML) + .when().get("/abc/not_found") + .then() + .statusCode(404) + .contentType(ContentType.HTML) + .body(containsString("\"/abc/index.html")) // check that index.html is displayed + .body(Matchers.containsString("

404 - Resource Not Found

")); + } +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTrailingSlashTestCase.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTrailingSlashTestCase.java new file mode 100644 index 0000000000000..3fe1263e2381b --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperHttpRootTrailingSlashTestCase.java @@ -0,0 +1,37 @@ +package io.quarkus.resteasy.test; + +import static org.hamcrest.CoreMatchers.containsString; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +public class NotFoundExceptionMapperHttpRootTrailingSlashTestCase { + private static final String META_INF_RESOURCES = "META-INF/resources/"; + + @RegisterExtension + static QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(RootResource.class) + .addAsResource(new StringAsset("quarkus.http.root-path=/abc/"), "application.properties") + .addAsResource(new StringAsset("index content"), META_INF_RESOURCES + "index.html")); + + @Test + public void testHtmlResourceNotFound() { + // test the exception mapper provided in dev mode, if no accept, will just return a plain 404 + RestAssured.given().accept(ContentType.HTML) + .when().get("/abc/not_found") + .then() + .statusCode(404) + .contentType(ContentType.HTML) + .body(containsString("\"/abc/index.html")) // check that index.html is displayed + .body(Matchers.containsString("

404 - Resource Not Found

")); + } +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java index 2f57f703e9b53..955d8235a1a06 100644 --- a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java @@ -40,7 +40,7 @@ public void testHtmlResourceNotFound() { .then() .statusCode(404) .contentType(ContentType.HTML) - .body(containsString("index.html")) // check that index.html is displayed + .body(containsString("\"/index.html")) // check that index.html is displayed .body(Matchers.containsString("

404 - Resource Not Found

")); } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ExceptionMapperRecorder.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ExceptionMapperRecorder.java index 191a9617e4c36..2a569b2eb67fb 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ExceptionMapperRecorder.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ExceptionMapperRecorder.java @@ -32,4 +32,7 @@ public void nonJaxRsClassNameToMethodPaths(Map no NotFoundExceptionMapper.nonJaxRsClassNameToMethodPaths(nonJaxRsClassNameToMethodPaths); } + public void setHttpRoot(String rootPath) { + NotFoundExceptionMapper.setHttpRoot(rootPath); + } } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java index f71dd40b1ed5c..bb9b4a4928789 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/NotFoundExceptionMapper.java @@ -43,6 +43,7 @@ public class NotFoundExceptionMapper implements ExceptionMapper VARIANTS = Arrays.asList(JSON_VARIANT, HTML_VARIANT); + private volatile static String httpRoot = ""; private volatile static List servletMappings = Collections.EMPTY_LIST; private volatile static List staticResources = Collections.EMPTY_LIST; private volatile static List additionalEndpoints = Collections.EMPTY_LIST; @@ -54,6 +55,10 @@ public class NotFoundExceptionMapper implements ExceptionMapper descriptions) { TemplateHtmlBuilder sb = new TemplateHtmlBuilder("404 - Resource Not Found", "", "Resources overview"); sb.resourcesStart("REST resources"); for (ResourceDescription resource : descriptions) { - sb.resourcePath(resource.basePath); + sb.resourcePath(adjustRoot(resource.basePath)); for (MethodDescription method : resource.calls) { sb.method(method.method, method.fullPath); if (method.consumes != null) { @@ -229,7 +234,7 @@ private Response respond(List descriptions) { if (!servletMappings.isEmpty()) { sb.resourcesStart("Servlet mappings"); for (String servletMapping : servletMappings) { - sb.servletMapping(servletMapping); + sb.servletMapping(adjustRoot(servletMapping)); } sb.resourcesEnd(); } @@ -237,7 +242,7 @@ private Response respond(List descriptions) { if (!staticResources.isEmpty()) { sb.resourcesStart("Static resources"); for (String staticResource : staticResources) { - sb.staticResourcePath(staticResource); + sb.staticResourcePath(adjustRoot(staticResource)); } sb.resourcesEnd(); } @@ -245,7 +250,7 @@ private Response respond(List descriptions) { if (!additionalEndpoints.isEmpty()) { sb.resourcesStart("Additional endpoints"); for (String additionalEndpoint : additionalEndpoints) { - sb.staticResourcePath(additionalEndpoint); + sb.staticResourcePath(adjustRoot(additionalEndpoint)); } sb.resourcesEnd(); } @@ -255,6 +260,28 @@ private Response respond(List descriptions) { return Response.status(Status.NOT_FOUND).build(); } + private String adjustRoot(String basePath) { + //httpRoot can optionally end with a slash + //also some templates want the returned path to start with a / and some don't + //to make this work we check if the basePath starts with a / or not, and make sure we + //the return value follows the same pattern + + if (httpRoot.equals("/")) { + //leave it alone + return basePath; + } + if (basePath.startsWith("/")) { + if (!httpRoot.endsWith("/")) { + return httpRoot + basePath; + } + return httpRoot.substring(0, httpRoot.length() - 1) + basePath; + } + if (httpRoot.endsWith("/")) { + return httpRoot.substring(1) + basePath; + } + return httpRoot.substring(1) + "/" + basePath; + } + private static Variant selectVariant(HttpHeaders headers) { ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation(); negotiation.setAcceptHeaders(headers.getRequestHeaders().get(ACCEPT)); From 2234377ff5372c32c985cc37c657ada774f81c79 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 2 Dec 2019 14:31:51 +0200 Subject: [PATCH 190/602] Add documentation for Spring Security's @PreAuthorize --- docs/src/main/asciidoc/spring-security.adoc | 146 +++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-security.adoc b/docs/src/main/asciidoc/spring-security.adoc index 1670fe4887f86..07e5fdc1d5718 100644 --- a/docs/src/main/asciidoc/spring-security.adoc +++ b/docs/src/main/asciidoc/spring-security.adoc @@ -214,7 +214,7 @@ You can of course create a native image using the instructions of the link:build == Supported Spring Security functionalities -Quarkus currently only supports a small subset of the functionalities that Spring Security provides and more features will be added in the future. More specifically, Quarkus supports the security related features of role-based authorization semantics +Quarkus currently only supports a subset of the functionalities that Spring Security provides with more features being planned. More specifically, Quarkus supports the security related features of role-based authorization semantics (think of `@Secured` instead of `@RolesAllowed`). === Annotations @@ -228,8 +228,152 @@ The table below summarizes the supported annotations: |@Secured | +|@PreAuthorize +|See next section for more details + |=== +==== @PreAuthorize + +Quarkus provides support for some of the most used features of Spring Security's `@PreAuthorize` annotation. +The expressions that are supported are the following: + +* hasRole + +To test if the current user has a specific role, the `hasRole` expression can be used inside `@PreAuthorize`. +Some examples are: `@PreAuthorize("hasRole('admin')")`, `@PreAuthorize("hasRole(@roles.USER)")` where the `roles` is a bean that could be defined like so: + +[source, java] +---- +import org.springframework.stereotype.Component; + +@Component +public class Roles { + + public final String ADMIN = "admin"; + public final String USER = "user"; +} + +---- + +* hasAnyRole + +In the same fashion as `hasRole`, users can use `hasAnyRole` to check if the logged in user has any of the specified roles. Some examples are: +`@PreAuthorize("hasAnyRole('admin')")`, `@PreAuthorize("hasAnyRole(@roles.USER, 'view')")` + +permitAll:: Adding `@PreAuthorize("permitAll()")` to a method will ensure that that method is accessible by any user (including anonymous users). Adding it to a class will ensure that all public methods ++of the class that are not annotated with any other Spring Security annotation will be accessible. + +denyAll:: Adding `@PreAuthorize("denyAll()")` to a method will ensure that that method is not accessible by any user. Adding it to a class will ensure that all public methods ++of the class that are not annotated with any other Spring Security annotation will not be accessible to any user. + +isAnonymous:: When annotating a bean method with `@PreAuthorize("isAnonymous()")` the method will only be accessible if the current user is anonymous - i.e. a non logged in user. + +isAuthenticated:: When annotating a bean method with `@PreAuthorize("isAuthenticated()")` the method will only be accessible if the current user is a logged in user. Essentially the ++method is only unavailable for anonymous users. + +#paramName == authentication.principal.username:: This syntax allows users to check if a parameter (or a field of the parameter) of the secured method is equal to the logged in username. ++Examples of this use case are: + +[source,java] +---- +public class Person { + + private final String name; + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} + +@Component +public class MyComponent { + + @PreAuthorize("#username == authentication.principal.username") <1> + public void doSomething(String username, String other){ + + } + + @PreAuthorize("#person.name == authentication.principal.username") <2> + public void doSomethingElse(Person person){ + + } +} +---- + +<1> `doSomething` can be executed if the current logged in user is the same as the `username` method parameter +<2> `doSomethingElse` can be executed if the current logged in user is the same as the `name` field of `person` method parameter + +*Tip* the use of `authentication.` is optional, so using `principal.username` has the same result. + +#paramName != authentication.principal.username:: This is similar to the previous expression with the difference being that the method parameter must different than the logged in username. + +@beanName.method():: This syntax allows developers to specify that the execution of method of a specific bean will determine if the current user can access the secured method. ++The syntax is best explained with an example. ++Let's assume that a `MyComponent` bean has been created like so: + +[source,java] +---- +@Component +public class MyComponent { + + @PreAuthorize("@personChecker.check(#person, authentication.principal.username)") + public void doSomething(Person person){ + + } +} +---- + +The `doSomething` method has been annotated with `@PreAuthorize` using an expression that indicates that method `check` of a bean named `personChecker` needs +to be invoked to determine whether the current user is authorized to invoke the `doSomething` method. + +An example of the `PersonChecker` could be: + +[source,java] +---- +@Component +public class PersonChecker { + + @Override + public boolean check(Person person, String username) { + return person.getName().equals(username); + } +} +---- + +Note that for the `check` method the parameter types must match what is specified in `@PreAuthorize` and that the return type must be a `boolean`. + +===== Combining expressions + +The `@PreAuthorize` annotations allows for the combination of expressions using logical `AND` / `OR`. Currently there is a limitation where only a single +logical operation can be used (meaning mixing `AND` and `OR` isn't allowed). + +Some examples of allowed expressions are: + +[source,java] +---- + + @PreAuthorize("hasAnyRole('user', 'admin') AND #user == principal.username") + public void allowedForUser(String user) { + + } + + @PreAuthorize("hasRole('user') OR hasRole('admin')") + public void allowedForUserOrAdmin() { + + } + + @PreAuthorize("hasAnyRole('view1', 'view2') OR isAnonymous() OR hasRole('test')") + public void allowedForAdminOrAnonymous() { + + } +---- + +Also to be noted that currently parenthesis are not supported and also each of expressions are evaluated from left to right when needed. == Important Technical Note From 4347ea600a81fadfc19573788dd7fc694da16a89 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 3 Dec 2019 23:47:05 +0100 Subject: [PATCH 191/602] Fix a few formatting issues and add a missing word --- docs/src/main/asciidoc/spring-security.adoc | 58 +++++++++++---------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/src/main/asciidoc/spring-security.adoc b/docs/src/main/asciidoc/spring-security.adoc index 07e5fdc1d5718..457a13feddb6b 100644 --- a/docs/src/main/asciidoc/spring-security.adoc +++ b/docs/src/main/asciidoc/spring-security.adoc @@ -190,17 +190,19 @@ public class GreetingControllerTest { == Test the changes -- Access allowed +Access allowed:: Open your browser again to http://localhost:8080/greeting and introduce `scott` and `jb0ss` in the dialog displayed. - ++ The word `hello` should be displayed. -- Access forbidden +Access forbidden:: Open your browser again to http://localhost:8080/greeting and let empty the dialog displayed. - ++ The result should be: ++ +[source] ---- Access to localhost was denied You don't have authorization to view this page. @@ -238,11 +240,12 @@ The table below summarizes the supported annotations: Quarkus provides support for some of the most used features of Spring Security's `@PreAuthorize` annotation. The expressions that are supported are the following: -* hasRole - +hasRole:: ++ To test if the current user has a specific role, the `hasRole` expression can be used inside `@PreAuthorize`. ++ Some examples are: `@PreAuthorize("hasRole('admin')")`, `@PreAuthorize("hasRole(@roles.USER)")` where the `roles` is a bean that could be defined like so: - ++ [source, java] ---- import org.springframework.stereotype.Component; @@ -253,28 +256,29 @@ public class Roles { public final String ADMIN = "admin"; public final String USER = "user"; } - ---- -* hasAnyRole +hasAnyRole:: -In the same fashion as `hasRole`, users can use `hasAnyRole` to check if the logged in user has any of the specified roles. Some examples are: -`@PreAuthorize("hasAnyRole('admin')")`, `@PreAuthorize("hasAnyRole(@roles.USER, 'view')")` +In the same fashion as `hasRole`, users can use `hasAnyRole` to check if the logged in user has any of the specified roles. ++ +Some examples are: `@PreAuthorize("hasAnyRole('admin')")`, `@PreAuthorize("hasAnyRole(@roles.USER, 'view')")` permitAll:: Adding `@PreAuthorize("permitAll()")` to a method will ensure that that method is accessible by any user (including anonymous users). Adding it to a class will ensure that all public methods -+of the class that are not annotated with any other Spring Security annotation will be accessible. +of the class that are not annotated with any other Spring Security annotation will be accessible. denyAll:: Adding `@PreAuthorize("denyAll()")` to a method will ensure that that method is not accessible by any user. Adding it to a class will ensure that all public methods -+of the class that are not annotated with any other Spring Security annotation will not be accessible to any user. +of the class that are not annotated with any other Spring Security annotation will not be accessible to any user. isAnonymous:: When annotating a bean method with `@PreAuthorize("isAnonymous()")` the method will only be accessible if the current user is anonymous - i.e. a non logged in user. isAuthenticated:: When annotating a bean method with `@PreAuthorize("isAuthenticated()")` the method will only be accessible if the current user is a logged in user. Essentially the -+method is only unavailable for anonymous users. +method is only unavailable for anonymous users. #paramName == authentication.principal.username:: This syntax allows users to check if a parameter (or a field of the parameter) of the secured method is equal to the logged in username. -+Examples of this use case are: - ++ +Examples of this use case are: ++ [source,java] ---- public class Person { @@ -304,18 +308,18 @@ public class MyComponent { } } ---- - <1> `doSomething` can be executed if the current logged in user is the same as the `username` method parameter <2> `doSomethingElse` can be executed if the current logged in user is the same as the `name` field of `person` method parameter ++ +TIP: the use of `authentication.` is optional, so using `principal.username` has the same result. -*Tip* the use of `authentication.` is optional, so using `principal.username` has the same result. - -#paramName != authentication.principal.username:: This is similar to the previous expression with the difference being that the method parameter must different than the logged in username. +#paramName != authentication.principal.username:: This is similar to the previous expression with the difference being that the method parameter must be different than the logged in username. @beanName.method():: This syntax allows developers to specify that the execution of method of a specific bean will determine if the current user can access the secured method. -+The syntax is best explained with an example. -+Let's assume that a `MyComponent` bean has been created like so: - ++ +The syntax is best explained with an example. +Let's assume that a `MyComponent` bean has been created like so: ++ [source,java] ---- @Component @@ -327,12 +331,12 @@ public class MyComponent { } } ---- - ++ The `doSomething` method has been annotated with `@PreAuthorize` using an expression that indicates that method `check` of a bean named `personChecker` needs to be invoked to determine whether the current user is authorized to invoke the `doSomething` method. - ++ An example of the `PersonChecker` could be: - ++ [source,java] ---- @Component @@ -344,7 +348,7 @@ public class PersonChecker { } } ---- - ++ Note that for the `check` method the parameter types must match what is specified in `@PreAuthorize` and that the return type must be a `boolean`. ===== Combining expressions From 41473d69c33802e616a1b68d97a6723bda40242e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 4 Dec 2019 00:10:21 +0100 Subject: [PATCH 192/602] Fix a couple of things in the precedence warning --- docs/src/main/asciidoc/spring-security.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/spring-security.adoc b/docs/src/main/asciidoc/spring-security.adoc index 457a13feddb6b..46817567605c2 100644 --- a/docs/src/main/asciidoc/spring-security.adoc +++ b/docs/src/main/asciidoc/spring-security.adoc @@ -377,7 +377,7 @@ Some examples of allowed expressions are: } ---- -Also to be noted that currently parenthesis are not supported and also each of expressions are evaluated from left to right when needed. +Also to be noted that currently parentheses are not supported and expressions are evaluated from left to right when needed. == Important Technical Note From 06eb6eae699e3355a3694003de143b347872161c Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 26 Nov 2019 18:51:19 +0000 Subject: [PATCH 193/602] Support for the fixed redirect URI --- .../runtime/CodeAuthenticationMechanism.java | 128 ++++++++++++++---- .../io/quarkus/oidc/runtime/OidcConfig.java | 15 +- .../AuthenticationRedirectException.java | 29 ++++ .../security/HttpSecurityRecorder.java | 6 + .../src/main/resources/application.properties | 1 + .../io/quarkus/it/keycloak/CodeFlowTest.java | 19 ++- 6 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AuthenticationRedirectException.java diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 6e58af3a13dfc..d53f77fc6e99d 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -11,6 +11,8 @@ import javax.enterprise.context.ApplicationScoped; +import org.jboss.logging.Logger; + import io.netty.handler.codec.http.HttpResponseStatus; import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.IdTokenCredential; @@ -19,6 +21,7 @@ import io.quarkus.security.identity.IdentityProviderManager; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.runtime.QuarkusSecurityIdentity; +import io.quarkus.vertx.http.runtime.security.AuthenticationRedirectException; import io.quarkus.vertx.http.runtime.security.ChallengeData; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpHeaders; @@ -31,9 +34,11 @@ @ApplicationScoped public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMechanism { + private static final Logger LOG = Logger.getLogger(CodeAuthenticationMechanism.class); + private static final String STATE_COOKIE_NAME = "q_auth"; private static final String SESSION_COOKIE_NAME = "q_session"; - private static final String SESSION_COOKIE_DELIM = "___"; + private static final String COOKIE_DELIM = "___"; private static QuarkusSecurityIdentity augmentIdentity(SecurityIdentity securityIdentity, String accessToken, @@ -62,7 +67,7 @@ public CompletionStage authenticate(RoutingContext context, // if session already established, try to re-authenticate if (sessionCookie != null) { - String[] tokens = sessionCookie.getValue().split(SESSION_COOKIE_DELIM); + String[] tokens = sessionCookie.getValue().split(COOKIE_DELIM); return authenticate(identityProviderManager, new IdTokenCredential(tokens[0])) .thenCompose(new Function>() { @Override @@ -78,9 +83,8 @@ public CompletionStage apply(SecurityIdentity securityIdentity @Override public CompletionStage getChallenge(RoutingContext context) { - removeSessionCookie(context); + removeCookie(context, SESSION_COOKIE_NAME); ChallengeData challenge; - JsonObject params = new JsonObject(); List scopes = new ArrayList<>(); @@ -89,8 +93,12 @@ public CompletionStage getChallenge(RoutingContext context) { config.authentication.scopes.ifPresent(scopes::addAll); params.put("scopes", new JsonArray(scopes)); - params.put("redirect_uri", buildRedirectUri(context)); - params.put("state", generateState(context)); + + URI absoluteUri = URI.create(context.request().absoluteURI()); + String dynamicPath = getDynamicPath(context, absoluteUri); + params.put("redirect_uri", buildCodeRedirectUri(context, absoluteUri, dynamicPath)); + + params.put("state", generateState(context, dynamicPath)); challenge = new ChallengeData(HttpResponseStatus.FOUND.code(), HttpHeaders.LOCATION, auth.authorizeURL(params)); @@ -105,9 +113,53 @@ private CompletionStage performCodeFlow(IdentityProviderManage if (code == null) { return CompletableFuture.completedFuture(null); } + CompletableFuture cf = new CompletableFuture<>(); + + URI absoluteUri = URI.create(context.request().absoluteURI()); + + Cookie stateCookie = context.getCookie(STATE_COOKIE_NAME); + if (stateCookie != null) { + List values = context.queryParam("state"); + // IDP must return a 'state' query parameter and the value of the state cookie must start with this parameter's value + if (values.size() != 1 || !stateCookie.getValue().startsWith(values.get(0))) { + cf.completeExceptionally(new AuthenticationFailedException()); + return cf; + } else if (context.queryParam("pathChecked").isEmpty()) { + // This is an original redirect from IDP, check if the request path needs to be updated + String[] pair = stateCookie.getValue().split(COOKIE_DELIM); + if (pair.length == 2) { + // The extra path that needs to be added to the current request path + String extraPath = pair[1]; + // Adding a query marker that the state cookie has already been used to restore the path + // as deleting it now would increase the risk of CSRF + String extraQuery = "?pathChecked=true"; + + // The query parameters returned from IDP need to be included + if (absoluteUri.getRawQuery() != null) { + extraQuery += ("&" + absoluteUri.getRawQuery()); + } + + String localRedirectUri = buildLocalRedirectUri(context, absoluteUri, extraPath + extraQuery); + LOG.debug("Local redirectUri: " + localRedirectUri); + + cf.completeExceptionally(new AuthenticationRedirectException(localRedirectUri)); + return cf; + } + // The redirect path matches the original request path, the state cookie is no longer needed + removeCookie(context, STATE_COOKIE_NAME); + } else { + // Local redirect restoring the original request path, the state cookie is no longer needed + removeCookie(context, STATE_COOKIE_NAME); + } + } else { + // State cookie must be available to minimize the risk of CSRF + cf.completeExceptionally(new AuthenticationFailedException()); + return cf; + } + params.put("code", code); - params.put("redirect_uri", buildRedirectUri(context)); + params.put("redirect_uri", buildCodeRedirectUri(context, absoluteUri, getDynamicPath(context, absoluteUri))); auth.authenticate(params, userAsyncResult -> { if (userAsyncResult.failed()) { @@ -129,46 +181,74 @@ private CompletionStage performCodeFlow(IdentityProviderManage return cf; } - private void processSuccessfulAuthentication(RoutingContext context, CompletableFuture cf, + private void processSuccessfulAuthentication(RoutingContext context, + CompletableFuture cf, AccessToken result, SecurityIdentity securityIdentity) { - removeSessionCookie(context); + removeCookie(context, SESSION_COOKIE_NAME); CookieImpl cookie = new CookieImpl(SESSION_COOKIE_NAME, new StringBuilder(result.opaqueIdToken()) - .append(SESSION_COOKIE_DELIM) + .append(COOKIE_DELIM) .append(result.opaqueAccessToken()) - .append(SESSION_COOKIE_DELIM) + .append(COOKIE_DELIM) .append(result.opaqueRefreshToken()).toString()); cookie.setMaxAge(result.idToken().getInteger("exp")); cookie.setSecure(context.request().isSSL()); cookie.setHttpOnly(true); - context.response().addCookie(cookie); + cf.complete(augmentIdentity(securityIdentity, result.opaqueAccessToken(), result.opaqueRefreshToken())); } - private String generateState(RoutingContext context) { - CookieImpl cookie = new CookieImpl(STATE_COOKIE_NAME, UUID.randomUUID().toString()); + private String getDynamicPath(RoutingContext context, URI absoluteUri) { + if (config.authentication.redirectPath.isPresent()) { + String redirectPath = config.authentication.redirectPath.get(); + String requestPath = absoluteUri.getRawPath(); + if (requestPath.startsWith(redirectPath) && requestPath.length() > redirectPath.length()) { + return requestPath.substring(redirectPath.length()); + } + } + return null; + } + + private String generateState(RoutingContext context, String dynamicPath) { + String uuid = UUID.randomUUID().toString(); + String cookieValue = uuid; + if (dynamicPath != null) { + cookieValue += (COOKIE_DELIM + dynamicPath); + } + + CookieImpl cookie = new CookieImpl(STATE_COOKIE_NAME, cookieValue); cookie.setHttpOnly(true); cookie.setSecure(context.request().isSSL()); - cookie.setMaxAge(-1); + // max-age is 30 minutes + cookie.setMaxAge(60 * 30); context.response().addCookie(cookie); - - return cookie.getValue(); + return uuid; } - private String buildRedirectUri(RoutingContext context) { - URI absoluteUri = URI.create(context.request().absoluteURI()); + private String buildCodeRedirectUri(RoutingContext context, URI absoluteUri, String dynamicPath) { StringBuilder builder = new StringBuilder(context.request().scheme()).append("://") - .append(absoluteUri.getAuthority()) - .append(absoluteUri.getPath()); + .append(absoluteUri.getAuthority()); - return builder.toString(); + String path = dynamicPath != null + ? config.authentication.redirectPath.get() + : absoluteUri.getRawPath(); + + return builder.append(path).toString(); + } + + private String buildLocalRedirectUri(RoutingContext context, URI absoluteUri, String extraPath) { + return new StringBuilder(context.request().scheme()).append("://") + .append(absoluteUri.getAuthority()) + .append(absoluteUri.getRawPath()) + .append(extraPath) + .toString(); } - private void removeSessionCookie(RoutingContext context) { - context.response().removeCookie(SESSION_COOKIE_NAME, true); + private Cookie removeCookie(RoutingContext context, String cookieName) { + return context.response().removeCookie(cookieName, true); } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java index 7d9aee11b475b..aa940299e003e 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java @@ -138,12 +138,23 @@ public static Roles fromClaimPathAndSeparator(String path, String sep) { } } + /** + * Defines the authorization request properties when authenticating + * users using the Authorization Code Grant Type. + */ @ConfigGroup public static class Authentication { + /** + * Relative path for calculating a "redirect_uri" parameter. + * It set it will be appended to the request URI's host and port, otherwise the complete request URI will be used. + * It has to start from the forward slash, for example: "/service" + * + */ + @ConfigItem + public Optional redirectPath; /** - * Defines a fixed list of scopes which should be added to authorization requests when authenticating users using the - * Authorization Code Grant Type. + * List of scopes * */ @ConfigItem diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AuthenticationRedirectException.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AuthenticationRedirectException.java new file mode 100644 index 0000000000000..746d076a6feec --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/AuthenticationRedirectException.java @@ -0,0 +1,29 @@ +package io.quarkus.vertx.http.runtime.security; + +/** + * Exception indicating that a redirect is required for the authentication flow to complete. + * For example, it can be used during an OIDC authorization code flow to redirect a user to + * the original request URI. + */ +public class AuthenticationRedirectException extends RuntimeException { + + int code; + String redirectUri; + + public AuthenticationRedirectException(String redirectUri) { + this(302, redirectUri); + } + + public AuthenticationRedirectException(int code, String redirectUri) { + this.code = code; + this.redirectUri = redirectUri; + } + + public int getCode() { + return 302; + } + + public String getRedirectUri() { + return redirectUri; + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java index 3bc24a6ae9b9e..06e6c0e07831e 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityRecorder.java @@ -15,6 +15,7 @@ import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; import io.vertx.ext.web.RoutingContext; @Recorder @@ -47,6 +48,11 @@ public void run() { event.response().end(); } }); + } else if (throwable instanceof AuthenticationRedirectException) { + AuthenticationRedirectException redirectEx = (AuthenticationRedirectException) throwable; + event.response().setStatusCode(redirectEx.getCode()); + event.response().headers().set(HttpHeaders.LOCATION, redirectEx.getRedirectUri()); + event.response().end(); } else { event.fail(throwable); } diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties index 2d605b30ddfef..189b2c0ab62ff 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -2,6 +2,7 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus quarkus.oidc.client-id=quarkus-app quarkus.oidc.authentication.scopes=profile,email,phone +quarkus.oidc.authentication.redirect-path=/web-app quarkus.http.cors=true quarkus.oidc.application-type=web-app quarkus.http.auth.permission.roles1.paths=/index.html diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index 5ec2eb5ccfe5c..10961917ae92c 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -208,7 +208,6 @@ public void testIdTokenInjection() throws IOException, InterruptedException { } @Test - //@Disabled public void testAccessTokenInjection() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); @@ -252,6 +251,24 @@ public void testAccessAndRefreshTokenInjection() throws IOException, Interrupted } } + @Test + public void testAccessAndRefreshTokenInjectionWithoutIndexHtml() throws IOException, InterruptedException { + try (final WebClient webClient = new WebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh"); + + assertEquals("Log in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + page = loginForm.getInputByName("login").click(); + + assertEquals("RT injected", page.getBody().asText()); + } + } + @Test public void testNoCodeFlowUnprotected() { RestAssured.when().get("/public-web-app/access") From e06e70b605465bfd53afbf612a6a12d6f3a809ba Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 4 Dec 2019 18:56:52 +0200 Subject: [PATCH 194/602] Fix XML content handling in Spring Web Controller Fixes: #5943 --- .../web/deployment/SpringWebProcessor.java | 4 ++++ integration-tests/spring-web/pom.xml | 4 ++++ .../java/io/quarkus/it/spring/web/Book.java | 24 +++++++++++++++++++ .../quarkus/it/spring/web/BookController.java | 14 +++++++++++ .../it/spring/web/SpringControllerTest.java | 9 +++++++ 5 files changed, 55 insertions(+) create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Book.java create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/BookController.java diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java index 61b68f5e6167e..de194b9e3c385 100644 --- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java +++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java @@ -287,6 +287,10 @@ public void registerProviders(BeanArchiveIndexBuildItem beanArchiveIndexBuildIte useAllAvailable = true; break OUTER; } + if (collectProviders(providersToRegister, categorizedContextResolvers, instance, "produces")) { + useAllAvailable = true; + break OUTER; + } if (collectProviders(providersToRegister, categorizedReaders, instance, "consumes")) { useAllAvailable = true; diff --git a/integration-tests/spring-web/pom.xml b/integration-tests/spring-web/pom.xml index 309dd9734bcc2..edeeb7e964e09 100644 --- a/integration-tests/spring-web/pom.xml +++ b/integration-tests/spring-web/pom.xml @@ -23,6 +23,10 @@ io.quarkus quarkus-undertow + + io.quarkus + quarkus-resteasy-jaxb + io.quarkus quarkus-spring-di diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Book.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Book.java new file mode 100644 index 0000000000000..a893ed259da9b --- /dev/null +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Book.java @@ -0,0 +1,24 @@ +package io.quarkus.it.spring.web; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class Book { + + private String name; + + public Book() { + } + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/BookController.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/BookController.java new file mode 100644 index 0000000000000..7d2351bcb14b4 --- /dev/null +++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/BookController.java @@ -0,0 +1,14 @@ +package io.quarkus.it.spring.web; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BookController { + + @GetMapping(produces = MediaType.APPLICATION_XML_VALUE, path = "/book") + public Book someBook() { + return new Book("Guns germs and steel"); + } +} diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java index 2383590700d34..8d12461c8a247 100644 --- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java +++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java @@ -204,4 +204,13 @@ public void testResponseEntityWithIllegalArgumentException() { .body(containsString("hello from error")) .statusCode(402); } + + @Test + public void testMethodReturningXmlContent() { + RestAssured.when().get("/book") + .then() + .statusCode(200) + .contentType("application/xml") + .body(containsString("steel")); + } } From 1c218df51f3e2e89d2a5d3f622cfceb93f9491f8 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 3 Dec 2019 09:18:02 +0530 Subject: [PATCH 195/602] issue-5424 Make hot deployment work reliably for newly added resources in QuarkusDevModeTest --- .../test/NotFoundExceptionMapperTestCase.java | 7 ++----- .../java/io/quarkus/test/QuarkusDevModeTest.java | 13 +++++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java index 955d8235a1a06..a078c8ef4b899 100644 --- a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NotFoundExceptionMapperTestCase.java @@ -7,7 +7,6 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -54,9 +53,8 @@ public void testJsonResourceNotFound() { .contentType(ContentType.JSON); } - @Disabled("https://github.com/quarkusio/quarkus/issues/5424") @Test - public void shouldDisplayNewAddedFileIn404ErrorPage() throws InterruptedException { + public void shouldDisplayNewAddedFileIn404ErrorPage() { String CONTENT = "html content"; test.addResourceFile(META_INF_RESOURCES + "index2.html", CONTENT); @@ -74,9 +72,8 @@ public void shouldDisplayNewAddedFileIn404ErrorPage() throws InterruptedExceptio .body(containsString("index2.html")); // check that index2.html is displayed } - @Disabled("https://github.com/quarkusio/quarkus/issues/5424") @Test - public void shouldNotDisplayDeletedFileIn404ErrorPage() throws InterruptedException { + public void shouldNotDisplayDeletedFileIn404ErrorPage() { String TEST_CONTENT = "test html content"; test.addResourceFile(META_INF_RESOURCES + "test.html", TEST_CONTENT); diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java index 84b59d8219b6a..ed079283a1d0c 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java @@ -310,6 +310,8 @@ public void addSourceFile(Class sourceFile) { Path path = copySourceFilesForClass(projectSourceRoot, deploymentSourcePath, testLocation, testLocation.resolve(sourceFile.getName().replace(".", "/") + ".class")); sleepForFileChanges(path); + // since this is a new file addition, even wait for the parent dir's last modified timestamp to change + sleepForFileChanges(path.getParent()); } void modifyFile(String name, Function mutator, Path path) { @@ -384,11 +386,15 @@ public void modifyResourceFile(String path, Function mutator) { * the deployment resources directory */ public void addResourceFile(String path, byte[] data) { + final Path resourceFilePath = deploymentResourcePath.resolve(path); try { - Files.write(deploymentResourcePath.resolve(path), data); + Files.write(resourceFilePath, data); } catch (IOException e) { throw new UncheckedIOException(e); } + sleepForFileChanges(resourceFilePath); + // since this is a new file addition, even wait for the parent dir's last modified timestamp to change + sleepForFileChanges(resourceFilePath.getParent()); } /** @@ -396,11 +402,14 @@ public void addResourceFile(String path, byte[] data) { * the deployment resources directory */ public void deleteResourceFile(String path) { + final Path resourceFilePath = deploymentResourcePath.resolve(path); try { - Files.delete(deploymentResourcePath.resolve(path)); + Files.delete(resourceFilePath); } catch (IOException e) { throw new UncheckedIOException(e); } + // wait for last modified time of the parent to get updated + sleepForFileChanges(resourceFilePath.getParent()); } /** From e0d90e0b5fdd48143676bc76f3dbd9b27673a730 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 29 Nov 2019 09:07:32 +1100 Subject: [PATCH 196/602] Check if the user ran compile and otherwise run it for them Fixes #5832 --- .../main/java/io/quarkus/maven/DevMojo.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 2aa5acca4ae0c..f915e64dd7242 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -71,6 +71,30 @@ */ @Mojo(name = "dev", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class DevMojo extends AbstractMojo { + + /** + * running any one of these phases means the compile phase will have been run, if these have + * not been run we manually run compile + */ + private static final Set POST_COMPILE_PHASES = new HashSet<>(Arrays.asList( + "compile", + "process-classes", + "generate-test-sources", + "process-test-sources", + "generate-test-resources", + "process-test-resources", + "test-compile", + "process-test-classes", + "test", + "prepare-package", + "package", + "pre-integration-test", + "integration-test", + "post-integration-test", + "verify", + "install", + "deploy")); + /** * The directory for compiled classes. */ @@ -224,8 +248,20 @@ public void execute() throws MojoFailureException, MojoExecutionException { if (!sourceDir.isDirectory()) { getLog().warn("The project's sources directory does not exist " + sourceDir); } + //we check to see if there was a compile (or later) goal before this plugin + boolean compileNeeded = true; + for (String goal : session.getGoals()) { + if (POST_COMPILE_PHASES.contains(goal)) { + compileNeeded = false; + break; + } + if (goal.endsWith("quarkus:dev")) { + break; + } + } - if (!buildDir.isDirectory() || !new File(buildDir, "classes").isDirectory()) { + //if the user did not compile we run it for them + if (compileNeeded) { try { InvocationRequest request = new DefaultInvocationRequest(); request.setBatchMode(true); From 15db5c10e1c6829a1f1794bf7b2630eb2bf617e7 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sun, 10 Nov 2019 09:20:32 +0100 Subject: [PATCH 197/602] Upgrade GraalVM to 19.3.0 --- bom/runtime/pom.xml | 6 +- build-parent/pom.xml | 2 +- .../ThreadLocalRandomProcessor.java | 15 ----- .../quarkus/deployment/pkg/NativeConfig.java | 4 +- .../pkg/steps/NativeImageBuildStep.java | 12 ++-- .../steps/NativeImageAutoFeatureStep.java | 4 +- core/runtime/pom.xml | 2 +- .../SecurityServicesFeatureSubstitutions.java | 61 +++++++++++++++++++ core/test-extension/runtime/pom.xml | 2 +- .../quarkus/gradle/tasks/QuarkusNative.java | 2 +- .../io/quarkus/maven/NativeImageMojo.java | 2 +- .../integration-test-pom.xml | 1 - docs/src/main/asciidoc/native-and-ssl.adoc | 2 +- extensions/agroal/runtime/pom.xml | 2 +- .../amazon-lambda-http/deployment/pom.xml | 2 +- extensions/amazon-lambda-http/runtime/pom.xml | 4 +- extensions/artemis-core/runtime/pom.xml | 2 +- .../azure-functions-http/deployment/pom.xml | 2 +- .../azure-functions-http/runtime/pom.xml | 2 +- extensions/caffeine/runtime/pom.xml | 2 +- .../elasticsearch-rest-client/runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- extensions/elytron-security/runtime/pom.xml | 2 +- extensions/flyway/runtime/pom.xml | 2 +- extensions/hibernate-orm/runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- .../hibernate-validator/runtime/pom.xml | 2 +- extensions/infinispan-client/runtime/pom.xml | 2 +- .../infinispan-embedded/runtime/pom.xml | 2 +- extensions/jaeger/runtime/pom.xml | 2 +- extensions/jaxb/runtime/pom.xml | 2 +- extensions/jdbc/jdbc-derby/runtime/pom.xml | 2 +- extensions/jdbc/jdbc-h2/runtime/pom.xml | 2 +- extensions/jdbc/jdbc-mariadb/runtime/pom.xml | 2 +- extensions/jdbc/jdbc-mssql/runtime/pom.xml | 2 +- extensions/jdbc/jdbc-mysql/runtime/pom.xml | 2 +- .../jdbc/jdbc-postgresql/runtime/pom.xml | 2 +- .../runtime/deployment/JGitProcessor.java | 10 ++- extensions/jgit/runtime/pom.xml | 2 +- .../jgit/runtime/PortWatcherRunTime.java | 16 +++++ .../runtime/PortWatcherSubstitutions.java | 27 ++++++++ extensions/kafka-client/runtime/pom.xml | 2 +- extensions/kafka-streams/runtime/pom.xml | 2 +- .../KeycloakReflectionBuildStep.java | 7 +++ .../keycloak-authorization/runtime/pom.xml | 4 ++ .../pep/runtime/PortWatcherRunTime.java | 16 +++++ .../pep/runtime/PortWatcherSubstitutions.java | 27 ++++++++ extensions/kubernetes-client/runtime/pom.xml | 2 +- extensions/mongodb-client/runtime/pom.xml | 2 +- extensions/narayana-jta/runtime/pom.xml | 2 +- extensions/neo4j/runtime/pom.xml | 2 +- extensions/netty/runtime/pom.xml | 2 +- extensions/quartz/runtime/pom.xml | 2 +- extensions/rest-client/runtime/pom.xml | 2 +- extensions/resteasy-common/deployment/pom.xml | 2 +- extensions/resteasy-common/runtime/pom.xml | 2 +- extensions/resteasy/deployment/pom.xml | 2 +- extensions/security/runtime/pom.xml | 2 +- .../deployment/pom.xml | 2 +- .../smallrye-fault-tolerance/runtime/pom.xml | 2 +- .../smallrye-opentracing/runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- .../undertow-websockets/runtime/pom.xml | 2 +- extensions/undertow/runtime/pom.xml | 2 +- extensions/vertx-core/runtime/pom.xml | 2 +- extensions/vertx/runtime/pom.xml | 2 +- .../amazon-lambda-http-resteasy/pom.xml | 1 - integration-tests/amazon-lambda-http/pom.xml | 1 - integration-tests/amazon-lambda/pom.xml | 1 - .../elytron-security-jdbc/pom.xml | 1 - integration-tests/flyway/pom.xml | 1 - .../hibernate-orm-panache/pom.xml | 1 - .../hibernate-search-elasticsearch/pom.xml | 1 - integration-tests/hibernate-validator/pom.xml | 1 - .../infinispan-cache-jpa/pom.xml | 1 - integration-tests/infinispan-embedded/pom.xml | 1 - integration-tests/jpa-derby/pom.xml | 1 - integration-tests/jpa-h2/pom.xml | 1 - integration-tests/jpa-mariadb/pom.xml | 1 - integration-tests/jpa-mssql/pom.xml | 1 - integration-tests/jpa-mysql/pom.xml | 1 - integration-tests/jpa-postgresql/pom.xml | 1 - integration-tests/jpa-without-entity/pom.xml | 1 - .../integration-tests/itest/pom.xml | 1 - integration-tests/neo4j/README.md | 1 - integration-tests/vault-app/pom.xml | 1 - integration-tests/vertx-graphql/pom.xml | 1 - integration-tests/vertx-http/pom.xml | 1 - 90 files changed, 232 insertions(+), 110 deletions(-) delete mode 100644 core/deployment/src/main/java/io/quarkus/deployment/ThreadLocalRandomProcessor.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java create mode 100644 extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java create mode 100644 extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java create mode 100644 extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java create mode 100644 extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 67f5f53e184c1..ffbe561fbe4e3 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -74,7 +74,7 @@ 3.5.2 1.7.1 - 19.2.1 + 19.3.0 1.0.0.Final 2.9.10.20191020 1.0.0.Final @@ -796,7 +796,7 @@ ${jboss-logmanager.version} - com.oracle.substratevm + org.graalvm.nativeimage svm @@ -1546,7 +1546,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm ${graal-sdk.version} provided diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ccb5edafdc8da..569d4ddcb53e3 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -32,7 +32,7 @@ - 19.2.1 + 19.3.0 4.1.1 0.0.9 3.8.4 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ThreadLocalRandomProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/ThreadLocalRandomProcessor.java deleted file mode 100644 index 6bbd0c0f55cee..0000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/ThreadLocalRandomProcessor.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.quarkus.deployment; - -import java.util.concurrent.ThreadLocalRandom; - -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; - -public class ThreadLocalRandomProcessor { - @BuildStep - RuntimeReinitializedClassBuildItem registerThreadLocalRandomReinitialize() { - // ThreadLocalRandom is bugged currently and doesn't reset the seeder - // See https://github.com/oracle/graal/issues/1614 for more details - return new RuntimeReinitializedClassBuildItem(ThreadLocalRandom.class.getName()); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index df72dd3f5401a..3ede83fb7f76e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -38,7 +38,7 @@ public class NativeConfig { /** * If JNI should be enabled */ - @ConfigItem(defaultValue = "false") + @ConfigItem(defaultValue = "true") public boolean enableJni; /** @@ -132,7 +132,7 @@ public class NativeConfig { /** * The docker image to use to do the image build */ - @ConfigItem(defaultValue = "quay.io/quarkus/ubi-quarkus-native-image:19.2.1") + @ConfigItem(defaultValue = "quay.io/quarkus/ubi-quarkus-native-image:19.3.0-java8") public String builderImage; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index ad5d4be80a4e9..2e09f768080b8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -73,7 +73,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa final String runnerJarName = runnerJar.getFileName().toString(); - boolean vmVersionOutOfDate = isThisGraalVMVersionObsolete(); + isThisGraalVMVersionObsolete(); HashMap env = new HashMap<>(System.getenv()); List nativeImage; @@ -178,8 +178,6 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa command.add("-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time command.add("-jar"); command.add(runnerJarName); - //https://github.com/oracle/graal/issues/660 - command.add("-J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1"); if (nativeConfig.enableFallbackImages) { command.add("-H:FallbackThreshold=5"); } else { @@ -296,16 +294,14 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } //FIXME remove after transition period - private boolean isThisGraalVMVersionObsolete() { + private void isThisGraalVMVersionObsolete() { final String vmName = System.getProperty("java.vm.name"); log.info("Running Quarkus native-image plugin on " + vmName); - final List obsoleteGraalVmVersions = Arrays.asList("1.0.0", "19.0.", "19.1.", "19.2.0"); + final List obsoleteGraalVmVersions = Arrays.asList("1.0.0", "19.0.", "19.1.", "19.2."); final boolean vmVersionIsObsolete = obsoleteGraalVmVersions.stream().anyMatch(vmName::contains); if (vmVersionIsObsolete) { - log.error("Out of date build of GraalVM detected! Please upgrade to GraalVM 19.2.1."); - return true; + log.error("Out of date build of GraalVM detected! Please upgrade to GraalVM 19.3.0."); } - return false; } private static File getNativeImageExecutable(Optional graalVmHome, File javaHome, Map env) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index e7706d74122ae..776aeb77a2860 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -65,7 +65,7 @@ public class NativeImageAutoFeatureStep { static final String RUNTIME_REFLECTION = RuntimeReflection.class.getName(); static final String BEFORE_ANALYSIS_ACCESS = Feature.BeforeAnalysisAccess.class.getName(); static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry"; - static final String LOCALIZATION_SUPPORT = "com.oracle.svm.core.jdk.LocalizationSupport"; + static final String LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.LocalizationFeature"; @BuildStep List registerPackageResources( @@ -205,7 +205,7 @@ public void write(String s, byte[] bytes) { } if (!resourceBundles.isEmpty()) { - ResultHandle locClass = overallCatch.loadClass(LOCALIZATION_SUPPORT); + ResultHandle locClass = overallCatch.loadClass(LOCALIZATION_FEATURE); ResultHandle params = overallCatch.marshalAsArray(Class.class, overallCatch.loadClass(String.class)); ResultHandle registerMethod = overallCatch.invokeVirtualMethod( diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 5dd0cff829c7f..9621dd3d2e2e3 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -70,7 +70,7 @@ graal-sdk - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java new file mode 100644 index 0000000000000..53a0af793c52f --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java @@ -0,0 +1,61 @@ +package io.quarkus.runtime.graal; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.SecurityServicesFeature; + +/** + * @deprecated This class contains a workaround for a NPE that is thrown when a third-party security provider (meaning not one + * from the JDK) is used in Quarkus. A GraalVM issue has been created for it, the fix should be part of GraalVM + * 19.3.1. This class should be removed as soon as we integrate a GraalVM release that includes the fix.
+ * See https://github.com/oracle/graal/issues/1883 for more details about the bug and the fix. + */ +@Deprecated +@TargetClass(value = SecurityServicesFeature.class, onlyWith = GraalVersion19_3_0.class) +final class Target_com_oracle_svm_hosted_SecurityServicesFeature { + + @Substitute + private static Class lambda$getConsParamClassAccessor$0(Map knownEngines, Field consParamClassNameField, + BeforeAnalysisAccess access, java.lang.String serviceType) { + try { + /* + * Access the Provider.knownEngines map and extract the EngineDescription + * corresponding to the serviceType. Note that the map holds EngineDescription(s) of + * only those service types that are shipped in the JDK. From the EngineDescription + * object extract the value of the constructorParameterClassName field then, if the + * class name is not null, get the corresponding Class object and return it. + */ + /* EngineDescription */Object engineDescription = knownEngines.get(serviceType); + /* + * This isn't an engine known to the Provider (which actually means that it isn't + * one that's shipped in the JDK), so we don't have the predetermined knowledge of + * the constructor param class. + */ + if (engineDescription == null) { + return null; + } + String constrParamClassName = (String) consParamClassNameField.get(engineDescription); + if (constrParamClassName != null) { + return access.findClassByName(constrParamClassName); + } + } catch (IllegalAccessException e) { + VMError.shouldNotReachHere(e); + } + return null; + + } +} + +final class GraalVersion19_3_0 implements BooleanSupplier { + public boolean getAsBoolean() { + final String version = System.getProperty("org.graalvm.version"); + return version.startsWith("19.3."); + } +} diff --git a/core/test-extension/runtime/pom.xml b/core/test-extension/runtime/pom.xml index 208261cf59b6e..195aa6ed065e5 100644 --- a/core/test-extension/runtime/pom.xml +++ b/core/test-extension/runtime/pom.xml @@ -31,7 +31,7 @@ quarkus-arc
- com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index b1b95b4727f17..068ace098c274 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -49,7 +49,7 @@ public class QuarkusNative extends QuarkusTask { private boolean enableServer = false; - private boolean enableJni = false; + private boolean enableJni = true; private boolean autoServiceLoaderRegistration = false; diff --git a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java index f69dab9920482..910da7031d796 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java @@ -100,7 +100,7 @@ public class NativeImageMojo extends AbstractMojo { @Parameter(defaultValue = "false") private Boolean enableServer; - @Parameter(defaultValue = "false") + @Parameter(defaultValue = "true") private Boolean enableJni; @Parameter(defaultValue = "false") diff --git a/devtools/maven/src/main/resources/create-extension-templates/integration-test-pom.xml b/devtools/maven/src/main/resources/create-extension-templates/integration-test-pom.xml index 25505bd88e682..3e81c40e855ec 100644 --- a/devtools/maven/src/main/resources/create-extension-templates/integration-test-pom.xml +++ b/devtools/maven/src/main/resources/create-extension-templates/integration-test-pom.xml @@ -105,7 +105,6 @@ false false ${graalvmHome} - true true false diff --git a/docs/src/main/asciidoc/native-and-ssl.adoc b/docs/src/main/asciidoc/native-and-ssl.adoc index a379b60e310b1..3baa641d49ec8 100644 --- a/docs/src/main/asciidoc/native-and-ssl.adoc +++ b/docs/src/main/asciidoc/native-and-ssl.adoc @@ -125,7 +125,7 @@ And build again: If you check carefully the native executable build options, you can see that the SSL related options are gone: ``` -[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] /opt/graalvm/bin/native-image -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dcom.sun.xml.internal.bind.v2.bytecode.ClassTailor.noOptimize=true -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar rest-client-1.0-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:+PrintAnalysisCallTree -H:EnableURLProtocols=http -H:-SpawnIsolates -H:-JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace +[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] /opt/graalvm/bin/native-image -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dcom.sun.xml.internal.bind.v2.bytecode.ClassTailor.noOptimize=true -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar rest-client-1.0-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:+PrintAnalysisCallTree -H:EnableURLProtocols=http -H:-SpawnIsolates -H:+JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace ``` And we end up with: diff --git a/extensions/agroal/runtime/pom.xml b/extensions/agroal/runtime/pom.xml index 3600282874ecd..4c191c9b6a3ea 100644 --- a/extensions/agroal/runtime/pom.xml +++ b/extensions/agroal/runtime/pom.xml @@ -27,7 +27,7 @@ quarkus-narayana-jta - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/amazon-lambda-http/deployment/pom.xml b/extensions/amazon-lambda-http/deployment/pom.xml index 511d6393b3c67..94476ebeb81b6 100644 --- a/extensions/amazon-lambda-http/deployment/pom.xml +++ b/extensions/amazon-lambda-http/deployment/pom.xml @@ -32,7 +32,7 @@ quarkus-amazon-lambda-http - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/amazon-lambda-http/runtime/pom.xml b/extensions/amazon-lambda-http/runtime/pom.xml index 42509a4a966ae..800c30c47bc1d 100644 --- a/extensions/amazon-lambda-http/runtime/pom.xml +++ b/extensions/amazon-lambda-http/runtime/pom.xml @@ -29,7 +29,7 @@ quarkus-core - com.oracle.substratevm + org.graalvm.nativeimage svm @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/extensions/artemis-core/runtime/pom.xml b/extensions/artemis-core/runtime/pom.xml index 2ce771b64827b..c08161d8bcaa6 100644 --- a/extensions/artemis-core/runtime/pom.xml +++ b/extensions/artemis-core/runtime/pom.xml @@ -65,7 +65,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/azure-functions-http/deployment/pom.xml b/extensions/azure-functions-http/deployment/pom.xml index bf6846299c1ce..b80d6dc35f14c 100644 --- a/extensions/azure-functions-http/deployment/pom.xml +++ b/extensions/azure-functions-http/deployment/pom.xml @@ -23,7 +23,7 @@ quarkus-vertx-http-deployment - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/azure-functions-http/runtime/pom.xml b/extensions/azure-functions-http/runtime/pom.xml index da4ad843166eb..353b363fdeae2 100644 --- a/extensions/azure-functions-http/runtime/pom.xml +++ b/extensions/azure-functions-http/runtime/pom.xml @@ -24,7 +24,7 @@ quarkus-core - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/caffeine/runtime/pom.xml b/extensions/caffeine/runtime/pom.xml index 2246644b68522..aa917db6e66a0 100644 --- a/extensions/caffeine/runtime/pom.xml +++ b/extensions/caffeine/runtime/pom.xml @@ -18,7 +18,7 @@ caffeine - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/elasticsearch-rest-client/runtime/pom.xml b/extensions/elasticsearch-rest-client/runtime/pom.xml index 3f269656070bf..c12933c484ba6 100644 --- a/extensions/elasticsearch-rest-client/runtime/pom.xml +++ b/extensions/elasticsearch-rest-client/runtime/pom.xml @@ -43,7 +43,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/elytron-security-properties-file/runtime/pom.xml b/extensions/elytron-security-properties-file/runtime/pom.xml index 0cbc3141c4543..6017955d47d95 100644 --- a/extensions/elytron-security-properties-file/runtime/pom.xml +++ b/extensions/elytron-security-properties-file/runtime/pom.xml @@ -27,7 +27,7 @@ quarkus-elytron-security - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/elytron-security/runtime/pom.xml b/extensions/elytron-security/runtime/pom.xml index 71aec8628e7e6..a80fea5800a30 100644 --- a/extensions/elytron-security/runtime/pom.xml +++ b/extensions/elytron-security/runtime/pom.xml @@ -23,7 +23,7 @@ quarkus-vertx-http - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml index f1804ae019a7f..c3e8a93eef5ee 100644 --- a/extensions/flyway/runtime/pom.xml +++ b/extensions/flyway/runtime/pom.xml @@ -30,7 +30,7 @@ quarkus-narayana-jta - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/hibernate-orm/runtime/pom.xml b/extensions/hibernate-orm/runtime/pom.xml index 491c0406cd599..68d973162fc06 100644 --- a/extensions/hibernate-orm/runtime/pom.xml +++ b/extensions/hibernate-orm/runtime/pom.xml @@ -76,7 +76,7 @@ jakarta.transaction-api - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/hibernate-search-elasticsearch/runtime/pom.xml b/extensions/hibernate-search-elasticsearch/runtime/pom.xml index 56449247de528..9118d971db2d7 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/pom.xml +++ b/extensions/hibernate-search-elasticsearch/runtime/pom.xml @@ -35,7 +35,7 @@ hibernate-search-mapper-orm - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/hibernate-validator/runtime/pom.xml b/extensions/hibernate-validator/runtime/pom.xml index 7183ba22ea317..d19c75c3ef356 100644 --- a/extensions/hibernate-validator/runtime/pom.xml +++ b/extensions/hibernate-validator/runtime/pom.xml @@ -58,7 +58,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml index a1f9138c2e6b9..c1cebdb832fca 100644 --- a/extensions/infinispan-client/runtime/pom.xml +++ b/extensions/infinispan-client/runtime/pom.xml @@ -85,7 +85,7 @@ protostream-processor - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/infinispan-embedded/runtime/pom.xml b/extensions/infinispan-embedded/runtime/pom.xml index 3b10ece87523f..db14ebc7fb6e9 100644 --- a/extensions/infinispan-embedded/runtime/pom.xml +++ b/extensions/infinispan-embedded/runtime/pom.xml @@ -53,7 +53,7 @@ true - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jaeger/runtime/pom.xml b/extensions/jaeger/runtime/pom.xml index 552f9fc3751f7..5925e55b73e00 100644 --- a/extensions/jaeger/runtime/pom.xml +++ b/extensions/jaeger/runtime/pom.xml @@ -27,7 +27,7 @@ jaeger-thrift - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jaxb/runtime/pom.xml b/extensions/jaxb/runtime/pom.xml index 3d91533a1042d..7e4aa5bab9d3e 100644 --- a/extensions/jaxb/runtime/pom.xml +++ b/extensions/jaxb/runtime/pom.xml @@ -15,7 +15,7 @@ XML serialization support - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-derby/runtime/pom.xml b/extensions/jdbc/jdbc-derby/runtime/pom.xml index bab7a00fb7012..29909c78e29f6 100644 --- a/extensions/jdbc/jdbc-derby/runtime/pom.xml +++ b/extensions/jdbc/jdbc-derby/runtime/pom.xml @@ -22,7 +22,7 @@ derbyclient - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-h2/runtime/pom.xml b/extensions/jdbc/jdbc-h2/runtime/pom.xml index ef7f584445901..d15aeb177e264 100644 --- a/extensions/jdbc/jdbc-h2/runtime/pom.xml +++ b/extensions/jdbc/jdbc-h2/runtime/pom.xml @@ -28,7 +28,7 @@ --> - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-mariadb/runtime/pom.xml b/extensions/jdbc/jdbc-mariadb/runtime/pom.xml index 9e37c30953be4..550d0382e5d55 100644 --- a/extensions/jdbc/jdbc-mariadb/runtime/pom.xml +++ b/extensions/jdbc/jdbc-mariadb/runtime/pom.xml @@ -18,7 +18,7 @@ mariadb-java-client - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-mssql/runtime/pom.xml b/extensions/jdbc/jdbc-mssql/runtime/pom.xml index 59239e66d20ba..1bdebd9b64dea 100644 --- a/extensions/jdbc/jdbc-mssql/runtime/pom.xml +++ b/extensions/jdbc/jdbc-mssql/runtime/pom.xml @@ -66,7 +66,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-mysql/runtime/pom.xml b/extensions/jdbc/jdbc-mysql/runtime/pom.xml index 6f01b9ed12158..019f1e3be1161 100644 --- a/extensions/jdbc/jdbc-mysql/runtime/pom.xml +++ b/extensions/jdbc/jdbc-mysql/runtime/pom.xml @@ -22,7 +22,7 @@ mysql-connector-java - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jdbc/jdbc-postgresql/runtime/pom.xml b/extensions/jdbc/jdbc-postgresql/runtime/pom.xml index 30660ebf35a2a..208186fd9646b 100644 --- a/extensions/jdbc/jdbc-postgresql/runtime/pom.xml +++ b/extensions/jdbc/jdbc-postgresql/runtime/pom.xml @@ -18,7 +18,7 @@ postgresql - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java index b050ad6a1b1da..5bca38b966fb6 100644 --- a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java +++ b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java @@ -1,11 +1,15 @@ package io.quarkus.jgit.runtime.deployment; +import java.util.Arrays; +import java.util.List; + import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.jgit.runtime.PortWatcherRunTime; class JGitProcessor { @@ -84,8 +88,10 @@ ReflectiveClassBuildItem reflection() { } @BuildStep - RuntimeInitializedClassBuildItem lazyDigest() { - return new RuntimeInitializedClassBuildItem("org.eclipse.jgit.transport.HttpAuthMethod$Digest"); + List runtimeInitializedClasses() { + return Arrays.asList( + new RuntimeInitializedClassBuildItem("org.eclipse.jgit.transport.HttpAuthMethod$Digest"), + new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName())); } @BuildStep diff --git a/extensions/jgit/runtime/pom.xml b/extensions/jgit/runtime/pom.xml index 620dff6e6e1f5..86662c78b0c7e 100644 --- a/extensions/jgit/runtime/pom.xml +++ b/extensions/jgit/runtime/pom.xml @@ -15,7 +15,7 @@ Access your Git repositories - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java b/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java new file mode 100644 index 0000000000000..16e0c2d15cd43 --- /dev/null +++ b/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java @@ -0,0 +1,16 @@ +package io.quarkus.jgit.runtime; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class PortWatcherRunTime { + + public static InetAddress anyLocalAddress; + + static { + try { + anyLocalAddress = InetAddress.getByName("0.0.0.0"); + } catch (UnknownHostException e) { + } + } +} diff --git a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java b/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java new file mode 100644 index 0000000000000..f4123fcf39142 --- /dev/null +++ b/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java @@ -0,0 +1,27 @@ +package io.quarkus.jgit.runtime; + +import java.net.InetAddress; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.InjectAccessors; +import com.oracle.svm.core.annotate.TargetClass; + +/* + * The following substitution is required because of a new restriction in GraalVM 19.3.0 that prohibits the presence of + * java.net.Inet4Address in the image heap. Each field annotated with @InjectAccessors is lazily recomputed at runtime on first + * access while PortWatcher.class can still be initialized during the native image build. + */ +@TargetClass(className = "com.jcraft.jsch.PortWatcher") +final class Target_com_jcraft_jsch_PortWatcher { + + @Alias + @InjectAccessors(AnyLocalAddressAccessor.class) + private static InetAddress anyLocalAddress; +} + +final class AnyLocalAddressAccessor { + + static InetAddress get() { + return PortWatcherRunTime.anyLocalAddress; + } +} diff --git a/extensions/kafka-client/runtime/pom.xml b/extensions/kafka-client/runtime/pom.xml index 08e2a0fa2e26b..e6b5e61a7837b 100644 --- a/extensions/kafka-client/runtime/pom.xml +++ b/extensions/kafka-client/runtime/pom.xml @@ -35,7 +35,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/kafka-streams/runtime/pom.xml b/extensions/kafka-streams/runtime/pom.xml index df683d160a149..798b2c3e44040 100644 --- a/extensions/kafka-streams/runtime/pom.xml +++ b/extensions/kafka-streams/runtime/pom.xml @@ -31,7 +31,7 @@ kafka-streams - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java index 8ac327b114ba6..8f0743b6df84a 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java @@ -32,7 +32,9 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.keycloak.pep.runtime.PortWatcherRunTime; public class KeycloakReflectionBuildStep { @@ -76,4 +78,9 @@ public void registerServiceProviders(BuildProducer ser ClaimsInformationPointProviderFactory.class.getName())); } + + @BuildStep + RuntimeInitializedClassBuildItem runtimeInitializedClass() { + return new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName()); + } } diff --git a/extensions/keycloak-authorization/runtime/pom.xml b/extensions/keycloak-authorization/runtime/pom.xml index 9c3f164056aa0..98c45d9f6975e 100644 --- a/extensions/keycloak-authorization/runtime/pom.xml +++ b/extensions/keycloak-authorization/runtime/pom.xml @@ -44,6 +44,10 @@ org.jboss.logging commons-logging-jboss-logging + + + org.graalvm.nativeimage + svm junit diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java new file mode 100644 index 0000000000000..4bc9855cfdc15 --- /dev/null +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java @@ -0,0 +1,16 @@ +package io.quarkus.keycloak.pep.runtime; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class PortWatcherRunTime { + + public static InetAddress anyLocalAddress; + + static { + try { + anyLocalAddress = InetAddress.getByName("0.0.0.0"); + } catch (UnknownHostException e) { + } + } +} diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java new file mode 100644 index 0000000000000..eab55a8602d79 --- /dev/null +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java @@ -0,0 +1,27 @@ +package io.quarkus.keycloak.pep.runtime; + +import java.net.InetAddress; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.InjectAccessors; +import com.oracle.svm.core.annotate.TargetClass; + +/* + * The following substitution is required because of a new restriction in GraalVM 19.3.0 that prohibits the presence of + * java.net.Inet4Address in the image heap. Each field annotated with @InjectAccessors is lazily recomputed at runtime on first + * access while PortWatcher.class can still be initialized during the native image build. + */ +@TargetClass(className = "com.jcraft.jsch.PortWatcher") +final class Target_com_jcraft_jsch_PortWatcher { + + @Alias + @InjectAccessors(AnyLocalAddressAccessor.class) + private static InetAddress anyLocalAddress; +} + +final class AnyLocalAddressAccessor { + + static InetAddress get() { + return PortWatcherRunTime.anyLocalAddress; + } +} diff --git a/extensions/kubernetes-client/runtime/pom.xml b/extensions/kubernetes-client/runtime/pom.xml index bc22cb63ae6e7..78b66d499d4ea 100644 --- a/extensions/kubernetes-client/runtime/pom.xml +++ b/extensions/kubernetes-client/runtime/pom.xml @@ -23,7 +23,7 @@ quarkus-jackson - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml index 43e6567abf057..99ff8c86f87c5 100644 --- a/extensions/mongodb-client/runtime/pom.xml +++ b/extensions/mongodb-client/runtime/pom.xml @@ -37,7 +37,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/narayana-jta/runtime/pom.xml b/extensions/narayana-jta/runtime/pom.xml index cfe45f6a03c73..78b06a9212007 100644 --- a/extensions/narayana-jta/runtime/pom.xml +++ b/extensions/narayana-jta/runtime/pom.xml @@ -45,7 +45,7 @@ smallrye-reactive-converter-api - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/neo4j/runtime/pom.xml b/extensions/neo4j/runtime/pom.xml index a9fde15ef64ef..3aea33dd25baf 100644 --- a/extensions/neo4j/runtime/pom.xml +++ b/extensions/neo4j/runtime/pom.xml @@ -22,7 +22,7 @@ quarkus-arc - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/netty/runtime/pom.xml b/extensions/netty/runtime/pom.xml index e84ebb0050580..034b97477a8bc 100644 --- a/extensions/netty/runtime/pom.xml +++ b/extensions/netty/runtime/pom.xml @@ -36,7 +36,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/quartz/runtime/pom.xml b/extensions/quartz/runtime/pom.xml index af327d6c85612..9ad81a1c8642f 100644 --- a/extensions/quartz/runtime/pom.xml +++ b/extensions/quartz/runtime/pom.xml @@ -23,7 +23,7 @@ quarkus-scheduler - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/rest-client/runtime/pom.xml b/extensions/rest-client/runtime/pom.xml index e7cd65dde329e..2686ff5db481f 100644 --- a/extensions/rest-client/runtime/pom.xml +++ b/extensions/rest-client/runtime/pom.xml @@ -58,7 +58,7 @@ commons-logging-jboss-logging - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/resteasy-common/deployment/pom.xml b/extensions/resteasy-common/deployment/pom.xml index 7f04f1644cfac..fc9ea9c2961eb 100644 --- a/extensions/resteasy-common/deployment/pom.xml +++ b/extensions/resteasy-common/deployment/pom.xml @@ -31,7 +31,7 @@ quarkus-arc-deployment - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/resteasy-common/runtime/pom.xml b/extensions/resteasy-common/runtime/pom.xml index 12fdcd0bda0ce..6d553d8f62029 100644 --- a/extensions/resteasy-common/runtime/pom.xml +++ b/extensions/resteasy-common/runtime/pom.xml @@ -15,7 +15,7 @@ REST framework implementing JAX-RS and more - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/resteasy/deployment/pom.xml b/extensions/resteasy/deployment/pom.xml index 0c1f1e66a00da..ccb5aa8354c4f 100644 --- a/extensions/resteasy/deployment/pom.xml +++ b/extensions/resteasy/deployment/pom.xml @@ -39,7 +39,7 @@ quarkus-security-spi - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/security/runtime/pom.xml b/extensions/security/runtime/pom.xml index 40136711a0b3e..2b2a2dee44ce1 100644 --- a/extensions/security/runtime/pom.xml +++ b/extensions/security/runtime/pom.xml @@ -23,7 +23,7 @@ jakarta.interceptor-api - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-fault-tolerance/deployment/pom.xml b/extensions/smallrye-fault-tolerance/deployment/pom.xml index c96bd88afac5c..4927005c5d9f6 100644 --- a/extensions/smallrye-fault-tolerance/deployment/pom.xml +++ b/extensions/smallrye-fault-tolerance/deployment/pom.xml @@ -31,7 +31,7 @@ quarkus-smallrye-fault-tolerance - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-fault-tolerance/runtime/pom.xml b/extensions/smallrye-fault-tolerance/runtime/pom.xml index 8284aa06e61a6..53da9a7c4edd1 100644 --- a/extensions/smallrye-fault-tolerance/runtime/pom.xml +++ b/extensions/smallrye-fault-tolerance/runtime/pom.xml @@ -50,7 +50,7 @@ commons-logging-jboss-logging - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-opentracing/runtime/pom.xml b/extensions/smallrye-opentracing/runtime/pom.xml index 3b4b099c8491b..e3b9f8f823e94 100644 --- a/extensions/smallrye-opentracing/runtime/pom.xml +++ b/extensions/smallrye-opentracing/runtime/pom.xml @@ -57,7 +57,7 @@ jakarta.servlet-api - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml b/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml index 5ce1596f590f0..6ff8897875253 100644 --- a/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-amqp/runtime/pom.xml @@ -54,7 +54,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml b/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml index 4144091a790e7..2499bff2d963f 100644 --- a/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml @@ -66,7 +66,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/smallrye-reactive-messaging-mqtt/runtime/pom.xml b/extensions/smallrye-reactive-messaging-mqtt/runtime/pom.xml index fc6ca58cc3dc4..f47ec2dc9b344 100644 --- a/extensions/smallrye-reactive-messaging-mqtt/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-mqtt/runtime/pom.xml @@ -50,7 +50,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/undertow-websockets/runtime/pom.xml b/extensions/undertow-websockets/runtime/pom.xml index e4e727a4a4330..67e66d5deb9e6 100644 --- a/extensions/undertow-websockets/runtime/pom.xml +++ b/extensions/undertow-websockets/runtime/pom.xml @@ -16,7 +16,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/undertow/runtime/pom.xml b/extensions/undertow/runtime/pom.xml index 6465349bedb6c..b4bf23076f96b 100644 --- a/extensions/undertow/runtime/pom.xml +++ b/extensions/undertow/runtime/pom.xml @@ -37,7 +37,7 @@ jakarta.enterprise.cdi-api - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/vertx-core/runtime/pom.xml b/extensions/vertx-core/runtime/pom.xml index ceaf0211ebc63..ea903f40e0974 100644 --- a/extensions/vertx-core/runtime/pom.xml +++ b/extensions/vertx-core/runtime/pom.xml @@ -42,7 +42,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/extensions/vertx/runtime/pom.xml b/extensions/vertx/runtime/pom.xml index b86c2cb88766d..0b7164bb5495a 100644 --- a/extensions/vertx/runtime/pom.xml +++ b/extensions/vertx/runtime/pom.xml @@ -57,7 +57,7 @@ - com.oracle.substratevm + org.graalvm.nativeimage svm diff --git a/integration-tests/amazon-lambda-http-resteasy/pom.xml b/integration-tests/amazon-lambda-http-resteasy/pom.xml index ab618e48d1ff6..b51e592e0ea16 100644 --- a/integration-tests/amazon-lambda-http-resteasy/pom.xml +++ b/integration-tests/amazon-lambda-http-resteasy/pom.xml @@ -101,7 +101,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/amazon-lambda-http/pom.xml b/integration-tests/amazon-lambda-http/pom.xml index 640f7b8dff641..7cb5aca960cbb 100644 --- a/integration-tests/amazon-lambda-http/pom.xml +++ b/integration-tests/amazon-lambda-http/pom.xml @@ -109,7 +109,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/amazon-lambda/pom.xml b/integration-tests/amazon-lambda/pom.xml index 9282acbe209aa..6ff4481343577 100644 --- a/integration-tests/amazon-lambda/pom.xml +++ b/integration-tests/amazon-lambda/pom.xml @@ -98,7 +98,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/elytron-security-jdbc/pom.xml b/integration-tests/elytron-security-jdbc/pom.xml index 7b8a78e0aa3fa..62026c71fdbe6 100644 --- a/integration-tests/elytron-security-jdbc/pom.xml +++ b/integration-tests/elytron-security-jdbc/pom.xml @@ -105,7 +105,6 @@ false false ${graalvmHome} - true true false diff --git a/integration-tests/flyway/pom.xml b/integration-tests/flyway/pom.xml index c2a3ddaddab1b..6a075b6817098 100644 --- a/integration-tests/flyway/pom.xml +++ b/integration-tests/flyway/pom.xml @@ -116,7 +116,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/hibernate-orm-panache/pom.xml b/integration-tests/hibernate-orm-panache/pom.xml index 572f19c9d6447..59a7b99a44149 100644 --- a/integration-tests/hibernate-orm-panache/pom.xml +++ b/integration-tests/hibernate-orm-panache/pom.xml @@ -137,7 +137,6 @@ false false ${graalvmHome} - false diff --git a/integration-tests/hibernate-search-elasticsearch/pom.xml b/integration-tests/hibernate-search-elasticsearch/pom.xml index c607395b7cd09..bf3e55f93a6f7 100644 --- a/integration-tests/hibernate-search-elasticsearch/pom.xml +++ b/integration-tests/hibernate-search-elasticsearch/pom.xml @@ -185,7 +185,6 @@ false false ${graalvmHome} - false false diff --git a/integration-tests/hibernate-validator/pom.xml b/integration-tests/hibernate-validator/pom.xml index 780f306fd868e..be2e6674ed488 100644 --- a/integration-tests/hibernate-validator/pom.xml +++ b/integration-tests/hibernate-validator/pom.xml @@ -122,7 +122,6 @@ false false ${graalvmHome} - false diff --git a/integration-tests/infinispan-cache-jpa/pom.xml b/integration-tests/infinispan-cache-jpa/pom.xml index 1b4cd3ab0f320..f6098d8fb0e50 100644 --- a/integration-tests/infinispan-cache-jpa/pom.xml +++ b/integration-tests/infinispan-cache-jpa/pom.xml @@ -121,7 +121,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/infinispan-embedded/pom.xml b/integration-tests/infinispan-embedded/pom.xml index 4a6826aab74ff..61d1046f266e5 100644 --- a/integration-tests/infinispan-embedded/pom.xml +++ b/integration-tests/infinispan-embedded/pom.xml @@ -144,7 +144,6 @@ -H:ResourceConfigurationFiles=${project.basedir}/src/main/resources/resources-config.json ${graalvmHome} - true diff --git a/integration-tests/jpa-derby/pom.xml b/integration-tests/jpa-derby/pom.xml index 10f426d0137f8..08776dd2d570d 100644 --- a/integration-tests/jpa-derby/pom.xml +++ b/integration-tests/jpa-derby/pom.xml @@ -112,7 +112,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/jpa-h2/pom.xml b/integration-tests/jpa-h2/pom.xml index 23eaf40c1d472..df11bc035b75d 100644 --- a/integration-tests/jpa-h2/pom.xml +++ b/integration-tests/jpa-h2/pom.xml @@ -112,7 +112,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/jpa-mariadb/pom.xml b/integration-tests/jpa-mariadb/pom.xml index 8c3eb78e8778f..bff44413ef8d3 100644 --- a/integration-tests/jpa-mariadb/pom.xml +++ b/integration-tests/jpa-mariadb/pom.xml @@ -148,7 +148,6 @@ false false ${graalvmHome} - false false diff --git a/integration-tests/jpa-mssql/pom.xml b/integration-tests/jpa-mssql/pom.xml index 6c9e42aeb732c..32c2a8870138d 100644 --- a/integration-tests/jpa-mssql/pom.xml +++ b/integration-tests/jpa-mssql/pom.xml @@ -156,7 +156,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/jpa-mysql/pom.xml b/integration-tests/jpa-mysql/pom.xml index 8d9fe860e614f..5494541abb1f5 100644 --- a/integration-tests/jpa-mysql/pom.xml +++ b/integration-tests/jpa-mysql/pom.xml @@ -148,7 +148,6 @@ false false ${graalvmHome} - false false diff --git a/integration-tests/jpa-postgresql/pom.xml b/integration-tests/jpa-postgresql/pom.xml index f9a9a9ab2d07e..5ce6c7a13db6d 100644 --- a/integration-tests/jpa-postgresql/pom.xml +++ b/integration-tests/jpa-postgresql/pom.xml @@ -143,7 +143,6 @@ false false ${graalvmHome} - false diff --git a/integration-tests/jpa-without-entity/pom.xml b/integration-tests/jpa-without-entity/pom.xml index daaaacf861b4c..3e40b635a3e51 100644 --- a/integration-tests/jpa-without-entity/pom.xml +++ b/integration-tests/jpa-without-entity/pom.xml @@ -105,7 +105,6 @@ true true ${graalvmHome} - false false diff --git a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/integration-tests/itest/pom.xml b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/integration-tests/itest/pom.xml index 1ccd2892cbcb3..e5fb5b277fb17 100644 --- a/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/integration-tests/itest/pom.xml +++ b/integration-tests/maven/src/test/resources/expected/create-extension-pom-itest/integration-tests/itest/pom.xml @@ -99,7 +99,6 @@ false false ${graalvmHome} - true true false diff --git a/integration-tests/neo4j/README.md b/integration-tests/neo4j/README.md index 88824027335ca..a4f57b1ed4ac9 100644 --- a/integration-tests/neo4j/README.md +++ b/integration-tests/neo4j/README.md @@ -54,7 +54,6 @@ The Quarkus maven plugin must be configured like this: true true true - true diff --git a/integration-tests/vault-app/pom.xml b/integration-tests/vault-app/pom.xml index 1882ce4c3713d..95d6b6503c56e 100644 --- a/integration-tests/vault-app/pom.xml +++ b/integration-tests/vault-app/pom.xml @@ -172,7 +172,6 @@ false false ${graalvmHome} - false diff --git a/integration-tests/vertx-graphql/pom.xml b/integration-tests/vertx-graphql/pom.xml index e0394569c31cb..3e0e554a507f5 100644 --- a/integration-tests/vertx-graphql/pom.xml +++ b/integration-tests/vertx-graphql/pom.xml @@ -91,7 +91,6 @@ false false ${graalvmHome} - true true false diff --git a/integration-tests/vertx-http/pom.xml b/integration-tests/vertx-http/pom.xml index eef1a2e2f7c98..151c17b42100a 100644 --- a/integration-tests/vertx-http/pom.xml +++ b/integration-tests/vertx-http/pom.xml @@ -103,7 +103,6 @@ -H:EnableURLProtocols=http,https ${graalvmHome} - true true From bdf662bc3a434b3017303beea44aac225f97fef7 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 10:36:25 +1100 Subject: [PATCH 198/602] Register the password provider at runtime This prevents the Graal 19.3.0 NPE when checking the providers list. --- .../deployment/ElytronDeploymentProcessor.java | 6 ++++++ .../elytron/security/runtime/ElytronRecorder.java | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java index ad4e4475d89ed..5ae4dfb1eb313 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java @@ -98,6 +98,12 @@ SecurityDomainBuildItem build(ElytronRecorder recorder, List builder, String realmN * @return the security domain runtime value */ public RuntimeValue buildDomain(RuntimeValue builder) { - Security.addProvider(new WildFlyElytronPasswordProvider()); return new RuntimeValue<>(builder.getValue().build()); } + + /** + * As of Graal 19.3.0 this has to be registered at runtime, due to a bug. + * + * 19.3.1 should fix this, see https://github.com/oracle/graal/issues/1883 + */ + public void registerPasswordProvider() { + Security.addProvider(new WildFlyElytronPasswordProvider()); + } } From c9f56db1a707391bf55759cda69064cdc82311c0 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 10:58:26 +1100 Subject: [PATCH 199/602] Initialize GpgSigner at runtime to avoid security provider problem --- .../java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java index 5bca38b966fb6..4211b7a9c38c3 100644 --- a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java +++ b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java @@ -91,6 +91,7 @@ ReflectiveClassBuildItem reflection() { List runtimeInitializedClasses() { return Arrays.asList( new RuntimeInitializedClassBuildItem("org.eclipse.jgit.transport.HttpAuthMethod$Digest"), + new RuntimeInitializedClassBuildItem("org.eclipse.jgit.lib.GpgSigner"), new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName())); } From 7f2acfdd1fe723ebe9570052727eba28f3792eaa Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 4 Dec 2019 11:19:38 +1100 Subject: [PATCH 200/602] Make Keycloak work on Graal 19.3.0 Remove some problematic substitutions and make bouncycastle classes initialized at runtime to work around security provider bug --- ci-templates/stages.yml | 2 +- .../SecurityServicesFeatureSubstitutions.java | 61 ------------------- .../KeycloakReflectionBuildStep.java | 10 ++- .../keycloak-authorization/runtime/pom.xml | 4 -- .../pep/runtime/PortWatcherRunTime.java | 16 ----- .../pep/runtime/PortWatcherSubstitutions.java | 27 -------- 6 files changed, 8 insertions(+), 112 deletions(-) delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java delete mode 100644 extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java delete mode 100644 extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 00535cd13e19b..b46b308befa7b 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -345,7 +345,7 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 25 + timeoutInMinutes: 35 modules: - kogito - kubernetes-client diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java deleted file mode 100644 index 53a0af793c52f..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/graal/SecurityServicesFeatureSubstitutions.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.quarkus.runtime.graal; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.function.BooleanSupplier; - -import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.SecurityServicesFeature; - -/** - * @deprecated This class contains a workaround for a NPE that is thrown when a third-party security provider (meaning not one - * from the JDK) is used in Quarkus. A GraalVM issue has been created for it, the fix should be part of GraalVM - * 19.3.1. This class should be removed as soon as we integrate a GraalVM release that includes the fix.
- * See https://github.com/oracle/graal/issues/1883 for more details about the bug and the fix. - */ -@Deprecated -@TargetClass(value = SecurityServicesFeature.class, onlyWith = GraalVersion19_3_0.class) -final class Target_com_oracle_svm_hosted_SecurityServicesFeature { - - @Substitute - private static Class lambda$getConsParamClassAccessor$0(Map knownEngines, Field consParamClassNameField, - BeforeAnalysisAccess access, java.lang.String serviceType) { - try { - /* - * Access the Provider.knownEngines map and extract the EngineDescription - * corresponding to the serviceType. Note that the map holds EngineDescription(s) of - * only those service types that are shipped in the JDK. From the EngineDescription - * object extract the value of the constructorParameterClassName field then, if the - * class name is not null, get the corresponding Class object and return it. - */ - /* EngineDescription */Object engineDescription = knownEngines.get(serviceType); - /* - * This isn't an engine known to the Provider (which actually means that it isn't - * one that's shipped in the JDK), so we don't have the predetermined knowledge of - * the constructor param class. - */ - if (engineDescription == null) { - return null; - } - String constrParamClassName = (String) consParamClassNameField.get(engineDescription); - if (constrParamClassName != null) { - return access.findClassByName(constrParamClassName); - } - } catch (IllegalAccessException e) { - VMError.shouldNotReachHere(e); - } - return null; - - } -} - -final class GraalVersion19_3_0 implements BooleanSupplier { - public boolean getAsBoolean() { - final String version = System.getProperty("org.graalvm.version"); - return version.startsWith("19.3."); - } -} diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java index 8f0743b6df84a..584b3fd9e79a2 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakReflectionBuildStep.java @@ -34,7 +34,6 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.keycloak.pep.runtime.PortWatcherRunTime; public class KeycloakReflectionBuildStep { @@ -80,7 +79,12 @@ public void registerServiceProviders(BuildProducer ser } @BuildStep - RuntimeInitializedClassBuildItem runtimeInitializedClass() { - return new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName()); + public void runtimeInit(BuildProducer runtimeInit) { + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.BouncyIntegration")); + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.PemUtils")); + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.DerUtils")); + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.KeystoreUtil")); + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.CertificateUtils")); + runtimeInit.produce(new RuntimeInitializedClassBuildItem("org.keycloak.common.util.OCSPUtils")); } } diff --git a/extensions/keycloak-authorization/runtime/pom.xml b/extensions/keycloak-authorization/runtime/pom.xml index 98c45d9f6975e..9c3f164056aa0 100644 --- a/extensions/keycloak-authorization/runtime/pom.xml +++ b/extensions/keycloak-authorization/runtime/pom.xml @@ -44,10 +44,6 @@ org.jboss.logging commons-logging-jboss-logging - - - org.graalvm.nativeimage - svm junit diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java deleted file mode 100644 index 4bc9855cfdc15..0000000000000 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherRunTime.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.keycloak.pep.runtime; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class PortWatcherRunTime { - - public static InetAddress anyLocalAddress; - - static { - try { - anyLocalAddress = InetAddress.getByName("0.0.0.0"); - } catch (UnknownHostException e) { - } - } -} diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java deleted file mode 100644 index eab55a8602d79..0000000000000 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/PortWatcherSubstitutions.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.quarkus.keycloak.pep.runtime; - -import java.net.InetAddress; - -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.InjectAccessors; -import com.oracle.svm.core.annotate.TargetClass; - -/* - * The following substitution is required because of a new restriction in GraalVM 19.3.0 that prohibits the presence of - * java.net.Inet4Address in the image heap. Each field annotated with @InjectAccessors is lazily recomputed at runtime on first - * access while PortWatcher.class can still be initialized during the native image build. - */ -@TargetClass(className = "com.jcraft.jsch.PortWatcher") -final class Target_com_jcraft_jsch_PortWatcher { - - @Alias - @InjectAccessors(AnyLocalAddressAccessor.class) - private static InetAddress anyLocalAddress; -} - -final class AnyLocalAddressAccessor { - - static InetAddress get() { - return PortWatcherRunTime.anyLocalAddress; - } -} From 90dfbf73f0283c6ac09cbb659a128d35138fce9a Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 5 Dec 2019 16:08:17 +1100 Subject: [PATCH 201/602] Quarkus Security 1.0.1.Final --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index ffbe561fbe4e3..32eff33ff677c 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -170,7 +170,7 @@ 3.0.0 5.3.1 4.7.2 - 1.0.0.Final + 1.0.1.Final 8.0.1 From 37a8898455059cc301bab5761c6d6c1b8756ad40 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Thu, 5 Dec 2019 08:01:13 +0100 Subject: [PATCH 202/602] Fix: Add extension block --- docs/src/main/asciidoc/kubernetes-client.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/kubernetes-client.adoc b/docs/src/main/asciidoc/kubernetes-client.adoc index 7e74ce209034f..6972a04acf030 100644 --- a/docs/src/main/asciidoc/kubernetes-client.adoc +++ b/docs/src/main/asciidoc/kubernetes-client.adoc @@ -22,7 +22,9 @@ Once you have your Quarkus project configured you can add the `kubernetes-client to your project by running the following command in your project base directory. [source] +---- ./mvnw quarkus:add-extension -Dextensions="kubernetes-client" +---- This will add the following to your pom.xml: From 2123cdec2c120a37aca95a6ba1ef804f42a86351 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 5 Dec 2019 11:08:40 +0100 Subject: [PATCH 203/602] CDI ref guide - document eager instantiation of beans --- docs/src/main/asciidoc/cdi-reference.adoc | 83 +++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 4116fcdcd3fe6..eda96eefe6117 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -168,6 +168,89 @@ public class CounterBean { == Non-standard Features +=== Eager Instantiation of Beans + +[[lazy_by_default]] +==== Lazy By Default + +By default, CDI beans are created lazily, when needed. +What exactly "needed" means depends on the scope of a bean. + +* A *normal scoped bean* (`@ApplicationScoped`, `@RequestScoped`, etc.) is needed when a method is invoked upon an injected instance (contextual reference per the specification). ++ +In other words, injecting a normal scoped bean will not suffice because a _client proxy_ is injected instead of a contextual instance of the bean. + +* A *bean with a pseudo-scope* (`@Dependent` and `@Singleton` ) is created when injected. + +.Lazy Instantiation Example +[source,java] +---- +@Singleton // => pseudo-scope +class AmazingService { + String ping() { + return "amazing"; + } +} + +@ApplicationScoped // => normal scope +class CoolService { + String ping() { + return "cool"; + } +} + +@Path("/ping") +public class PingResource { + + @Inject + AmazingService s1; <1> + + @Inject + CoolService s2; <2> + + @GET + public String ping() { + return s1.ping() + s2.ping(); <3> + } +} +---- +<1> Injection triggers the instantiation of `AmazingService`. +<2> Injection itself does not result in the instantiation of `CoolService`. A client proxy is injected. +<3> The first invocation upon the injected proxy triggers the instantiation of `CoolService`. + +==== Startup Event + +However, if you really need to instantiate a bean eagerly you can: + +* Declare an observer of the `StartupEvent` - the scope of the bean does not matter in this case: ++ +[source,java] +---- +@ApplicationScoped +class CoolService { + void startup(@Observes StartupEvent event) { <1> + } +} +---- +<1> A `CoolService` is created during startup to service the observer method invocation. + +* Use the bean in an observer of the `StartupEvent` - normal scoped beans must be used as described in <>: ++ +[source,java] +---- +@Dependent +class MyBeanStarter { + + void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { <1> + cool.toString(); <2> + } +} +---- +<1> The `AmazingService` is created during injection. +<2> The `CoolService` is a normal scoped bean so we have to invoke a method upon the injected proxy to force the instantiation. + +NOTE: Quarkus users are encouraged to always prefer the `@Observes StartupEvent` to `@Initialized(ApplicationScoped.class)` as explained in the link:lifecycle[Application Initialization and Termination] guide. + === Qualified Injected Fields In CDI, if you declare a field injection point you need to use `@Inject` and optionally a set of qualifiers: From 6600eaaba07df15c6091cc0c56d3bd34def7ce0c Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 4 Dec 2019 09:39:34 +0100 Subject: [PATCH 204/602] MainClassBuildStep - log error when app fails to start - remove Throwable.printStackTrace() --- .../deployment/steps/MainClassBuildStep.java | 13 ++++++++++++- .../main/java/io/quarkus/test/QuarkusUnitTest.java | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 04927872b1312..f86ac81b482e0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageInfo; +import org.jboss.logging.Logger; import io.quarkus.builder.Version; import io.quarkus.deployment.GeneratedClassGizmoAdaptor; @@ -58,6 +59,7 @@ class MainClassBuildStep { private static final String APP_CLASS = "io.quarkus.runner.ApplicationImpl"; private static final String MAIN_CLASS = "io.quarkus.runner.GeneratedMain"; private static final String STARTUP_CONTEXT = "STARTUP_CONTEXT"; + private static final String LOG = "LOG"; private static final String JAVA_LIBRARY_PATH = "java.library.path"; private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; @@ -101,6 +103,9 @@ MainClassBuildItem build(List staticInitTasks, // Application class: static init + // LOG static field + FieldCreator logField = file.getFieldCreator(LOG, Logger.class).setModifiers(Modifier.STATIC); + FieldCreator scField = file.getFieldCreator(STARTUP_CONTEXT, StartupContext.class); scField.setModifiers(Modifier.STATIC); @@ -118,6 +123,10 @@ MainClassBuildItem build(List staticInitTasks, // ensure that the config class is initialized mv.invokeStaticMethod(RunTimeConfigurationGenerator.C_ENSURE_INITIALIZED); + // Init the LOG instance + mv.writeStaticField(logField.getFieldDescriptor(), mv.invokeStaticMethod( + ofMethod(Logger.class, "getLogger", Logger.class, String.class), mv.load("io.quarkus.application"))); + ResultHandle startupContext = mv.newInstance(ofConstructor(StartupContext.class)); mv.writeStaticField(scField.getFieldDescriptor(), startupContext); TryBlock tryBlock = mv.tryBlock(); @@ -235,7 +244,9 @@ MainClassBuildItem build(List staticInitTasks, tryBlock.load(LaunchMode.DEVELOPMENT.equals(launchMode.getLaunchMode()))); cb = tryBlock.addCatch(Throwable.class); - cb.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cb.getCaughtException()); + cb.invokeVirtualMethod(ofMethod(Logger.class, "error", void.class, Object.class, Throwable.class), + cb.readStaticField(logField.getFieldDescriptor()), cb.load("Failed to start application"), + cb.getCaughtException()); cb.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext); cb.throwException(RuntimeException.class, "Failed to start quarkus", cb.getCaughtException()); mv.returnValue(null); diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java index 0c10e03324758..9957eab9fc1df 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java @@ -311,10 +311,10 @@ public void execute(BuildContext context) { } else if (cause != null) { assertException.accept(cause); } else { - fail("Unable to unwrap build exception from: " + e); + fail("Unable to unwrap the build exception from: " + e); } } else { - fail("Unable to unwrap build exception from: " + e); + fail("Unable to unwrap the build exception from: " + e); } } else { throw e; From 79233f95c67ad114cc6f077f2cdf2dd8c8f09a30 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 5 Dec 2019 13:31:56 +0000 Subject: [PATCH 205/602] Document new requirements to build on Fedora --- docs/src/main/asciidoc/building-native-image.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index 355182168237a..beb1707172bfe 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -37,7 +37,7 @@ What does having a working C developer environment mean? [source,shell] ---- # dnf (rpm-based) -sudo dnf install gcc glibc-devel zlib-devel +sudo dnf install gcc glibc-devel zlib-devel libstdc++-static # Debian-based distributions: sudo apt-get install build-essential libz-dev zlib1g-dev ---- From be68469a34eaccf0bf5739da37f7fad43cf201f9 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 27 Nov 2019 18:03:48 +0000 Subject: [PATCH 206/602] Throwing ForbiddenException if OidcUtils.findRoles throws an exception --- .../security/runtime/ElytronPasswordIdentityProvider.java | 2 +- .../security/runtime/ElytronTokenIdentityProvider.java | 2 +- .../security/runtime/ElytronTrustedIdentityProvider.java | 2 +- .../java/io/quarkus/oidc/runtime/OidcIdentityProvider.java | 5 +++-- .../src/main/java/io/quarkus/oidc/runtime/OidcUtils.java | 6 +++--- .../quarkus/smallrye/jwt/runtime/auth/MpJwtValidator.java | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPasswordIdentityProvider.java b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPasswordIdentityProvider.java index 2384973ac4bad..eb673653c1cc4 100644 --- a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPasswordIdentityProvider.java +++ b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPasswordIdentityProvider.java @@ -62,7 +62,7 @@ public SecurityIdentity get() { throw new RuntimeException(e); } catch (SecurityException e) { log.debug("Authentication failed", e); - throw new AuthenticationFailedException(); + throw new AuthenticationFailedException(e); } } }); diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTokenIdentityProvider.java b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTokenIdentityProvider.java index 29f9fe6a91ba9..fff7378522aed 100644 --- a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTokenIdentityProvider.java +++ b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTokenIdentityProvider.java @@ -61,7 +61,7 @@ public SecurityIdentity get() { throw new RuntimeException(e); } catch (SecurityException e) { log.debug("Authentication failed", e); - throw new AuthenticationFailedException(); + throw new AuthenticationFailedException(e); } } }); diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTrustedIdentityProvider.java b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTrustedIdentityProvider.java index fdc1b45333997..9137eee499be3 100644 --- a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTrustedIdentityProvider.java +++ b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronTrustedIdentityProvider.java @@ -70,7 +70,7 @@ public SecurityIdentity get() { throw new RuntimeException(e); } catch (SecurityException e) { log.debug("Authentication failed", e); - throw new AuthenticationFailedException(); + throw new AuthenticationFailedException(e); } } }); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 3acc3f79df4aa..929c41b9df181 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -10,6 +10,7 @@ import org.jose4j.jwt.consumer.InvalidJwtException; import io.quarkus.security.AuthenticationFailedException; +import io.quarkus.security.ForbiddenException; import io.quarkus.security.identity.AuthenticationRequestContext; import io.quarkus.security.identity.IdentityProvider; import io.quarkus.security.identity.SecurityIdentity; @@ -64,7 +65,7 @@ public void handle(AsyncResult event) { try { jwtPrincipal = new OidcJwtCallerPrincipal(JwtClaims.parse(token.accessToken().encode())); } catch (InvalidJwtException e) { - result.completeExceptionally(e); + result.completeExceptionally(new AuthenticationFailedException(e)); return; } builder.setPrincipal(jwtPrincipal); @@ -74,7 +75,7 @@ public void handle(AsyncResult event) { builder.addRole(role); } } catch (Exception e) { - result.completeExceptionally(e); + result.completeExceptionally(new ForbiddenException(e)); return; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index 450549d7cd066..a927a3174c055 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -11,11 +11,12 @@ import io.vertx.core.json.JsonObject; public final class OidcUtils { + private OidcUtils() { } - public static List findRoles(String clientId, OidcConfig.Roles rolesConfig, JsonObject json) throws Exception { + public static List findRoles(String clientId, OidcConfig.Roles rolesConfig, JsonObject json) { // If the user configured a specific path - check and enforce a claim at this path exists if (rolesConfig.getRoleClaimPath().isPresent()) { return findClaimWithRoles(rolesConfig, rolesConfig.getRoleClaimPath().get(), json, true); @@ -57,8 +58,7 @@ private static Object findClaimValue(String claimPath, JsonObject json, String[] Object claimValue = json.getValue(pathArray[step]); if (claimValue == null) { if (mustExist) { - throw new OIDCException( - "No claim exists at the path " + claimPath + " at the path segment " + pathArray[step]); + throw new OIDCException("No claim exists at the path " + claimPath + " at the path segment " + pathArray[step]); } } else if (step + 1 < pathArray.length) { if (claimValue instanceof JsonObject) { diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtValidator.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtValidator.java index 53c140ec8f5ae..a15f31adf86a9 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtValidator.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtValidator.java @@ -71,7 +71,7 @@ public CompletionStage authenticate(TokenAuthenticationRequest } catch (ParseException | MalformedClaimException e) { log.debug("Authentication failed", e); CompletableFuture cf = new CompletableFuture(); - cf.completeExceptionally(new AuthenticationFailedException()); + cf.completeExceptionally(new AuthenticationFailedException(e)); return cf; } } From 8ff719ac6a07d0cdb83b755c942191dd93a3d36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 3 Dec 2019 10:36:00 +0100 Subject: [PATCH 207/602] fix: move to RUNTIME_INIT all security related build items --- .../deployment/ElytronSecurityJdbcProcessor.java | 2 +- .../deployment/OAuth2DeploymentProcessor.java | 6 ++---- .../security/oauth2/runtime/OAuth2Recorder.java | 10 ++-------- .../deployment/ElytronPropertiesProcessor.java | 15 ++++++++++----- .../deployment/ElytronDeploymentProcessor.java | 4 ++-- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/extensions/elytron-security-jdbc/deployment/src/main/java/io/quarkus/elytron/security/jdbc/deployment/ElytronSecurityJdbcProcessor.java b/extensions/elytron-security-jdbc/deployment/src/main/java/io/quarkus/elytron/security/jdbc/deployment/ElytronSecurityJdbcProcessor.java index 4237f0289cb66..20a8fe922bae2 100644 --- a/extensions/elytron-security-jdbc/deployment/src/main/java/io/quarkus/elytron/security/jdbc/deployment/ElytronSecurityJdbcProcessor.java +++ b/extensions/elytron-security-jdbc/deployment/src/main/java/io/quarkus/elytron/security/jdbc/deployment/ElytronSecurityJdbcProcessor.java @@ -46,7 +46,7 @@ FeatureBuildItem feature() { * @throws Exception - on any failure */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) void configureJdbcRealmAuthConfig(JdbcRecorder recorder, BuildProducer securityRealm, BeanContainerBuildItem beanContainerBuildItem, //we need this to make sure ArC is initialized diff --git a/extensions/elytron-security-oauth2/deployment/src/main/java/io/quarkus/elytron/security/oauth2/deployment/OAuth2DeploymentProcessor.java b/extensions/elytron-security-oauth2/deployment/src/main/java/io/quarkus/elytron/security/oauth2/deployment/OAuth2DeploymentProcessor.java index 86604682a809d..fb3af86bf319c 100644 --- a/extensions/elytron-security-oauth2/deployment/src/main/java/io/quarkus/elytron/security/oauth2/deployment/OAuth2DeploymentProcessor.java +++ b/extensions/elytron-security-oauth2/deployment/src/main/java/io/quarkus/elytron/security/oauth2/deployment/OAuth2DeploymentProcessor.java @@ -1,7 +1,5 @@ package io.quarkus.elytron.security.oauth2.deployment; -import java.util.function.Supplier; - import javax.enterprise.context.ApplicationScoped; import org.wildfly.security.auth.server.SecurityRealm; @@ -60,7 +58,7 @@ ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { * @throws Exception - on any failure */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) AdditionalBeanBuildItem configureOauth2RealmAuthConfig(OAuth2Recorder recorder, BuildProducer securityRealm) throws Exception { if (oauth2.enabled) { @@ -84,7 +82,7 @@ ElytronTokenMarkerBuildItem marker() { RuntimeBeanBuildItem augmentor(OAuth2Recorder recorder) { return RuntimeBeanBuildItem.builder(SecurityIdentityAugmentor.class) .setScope(ApplicationScoped.class) - .setSupplier((Supplier) recorder.augmentor(oauth2)) + .setRuntimeValue(recorder.augmentor(oauth2)) .setRemovable(false) .build(); } diff --git a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java index e9475d95e775d..6e4101c005308 100644 --- a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java +++ b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java @@ -13,7 +13,6 @@ import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; -import java.util.function.Supplier; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; @@ -85,13 +84,8 @@ private SSLContext createSSLContext(OAuth2Config config) } } - public Supplier augmentor(OAuth2Config config) { - return new Supplier() { - @Override - public OAuth2Augmentor get() { - return new OAuth2Augmentor(config.roleClaim); - } - }; + public RuntimeValue augmentor(OAuth2Config config) { + return new RuntimeValue<>(new OAuth2Augmentor(config.roleClaim)); } } diff --git a/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java b/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java index 2f77784698364..5bc983551e649 100644 --- a/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java +++ b/extensions/elytron-security-properties-file/deployment/src/main/java/io/quarkus/elytron/security/properties/deployment/ElytronPropertiesProcessor.java @@ -52,14 +52,13 @@ FeatureBuildItem feature() { * to include the build artifact. * * @param recorder - runtime security recorder - * @param resources - NativeImageResourceBuildItem used to register the realm user/roles properties files names. * @param securityRealm - the producer factory for the SecurityRealmBuildItem * @return the AuthConfigBuildItem for the realm authentication mechanism if there was an enabled PropertiesRealmConfig, * null otherwise * @throws Exception - on any failure */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) void configureFileRealmAuthConfig(ElytronPropertiesFileRecorder recorder, BuildProducer resources, BuildProducer securityRealm) throws Exception { @@ -67,8 +66,6 @@ void configureFileRealmAuthConfig(ElytronPropertiesFileRecorder recorder, PropertiesRealmConfig realmConfig = propertiesConfig.file; log.debugf("Configuring from PropertiesRealmConfig, users=%s, roles=%s", realmConfig.users, realmConfig.roles); - // Add the users/roles properties files resource names to build artifact - resources.produce(new NativeImageResourceBuildItem(realmConfig.users, realmConfig.roles)); // Have the runtime recorder create the LegacyPropertiesSecurityRealm and create the build item RuntimeValue realm = recorder.createRealm(realmConfig); securityRealm @@ -77,6 +74,14 @@ void configureFileRealmAuthConfig(ElytronPropertiesFileRecorder recorder, } } + @BuildStep + void nativeResource(BuildProducer resources) throws Exception { + if (propertiesConfig.file.enabled) { + PropertiesRealmConfig realmConfig = propertiesConfig.file; + resources.produce(new NativeImageResourceBuildItem(realmConfig.users, realmConfig.roles)); + } + } + @BuildStep ElytronPasswordMarkerBuildItem marker() { if (propertiesConfig.file.enabled || propertiesConfig.embedded.enabled) { @@ -97,7 +102,7 @@ ElytronPasswordMarkerBuildItem marker() { * @throws Exception - on any failure */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) void configureMPRealmConfig(ElytronPropertiesFileRecorder recorder, BuildProducer securityRealm) throws Exception { if (propertiesConfig.embedded.enabled) { diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java index 5ae4dfb1eb313..be3e6efc3adff 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/ElytronDeploymentProcessor.java @@ -75,7 +75,7 @@ void addBeans(BuildProducer beans, List realms) throws Exception { if (realms.size() > 0) { @@ -105,7 +105,7 @@ public void registerPasswordProvider(ElytronRecorder recorder) { } @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) void identityManager(ElytronRecorder recorder, SecurityDomainBuildItem securityDomain, BeanContainerBuildItem bc) { if (securityDomain != null) { recorder.setDomainForIdentityProvider(bc.getValue(), securityDomain.getSecurityDomain()); From ea5edcd1d2a32a53e4a44bf02feb6309222708fa Mon Sep 17 00:00:00 2001 From: Justin Holmes Date: Thu, 5 Dec 2019 14:57:05 -0700 Subject: [PATCH 208/602] update directory for kotlin guide maven archetype --- docs/src/main/asciidoc/kotlin.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index 8c4ef69dffad4..e63a50e05f278 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -41,7 +41,7 @@ mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DclassName="org.acme.rest.GreetingResource" \ -Dpath="/greeting" \ -Dextensions="kotlin,resteasy-jsonb" -cd kotlin-quickstart +cd rest-kotlin-quickstart ---- When adding `kotlin` to the extensions list, the Maven plugin will generate a project that is properly From 293135c389305341655272eb3e6fd2fcce87221e Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Thu, 5 Dec 2019 14:48:08 +0100 Subject: [PATCH 209/602] Sentry tests - getSentryHandler method --- .../sentry/SentryLoggerCustomTest.java | 24 ++----------------- .../logging/sentry/SentryLoggerTest.java | 8 +++++-- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java index 43a0d912caef3..7e5a01186309a 100644 --- a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java @@ -1,22 +1,14 @@ package io.quarkus.logging.sentry; +import static io.quarkus.logging.sentry.SentryLoggerTest.getSentryHandler; import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; - -import org.jboss.logmanager.handlers.DelayedHandler; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; -import io.sentry.jul.SentryHandler; import io.sentry.jvmti.FrameCache; public class SentryLoggerCustomTest { @@ -27,19 +19,7 @@ public class SentryLoggerCustomTest { @Test public void sentryLoggerCustomTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof SentryHandler)) - .findFirst().orElse(null); - SentryHandler sentryHandler = (SentryHandler) handler; - assertThat(sentryHandler).isNotNull(); - assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); + assertThat(getSentryHandler().getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isTrue(); } diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java index 805a9bb7c83a1..a43b82ca08301 100644 --- a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java @@ -27,6 +27,11 @@ public class SentryLoggerTest { @Test public void sentryLoggerDefaultTest() { + assertThat(getSentryHandler().getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); + assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isFalse(); + } + + public static SentryHandler getSentryHandler() { LogManager logManager = LogManager.getLogManager(); assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); @@ -39,8 +44,7 @@ public void sentryLoggerDefaultTest() { .findFirst().orElse(null); SentryHandler sentryHandler = (SentryHandler) handler; assertThat(sentryHandler).isNotNull(); - assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); - assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isFalse(); + return sentryHandler; } @AfterAll From b13778e7525e48cc0d63642d22226f58cc718e82 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 5 Dec 2019 20:44:20 +0000 Subject: [PATCH 210/602] Upgrade to Hibernate ORM 5.4.10.Final --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 32eff33ff677c..02095903a9cff 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -82,7 +82,7 @@ 1.13 1.3.4 6.1.0.Final - 5.4.9.Final + 5.4.10.Final 6.0.0.Beta2 5.10.0.Final 1.1.1.Final From 2acc4433cbb446de899e4f92e41227f6e7d01d57 Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Wed, 4 Dec 2019 14:50:08 +0100 Subject: [PATCH 211/602] Use ApplicationScoped and DefaultBean for Artemis ServerLocator and ConnectionFactory --- .../quarkus/artemis/core/runtime/ArtemisCoreProducer.java | 6 +++++- .../quarkus/artemis/jms/runtime/ArtemisJmsProducer.java | 5 ++++- .../quarkus/spring/di/deployment/SpringDIProcessor.java | 8 ++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/ArtemisCoreProducer.java b/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/ArtemisCoreProducer.java index 44fa7a1d580d6..6ee05aeb1d237 100644 --- a/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/ArtemisCoreProducer.java +++ b/extensions/artemis-core/runtime/src/main/java/io/quarkus/artemis/core/runtime/ArtemisCoreProducer.java @@ -6,13 +6,17 @@ import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ServerLocator; +import io.quarkus.arc.DefaultBean; + @ApplicationScoped public class ArtemisCoreProducer { private ArtemisRuntimeConfig config; @Produces - public ServerLocator produceServerLocator() throws Exception { + @ApplicationScoped + @DefaultBean + public ServerLocator serverLocator() throws Exception { return ActiveMQClient.createServerLocator(config.url); } diff --git a/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/ArtemisJmsProducer.java b/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/ArtemisJmsProducer.java index a02a085871013..7e4748c7a170b 100644 --- a/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/ArtemisJmsProducer.java +++ b/extensions/artemis-jms/runtime/src/main/java/io/quarkus/artemis/jms/runtime/ArtemisJmsProducer.java @@ -6,6 +6,7 @@ import org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory; +import io.quarkus.arc.DefaultBean; import io.quarkus.artemis.core.runtime.ArtemisRuntimeConfig; @ApplicationScoped @@ -14,7 +15,9 @@ public class ArtemisJmsProducer { private ArtemisRuntimeConfig config; @Produces - public ConnectionFactory producesConnectionFactory() { + @ApplicationScoped + @DefaultBean + public ConnectionFactory connectionFactory() { return new ActiveMQJMSConnectionFactory(config.url, config.username.orElse(null), config.password.orElse(null)); } diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index 3ffe605bdc2e1..7fd9463ccce3e 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -199,7 +199,7 @@ Map> getStereotypeScopes(final IndexView index) { * * @param target The annotated element declaring the @Scope * @return A CDI built in (or session) scope that mostly matches - * the spring one. Websocket scope is currently mapped to @Dependant + * the spring one. Websocket scope is currently mapped to @Dependent * and spring custom scopes are not currently handled. */ private DotName getScope(final AnnotationTarget target) { @@ -270,7 +270,7 @@ private void visitAnnotation(final DotName clazz, final IndexView index, final M /** * Map spring annotations from an annotated class to equivalent CDI annotations - * + * * @param target The annotated class * @param stereotypeScopes A map on spring stereotype classes to all the scopes * they, or any of their stereotypes (etc) declare @@ -547,7 +547,7 @@ private static String determineName(AnnotationValue annotationValue) { /** * Get a single scope from the available options or throw a {@link DefinitionException} explaining * where the annotations conflict. - * + * * @param clazz The class annotated with the scopes * @param scopes The scopes from the class and its stereotypes * @param scopeStereotypes The stereotype annotations that declared the conflicting scopes @@ -574,7 +574,7 @@ private DotName validateScope(final ClassInfo clazz, final Set scopes, /** * Get the name of a bean or throw a {@link DefinitionException} if it has more than one name - * + * * @param clazz The class annotated with the names * @param names The names * @return The bane name From 222f4f235e402792a16293e70b0c11c822a036c4 Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Thu, 5 Dec 2019 20:50:57 +0100 Subject: [PATCH 212/602] Add ArtemisConfiguredBuildItem after ArtemisProducer is set up --- .../core/deployment/ArtemisCoreConfiguredBuildItem.java | 9 +++++++++ .../artemis/core/deployment/ArtemisCoreProcessor.java | 5 +++-- .../jms/deployment/ArtemisJmsConfiguredBuildItem.java | 9 +++++++++ .../artemis/jms/deployment/ArtemisJmsProcessor.java | 3 ++- 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreConfiguredBuildItem.java create mode 100644 extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsConfiguredBuildItem.java diff --git a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreConfiguredBuildItem.java b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreConfiguredBuildItem.java new file mode 100644 index 0000000000000..164dcf04a187e --- /dev/null +++ b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreConfiguredBuildItem.java @@ -0,0 +1,9 @@ +package io.quarkus.artemis.core.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Marker build item indicating that Artemis Core is configured + */ +public final class ArtemisCoreConfiguredBuildItem extends SimpleBuildItem { +} diff --git a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java index 5066119485db0..0d6fdcd4c7d24 100644 --- a/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java +++ b/extensions/artemis-core/deployment/src/main/java/io/quarkus/artemis/core/deployment/ArtemisCoreProcessor.java @@ -103,12 +103,13 @@ void load(BuildProducer additionalBean, BuildProducer artemisJms) { if (artemisJms.isPresent()) { - return; + return null; } recorder.setConfig(runtimeConfig, beanContainer.getValue()); + return new ArtemisCoreConfiguredBuildItem(); } } diff --git a/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsConfiguredBuildItem.java b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsConfiguredBuildItem.java new file mode 100644 index 0000000000000..288693fc7ee88 --- /dev/null +++ b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsConfiguredBuildItem.java @@ -0,0 +1,9 @@ +package io.quarkus.artemis.jms.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Marker build item indicating that Artemis JMS is configured + */ +public final class ArtemisJmsConfiguredBuildItem extends SimpleBuildItem { +} diff --git a/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java index 34d1e1c6f33a2..5108472cf9043 100644 --- a/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java +++ b/extensions/artemis-jms/deployment/src/main/java/io/quarkus/artemis/jms/deployment/ArtemisJmsProcessor.java @@ -34,9 +34,10 @@ HealthBuildItem health(ArtemisBuildTimeConfig buildConfig) { @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - void configure(ArtemisJmsRecorder recorder, ArtemisRuntimeConfig runtimeConfig, + ArtemisJmsConfiguredBuildItem configure(ArtemisJmsRecorder recorder, ArtemisRuntimeConfig runtimeConfig, BeanContainerBuildItem beanContainer) { recorder.setConfig(runtimeConfig, beanContainer.getValue()); + return new ArtemisJmsConfiguredBuildItem(); } } From 17d2dc84d19878ad37653f8eb204f1afe89669ff Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 09:47:30 +0100 Subject: [PATCH 213/602] Fix minor formatting issue in building-native-image guide --- docs/src/main/asciidoc/building-native-image.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/building-native-image.adoc b/docs/src/main/asciidoc/building-native-image.adoc index beb1707172bfe..73ccb5df7fb49 100644 --- a/docs/src/main/asciidoc/building-native-image.adoc +++ b/docs/src/main/asciidoc/building-native-image.adoc @@ -102,6 +102,7 @@ export PATH=${GRAALVM_HOME}/bin:$PATH ==== GraalVM binaries are not (yet) notarized for macOS Catalina as reported in this https://github.com/oracle/graal/issues/1724[GraalVM issue]. This means that you may see the following error when using `gu`: +[source,shell] ---- “gu” cannot be opened because the developer cannot be verified ---- From e88bb4edf89785ce7ecf5ba1e5b888ee2dea31c8 Mon Sep 17 00:00:00 2001 From: Vinicius Ferraz Date: Mon, 25 Nov 2019 14:01:41 -0300 Subject: [PATCH 214/602] PanacheEntityBase update entities method --- .../main/asciidoc/hibernate-orm-panache.adoc | 14 +- .../orm/panache/PanacheEntityBase.java | 43 ++++++ .../orm/panache/PanacheRepositoryBase.java | 42 +++++ .../hibernate/orm/panache/package-info.java | 12 +- .../orm/panache/runtime/JpaOperations.java | 49 ++++++ .../io/quarkus/it/panache/TestEndpoint.java | 143 ++++++++++++++++++ 6 files changed, 300 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/hibernate-orm-panache.adoc b/docs/src/main/asciidoc/hibernate-orm-panache.adoc index dd8937addfdbb..dbd992ed01540 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache.adoc @@ -179,6 +179,10 @@ Person.delete("status", Status.Alive); // delete all persons Person.deleteAll(); + +// update all living persons +Person.update("name = 'Moral' where status = ?1", Status.Alive); + ---- All `list` methods have equivalent `stream` versions. @@ -286,21 +290,27 @@ public class Person extends PanacheEntity { Normally, HQL queries are of this form: `from EntityName [where ...] [order by ...]`, with optional elements at the end. -If your query does not start with `from`, we support the following additional forms: +If your select query does not start with `from`, we support the following additional forms: - `order by ...` which will expand to `from EntityName order by ...` - `` (and single parameter) which will expand to `from EntityName where = ?` - `` will expand to `from EntityName where ` +If your update query does not start with `update`, we support the following additional forms: + +- `from EntityName ...` which will expand to `update from EntityName ...` +- `set? ` (and single parameter) which will expand to `update from EntityName set = ?` +- `set? ` will expand to `update from EntityName set ` + NOTE: You can also write your queries in plain link:https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql[HQL]: [source,java] ---- Order.find("select distinct o from Order o left join fetch o.lineItems"); +Order.update("update from Person set name = 'Moral' where status = ?", Status.Alive); ---- - == Query parameters You can pass query parameters by index (1-based) as shown below: diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java index 7cc6b94713523..362efb9aaa921 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntityBase.java @@ -718,4 +718,47 @@ public static void persist(Stream entities) { public static void persist(Object firstEntity, Object... entities) { JpaOperations.persist(firstEntity, entities); } + + /** + * Update all entities of this type matching the given query, with mandatory indexed parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities updated. + * @see #update(String, Map) + * @see #update(String, Parameters) + */ + @GenerateBridge + public static int update(String query, Object... params) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Update all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities updated. + * @see #update(String, Object...) + * @see #update(String, Parameters) + * + */ + @GenerateBridge + public static int update(String query, Map params) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Update all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities updated. + * @see #update(String, Object...) + * @see #update(String, Map) + */ + @GenerateBridge + public static int update(String query, Parameters params) { + throw JpaOperations.implementationInjectionMissing(); + } } diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java index 55f8af2437b19..2b474e29ac1e8 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheRepositoryBase.java @@ -714,4 +714,46 @@ public default void persist(Stream entities) { public default void persist(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { JpaOperations.persist(firstEntity, entities); } + + /** + * Update all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities updated. + * @see #update(String, Map) + * @see #update(String, Parameters) + */ + @GenerateBridge + public default int update(String query, Object... params) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Update all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities updated. + * @see #update(String, Object...) + * @see #update(String, Parameters) + */ + @GenerateBridge + public default int update(String query, Map params) { + throw JpaOperations.implementationInjectionMissing(); + } + + /** + * Update all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.hibernate.orm.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities updated. + * @see #update(String, Object...) + * @see #update(String, Map) + */ + @GenerateBridge + public default int update(String query, Parameters params) { + throw JpaOperations.implementationInjectionMissing(); + } } diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/package-info.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/package-info.java index 0aa4c33581b4f..b71b24af657d5 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/package-info.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/package-info.java @@ -33,7 +33,7 @@ * at the end. *

*

- * If your query does not start with from, we support the following additional forms: + * If your select query does not start with from, we support the following additional forms: *

*
    *
  • order by ... which will expand to from EntityName order by ...
  • @@ -42,6 +42,16 @@ *
  • <query> will expand to from EntityName where <query>
  • *
* + * If your update query does not start with update from, we support the following additional forms: + *

+ *
    + *
  • from EntityName ... which will expand to update from EntityName ...
  • + *
  • set? <singleColumnName> (and single parameter) which will expand to + * update from EntityName set <singleColumnName> = ?
  • + *
  • set? <update-query> will expand to + * update from EntityName set <update-query> = ?
  • + *
+ * * @author Stéphane Épardaud */ package io.quarkus.hibernate.orm.panache; diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java index 0f36c858ed93a..e12f5621fc283 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java @@ -17,6 +17,7 @@ import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.exception.PanacheQueryException; public class JpaOperations { @@ -156,6 +157,32 @@ private static String createCountQuery(Class entityClass, String query, int p return "SELECT COUNT(*) FROM " + getEntityName(entityClass) + " WHERE " + query; } + private static String createUpdateQuery(Class entityClass, String query, int paramCount) { + if (query == null) { + throw new PanacheQueryException("Query string cannot be null"); + } + + String trimmed = query.trim(); + if (trimmed.isEmpty()) { + throw new PanacheQueryException("Query string cannot be empty"); + } + + String trimmedLc = trimmed.toLowerCase(); + if (trimmedLc.startsWith("update ")) { + return query; + } + if (trimmedLc.startsWith("from ")) { + return "UPDATE " + query; + } + if (trimmedLc.indexOf(' ') == -1 && trimmedLc.indexOf('=') == -1 && paramCount == 1) { + query += " = ?1"; + } + if (trimmedLc.startsWith("set ")) { + return "UPDATE FROM " + getEntityName(entityClass) + " " + query; + } + return "UPDATE FROM " + getEntityName(entityClass) + " SET " + query; + } + private static String createDeleteQuery(Class entityClass, String query, int paramCount) { if (query == null) return "DELETE FROM " + getEntityName(entityClass); @@ -394,6 +421,28 @@ public static int executeUpdate(String query, Map params) { return jpaQuery.executeUpdate(); } + public static int executeUpdate(Class entityClass, String query, Object... params) { + String updateQuery = createUpdateQuery(entityClass, query, paramCount(params)); + return executeUpdate(updateQuery, params); + } + + public static int executeUpdate(Class entityClass, String query, Map params) { + String updateQuery = createUpdateQuery(entityClass, query, paramCount(params)); + return executeUpdate(updateQuery, params); + } + + public static int update(Class entityClass, String query, Map params) { + return executeUpdate(entityClass, query, params); + } + + public static int update(Class entityClass, String query, Parameters params) { + return update(entityClass, query, params.map()); + } + + public static int update(Class entityClass, String query, Object... params) { + return executeUpdate(entityClass, query, params); + } + public static void setRollbackOnly() { try { getTransactionManager().setRollbackOnly(); diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java index 36ce44d645651..9aa806f7266f0 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java @@ -25,6 +25,7 @@ import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.exception.PanacheQueryException; /** * Various tests covering Panache functionality. All tests should work in both standard JVM and in native mode. @@ -203,6 +204,8 @@ public String testModel() { Assertions.assertEquals(7, Person.deleteAll()); + testUpdate(); + // persistAndFlush Person person1 = new Person(); person1.name = "testFLush1"; @@ -221,6 +224,144 @@ public String testModel() { return "OK"; } + private void testUpdate() { + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + int updateByIndexParameter = Person.update("update from Person2 p set p.name = 'stefNEW' where p.name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + int updateByNamedParameter = Person.update("update from Person2 p set p.name = 'stefNEW' where p.name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, Person.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = Person.update("from Person2 p set p.name = 'stefNEW' where p.name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = Person.update("from Person2 p set p.name = 'stefNEW' where p.name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, Person.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = Person.update("set name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = Person.update("set name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, Person.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = Person.update("name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = Person.update("name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, Person.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = Person.update("name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = Person.update("name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2")); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, Person.deleteAll()); + + Assertions.assertThrows(PanacheQueryException.class, () -> Person.update(null), + "PanacheQueryException should have thrown"); + + Assertions.assertThrows(PanacheQueryException.class, () -> Person.update(" "), + "PanacheQueryException should have thrown"); + + } + + private void testUpdateDAO() { + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + int updateByIndexParameter = personDao.update("update from Person2 p set p.name = 'stefNEW' where p.name = ?1", + "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + int updateByNamedParameter = personDao.update("update from Person2 p set p.name = 'stefNEW' where p.name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, personDao.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = personDao.update("from Person2 p set p.name = 'stefNEW' where p.name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = personDao.update("from Person2 p set p.name = 'stefNEW' where p.name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, personDao.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = personDao.update("set name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = personDao.update("set name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, personDao.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = personDao.update("name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = personDao.update("name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2").map()); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, personDao.deleteAll()); + + makeSavedPerson("p1"); + makeSavedPerson("p2"); + + updateByIndexParameter = personDao.update("name = 'stefNEW' where name = ?1", "stefp1"); + Assertions.assertEquals(1, updateByIndexParameter, "More than one Person updated"); + + updateByNamedParameter = personDao.update("name = 'stefNEW' where name = :pName", + Parameters.with("pName", "stefp2")); + Assertions.assertEquals(1, updateByNamedParameter, "More than one Person updated"); + + Assertions.assertEquals(2, personDao.deleteAll()); + + Assertions.assertThrows(PanacheQueryException.class, () -> personDao.update(null), + "PanacheQueryException should have thrown"); + + Assertions.assertThrows(PanacheQueryException.class, () -> personDao.update(" "), + "PanacheQueryException should have thrown"); + } + private void testSorting() { Person person1 = new Person(); person1.name = "stef"; @@ -493,6 +634,8 @@ public String testModelDao() { Assertions.assertEquals(7, personDao.deleteAll()); + testUpdateDAO(); + //flush Person person1 = new Person(); person1.name = "testFlush1"; From a8b86d935f2145aee18f506f3cfb8cea10a93043 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 5 Dec 2019 19:38:13 +0100 Subject: [PATCH 215/602] Improve GraalVM detection by asking the version to native-image Until now we were using the VM name, which might just be a standard JVM if you're using a standard JVM and just have $GRAALVM_HOME set up. By calling native-image --version, we really check the proper version. --- .../pkg/steps/NativeImageBuildStep.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 2e09f768080b8..c2c122d68e01e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -73,8 +73,6 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa final String runnerJarName = runnerJar.getFileName().toString(); - isThisGraalVMVersionObsolete(); - HashMap env = new HashMap<>(System.getenv()); List nativeImage; @@ -131,6 +129,28 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeImage = Collections.singletonList(getNativeImageExecutable(graal, java, env).getAbsolutePath()); } + Optional graalVMVersion = Optional.empty(); + + try { + List versionCommand = new ArrayList<>(nativeImage); + versionCommand.add("--version"); + + Process versionProcess = new ProcessBuilder(versionCommand.toArray(new String[0])) + .start(); + versionProcess.waitFor(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(versionProcess.getInputStream()))) { + graalVMVersion = reader.lines().filter((l) -> l.startsWith("GraalVM Version")).findFirst(); + } + } catch (Exception e) { + throw new RuntimeException("Failed to get GraalVM version", e); + } + + if (graalVMVersion.isPresent()) { + checkGraalVMVersion(graalVMVersion.get()); + } else { + log.error("Unable to get GraalVM version from the native-image binary."); + } + try { List command = new ArrayList<>(nativeImage); if (nativeConfig.cleanupServer) { @@ -293,14 +313,13 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa } } - //FIXME remove after transition period - private void isThisGraalVMVersionObsolete() { - final String vmName = System.getProperty("java.vm.name"); - log.info("Running Quarkus native-image plugin on " + vmName); + private void checkGraalVMVersion(String version) { + log.info("Running Quarkus native-image plugin on " + version); final List obsoleteGraalVmVersions = Arrays.asList("1.0.0", "19.0.", "19.1.", "19.2."); - final boolean vmVersionIsObsolete = obsoleteGraalVmVersions.stream().anyMatch(vmName::contains); + final boolean vmVersionIsObsolete = obsoleteGraalVmVersions.stream().anyMatch(v -> version.contains(" " + v)); if (vmVersionIsObsolete) { - log.error("Out of date build of GraalVM detected! Please upgrade to GraalVM 19.3.0."); + throw new IllegalStateException( + "Out of date build of GraalVM detected: " + version + ". Please upgrade to GraalVM 19.3.0."); } } From e4e3240904adeabdedda42c0ca898ac6b6ebc14d Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 25 Nov 2019 22:35:48 -0300 Subject: [PATCH 216/602] Fixed gradle warnings --- .../io/quarkus/gradle/tasks/QuarkusBuild.java | 1 - .../io/quarkus/gradle/tasks/QuarkusDev.java | 1 - .../gradle/tasks/QuarkusListExtensions.java | 1 - .../quarkus/gradle/tasks/QuarkusNative.java | 24 +------------------ 4 files changed, 1 insertion(+), 26 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java index 7097db1843a49..95583b1662ffe 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java @@ -31,7 +31,6 @@ public QuarkusBuild() { super("Quarkus builds a runner jar based on the build jar"); } - @Optional @Input public boolean isUberJar() { return uberJar; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 4c88072bf067d..4bd20de12cfac 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -132,7 +132,6 @@ public void setJvmArgs(String jvmArgs) { this.jvmArgs = jvmArgs; } - @Optional @Input public boolean isPreventnoverify() { return preventnoverify; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index bf5879806f2d9..275c87a8e64b5 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -22,7 +22,6 @@ public class QuarkusListExtensions extends QuarkusPlatformTask { private String searchPattern; - @Optional @Input public boolean isAll() { return all; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index 068ace098c274..ad683b007509b 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -81,7 +81,6 @@ public QuarkusNative() { super("Building a native image"); } - @Optional @Input public boolean isAddAllCharsets() { return addAllCharsets; @@ -92,7 +91,6 @@ public void setAddAllCharsets(final boolean addAllCharsets) { this.addAllCharsets = addAllCharsets; } - @Optional @Input public boolean isReportErrorsAtRuntime() { return reportErrorsAtRuntime; @@ -103,7 +101,6 @@ public void setReportErrorsAtRuntime(boolean reportErrorsAtRuntime) { this.reportErrorsAtRuntime = reportErrorsAtRuntime; } - @Optional @Input public boolean isDebugSymbols() { return debugSymbols; @@ -114,7 +111,6 @@ public void setDebugSymbols(boolean debugSymbols) { this.debugSymbols = debugSymbols; } - @Optional @Input public boolean isDebugBuildProcess() { return debugBuildProcess; @@ -125,7 +121,6 @@ public void setDebugBuildProcess(boolean debugBuildProcess) { this.debugBuildProcess = debugBuildProcess; } - @Optional @Input public boolean isCleanupServer() { return cleanupServer; @@ -136,15 +131,13 @@ public void setCleanupServer(boolean cleanupServer) { this.cleanupServer = cleanupServer; } - @Optional @Input public boolean isEnableHttpUrlHandler() { return enableHttpUrlHandler; } - @Optional @Input - private boolean isEnableFallbackImages() { + public boolean isEnableFallbackImages() { return enableFallbackImages; } @@ -160,7 +153,6 @@ public void setEnableHttpUrlHandler(boolean enableHttpUrlHandler) { this.enableHttpUrlHandler = enableHttpUrlHandler; } - @Optional @Input public boolean isEnableHttpsUrlHandler() { return enableHttpsUrlHandler; @@ -171,7 +163,6 @@ public void setEnableHttpsUrlHandler(boolean enableHttpsUrlHandler) { this.enableHttpsUrlHandler = enableHttpsUrlHandler; } - @Optional @Input public boolean isEnableAllSecurityServices() { return enableAllSecurityServices; @@ -182,7 +173,6 @@ public void setEnableAllSecurityServices(boolean enableAllSecurityServices) { this.enableAllSecurityServices = enableAllSecurityServices; } - @Optional @Input public boolean isEnableIsolates() { return enableIsolates; @@ -193,7 +183,6 @@ public void setEnableIsolates(boolean enableIsolates) { this.enableIsolates = enableIsolates; } - @Optional @Input public String getGraalvmHome() { return graalvmHome; @@ -204,7 +193,6 @@ public void setGraalvmHome(String graalvmHome) { this.graalvmHome = graalvmHome; } - @Optional @Input public boolean isEnableServer() { return enableServer; @@ -215,7 +203,6 @@ public void setEnableServer(boolean enableServer) { this.enableServer = enableServer; } - @Optional @Input public boolean isEnableJni() { return enableJni; @@ -226,7 +213,6 @@ public void setEnableJni(boolean enableJni) { this.enableJni = enableJni; } - @Optional @Input public boolean isAutoServiceLoaderRegistration() { return autoServiceLoaderRegistration; @@ -237,7 +223,6 @@ public void setAutoServiceLoaderRegistration(boolean autoServiceLoaderRegistrati this.autoServiceLoaderRegistration = autoServiceLoaderRegistration; } - @Optional @Input public boolean isDumpProxies() { return dumpProxies; @@ -248,7 +233,6 @@ public void setDumpProxies(boolean dumpProxies) { this.dumpProxies = dumpProxies; } - @Optional @Input public String getNativeImageXmx() { return nativeImageXmx; @@ -278,13 +262,11 @@ public String getDockerBuild() { } @Option(description = "Container runtime", option = "container-runtime") - @Optional public void setContainerRuntime(String containerRuntime) { this.containerRuntime = containerRuntime; } @Option(description = "Container runtime options", option = "container-runtime-options") - @Optional public void setContainerRuntimeOptions(String containerRuntimeOptions) { this.containerRuntimeOptions = containerRuntimeOptions; } @@ -294,7 +276,6 @@ public void setDockerBuild(String dockerBuild) { this.dockerBuild = dockerBuild; } - @Optional @Input public boolean isEnableVMInspection() { return enableVMInspection; @@ -305,7 +286,6 @@ public void setEnableVMInspection(boolean enableVMInspection) { this.enableVMInspection = enableVMInspection; } - @Optional @Input public boolean isFullStackTraces() { return fullStackTraces; @@ -316,7 +296,6 @@ public void setFullStackTraces(boolean fullStackTraces) { this.fullStackTraces = fullStackTraces; } - @Optional @Input public boolean isEnableReports() { return enableReports; @@ -344,7 +323,6 @@ public void setAdditionalBuildArgs(List additionalBuildArgs) { this.additionalBuildArgs = additionalBuildArgs; } - @Optional @Input public boolean isReportExceptionStackTraces() { return reportExceptionStackTraces; From f4b2900b4cad14f6a2c5081869c98914a3079b9e Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 25 Nov 2019 22:49:33 -0300 Subject: [PATCH 217/602] Use gradle build --- devtools/gradle/build.gradle | 11 +- devtools/gradle/pom.xml | 145 +----------------- devtools/gradle/src/assembly/copy.xml | 18 --- .../resources/gradle-project/build.gradle | 4 +- 4 files changed, 10 insertions(+), 168 deletions(-) delete mode 100644 devtools/gradle/src/assembly/copy.xml diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index 6e154ecd6edfb..a533e6e4629c9 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -9,15 +9,20 @@ if (JavaVersion.current().isJava9Compatible()) { compileJava.options.compilerArgs.addAll(['--release', '8']) } -compileJava { +compileJava { sourceCompatibility = '1.8' targetCompatibility = '1.8' } repositories { - maven { url 'target/dependencies/' } + mavenLocal() mavenCentral() - maven { url 'https://repo.gradle.org/gradle/libs-releases-local/' } +} + +// Add a source set for the integration test suite +sourceSets { + functionalTest { + } } dependencies { diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index 0e6b7630d1ced..81adb4a629395 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -75,42 +75,9 @@ org.apache.maven maven-model
- - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test -
- - org.apache.maven.plugins - maven-dependency-plugin - - - copy-dependencies - generate-resources - - copy-dependencies - - false - - true - true - true - true - ${project.build.directory}/dependencies - - - - org.codehaus.mojo exec-maven-plugin @@ -125,6 +92,7 @@ ${gradle.task} -Pdescription=${project.description} -S + ${env.MAVEN_OPTS} ${settings.localRepository} @@ -137,83 +105,6 @@ - - maven-resources-plugin - - - copy-gradle-jars - package - - copy-resources - - - - ${basedir}/target - - - build/libs/ - - ${project.artifactId}-${project.version}.jar - ${project.artifactId}-${project.version}-javadoc.jar - ${project.artifactId}-${project.version}-sources.jar - - - - - - - - - maven-assembly-plugin - - - src/assembly/copy.xml - - false - false - - - - create-repo - package - - single - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - target/${project.artifactId}-${project.version}.jar - jar - - - target/${project.artifactId}-${project.version}-javadoc.jar - jar - javadoc - - - target/${project.artifactId}-${project.version}-sources.jar - jar - sources - - - ${skip.gradle.build} - - - - @@ -240,39 +131,5 @@ assemble - - full - - - full - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-zip - - attach-artifact - - - - - ${project.build.directory}/${project.artifactId}-${project.version}-docs.zip - zip - docs - - - ${skip.gradle.build} - - - - - - - diff --git a/devtools/gradle/src/assembly/copy.xml b/devtools/gradle/src/assembly/copy.xml deleted file mode 100644 index d2261e83989cf..0000000000000 --- a/devtools/gradle/src/assembly/copy.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - repo - - dir - - - - ${basedir}/build/libs/${project.artifactId}-${project.version}.jar - io/quarkus/quarkus-gradle-plugin/${project.version}/ - - - pom.xml - io/quarkus/quarkus-gradle-plugin/${project.version}/ - quarkus-gradle-plugin-${project.version}.pom - - - \ No newline at end of file diff --git a/devtools/gradle/src/test/resources/gradle-project/build.gradle b/devtools/gradle/src/test/resources/gradle-project/build.gradle index bfd1554070a34..2e6db65bd7396 100644 --- a/devtools/gradle/src/test/resources/gradle-project/build.gradle +++ b/devtools/gradle/src/test/resources/gradle-project/build.gradle @@ -5,9 +5,7 @@ plugins { } repositories { - maven { - url uri(System.getenv('MAVEN_LOCAL_REPO')) - } + mavenLocal() mavenCentral() } From 99ab4760f9ea3bb5bce21f210492868778889393 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 25 Nov 2019 22:58:44 -0300 Subject: [PATCH 218/602] Bump to Gradle 6.0.1 --- build-parent/pom.xml | 2 +- devtools/gradle/build.gradle | 22 ++++++------- .../gradle/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58702 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +-- devtools/gradle/gradlew | 29 ++++++++---------- devtools/gradle/pom.xml | 28 ----------------- devtools/gradle/settings.gradle | 13 ++++++++ .../java/io/quarkus/gradle/QuarkusPlugin.java | 4 +-- .../quarkus/gradle/tasks/QuarkusNative.java | 1 + independent-projects/tools/pom.xml | 2 +- 10 files changed, 41 insertions(+), 64 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 569d4ddcb53e3..91d4fb175f394 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -55,7 +55,7 @@ 3.6.2 0.7.6 - 5.6.4 + 6.0.1 2.1 diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index a533e6e4629c9..83db3821195cb 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'com.gradle.build-scan' version '2.3' id 'com.gradle.plugin-publish' version '0.10.1' id 'java-gradle-plugin' } @@ -19,20 +18,20 @@ repositories { mavenCentral() } -// Add a source set for the integration test suite +// Add a source set for the functional test suite sourceSets { functionalTest { } } dependencies { - compile "io.quarkus:quarkus-bootstrap-core:${version}" - compile "io.quarkus:quarkus-devtools-common:${version}" - compile "io.quarkus:quarkus-platform-descriptor-json:${version}" - compile "io.quarkus:quarkus-platform-descriptor-resolver-json:${version}" - compile "io.quarkus:quarkus-development-mode:${version}" - compile "io.quarkus:quarkus-creator:${version}" - compile gradleApi() + api gradleApi() + implementation "io.quarkus:quarkus-bootstrap-core:${version}" + implementation "io.quarkus:quarkus-devtools-common:${version}" + implementation "io.quarkus:quarkus-platform-descriptor-json:${version}" + implementation "io.quarkus:quarkus-platform-descriptor-resolver-json:${version}" + implementation "io.quarkus:quarkus-development-mode:${version}" + implementation "io.quarkus:quarkus-creator:${version}" testImplementation 'org.assertj:assertj-core:3.13.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' @@ -69,7 +68,4 @@ gradlePlugin { } buildScan { - //See also: https://docs.gradle.com/build-scan-plugin/ - termsOfServiceUrl = 'https://gradle.com/terms-of-service'; - termsOfServiceAgree = 'yes' -} +} \ No newline at end of file diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.jar b/devtools/gradle/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644 GIT binary patch delta 16535 zcmZ9zbyyr-lRgZCTOhc*ySoH;cXxO94DJ#b+}+(FxVr{|dypU*+~LbU`|kes`Fj57 zyY8yfeVy*U&eSRCZ-Sbgglb@bL`kJuD(i%VfWU)-fM5ZAqre6!L1F?a*_h28Ox@k% z)ux=5zF-P1b$GIsh22W}rhGA$wY4AMj)Kul`ohep<{7-Ia88yvi6?!4@QO*mP1?8% z^+-G1h=Bla=)vYr;y%0F`7k?YyaR;riRpp3>1dAn4tcrPo2W>F8o&vIoo8FT(bXb?GlmSb7V9@<6RmZzUyg~x=I4k!GQX(!lDs)h5@qh6pkwH=O@3LDKNm1i;WQ8o$Fl=C^mx!!2RpT&LbaQ5~-gj zk}V-#Uq1+j(|;TD?e?fpp}ORH^Fq!uFQ{?+R=-AAXl>dQHNRxA%eOvJm2_4jRrfpH z5-aw5XpBp(8nzoT7~-#u+*s{L@q<(~8X0g_k%xjtgn)pDhk$?(g|LNWtR{hhfS~+K zG5zN~69PBXF|=_%h}_p27^B$eqeB|SWFatETD2Oq;%Vn$m>?Zn)|n^BYMi`It%~RE z{?zseJ_NVFBivK1vbQd!dzAq}2e$&>Wo6B}`={5MckUhxc|L^S-q?bQA7!N=FxZWT zU=VP`Gg4To%<=zBf<;qVDNMDbkkc&;M*Z23z5%huy5rEWEer-UUAsxdlvL`%T?_}| z(AC(*xAH|wk8S#%l@lNw>O44BZp257X zHvrr{{odBrGrE6ZV); zj8iGg2`q{Cm5o=D;JE|EG^sx`O)a|Vsgst~3Ake^OY!6;?G&szhN9ov0-!PbvBcU5 zGRjaV&=KpDs4zqyN`T#AmhHfP#k*wGhXF?Dga*x|Bj`& zHV~0hpwX|JkNK!dAqe;o8Ea%7b%IeQD~k(41Q0J{%pt1LS1Ggcq3FOT= z5A|Vo_JTwHTm_Y#V?{dbMum`oDTd}5=vi-t>w&h{Z8|8w&TVt0^eE-i3>R&hl&SM_ zmq)Meerq`|97S(0OKH~x2bnWXD<9`-`tCM{=8}{PSRq_%t`k~5fPh}{h3YIkjBTGneZ+JF+OuXd^<)_ZuX5$u&ZP+pP<2g_}pc)~MKJVi9<{(FJ?Nr^j) z=vL&X+rs>>ym1r>$ddJHuRN}3R53kb3p*4jpEpZzzA*8+3P^Zm_{$%#!r=GQC(O@C zx6Lk~7MUL^QcV)@DgnE*4-XV`3c`9c&QcG>RRmvV%AHUPa?0%()8%asP!noiK|7#1;^qznQT z0~b;d`W|`=o_E4xvzJ%-6v|@%kGFdG2L#9-_6miL%AA`Q8UkV!?(cf~&k72JLx7X8 zv@-Q{@Bp3R5(7&$x6}zVF+a8(xRIt{)nsT>+Jf4+pyjHxT1sjigKcbRQ&rGv`O^=% z9loFMTS2`MJnyO-KNl${u=ILJh5e4pedY`0;4eN1B{>+214bTnrh^ygc0ClRkGF-6 z^KM>p6MJ-DjzMz}f}!mS!&hQLdMYMBZn`5Ft}T)22E31R0j608`P&({6Sv z+~0D8pDl^uBMtG_h6A3r60>3 ze}0-}HvlSJitaX&`j_DjiW^0DaQ|}DHmI7NLj)$z@t4@n`b%CaxbCFQaar%#KMbFrP8;UV*=UXv2t~N7${I78|hP9xX|r*{0)ZBS-A2?pnEp z5{%38c<{72i%oG5F zBn@<(E_yi9g#uyMnN0S#v~L6&+}+@3~P5v<;rEzy3qM((!S^E7A$!`9*Z zfXHq{x|C#{_u}V_a3rgg{+P${gr=ns+3nmp7N*3$9I`A)xCG=A&A zk)vJy%fy1XNE<$2gK24($*r7zv|jZX)Cs&uID;Ff>s4pn&mdgKDt8oUo#5NiSA)&e zJ4iE)n<|_?dQ#*Q@65>|bKEX#^E_AO@K|ufg}Vxmu;OF$c;lKXEaaj*j#yz`L)}N4 z7`o+@_lsZgv4de;{vM}N<&38%r!Vzbcm11k4Keo+>iUiF?hz3GnEb7mTyS3bsTfEg z{lk+$yF=lE(k<$qGn=dX;d3Di>#8R3#qeA{5c+~3qq1%VjOdZv{)bd5jroreFdBBbJ#1)lyIhM5VZs&!Pcn5PR2S# z=^0_9q~0cs$>}}R&gvTxD)MaWj`V7B0z1~8qhjtKm}`Y~#bXcn!m-JZ7H@n7E8l%j zuSN6NIX__j?Xk_ZA`0VxOyNX<7f$G+m_p4e*zNKonge<-rut`Usij{fL)mOusi|$U zG_o_^vj(A89K0u3WqcXp5zrI^AV?;CtmPSO5tiQ?Io$v79p?$~+?+i;NYf5nDND9A+Xjmwo|s55SQS$L9~oncx`VWnLO|nBSK6IuerhlQz zwuQ>taA1U{x7}WC)8#rZke-dv7{a2#t2m)1`e*N@kb5${9SJvk21PuQAlo!osvVYo z*AA*9nWA8WYM6BTBaiE#Wsp*ug2Ni;mUP#+IfgQB%!hX-a;LhvHF~Uiw$=FPa8M+Q zbNf%N{comPbCObF8bT2$?fkH+i>L&@2A|M|ni2YeC028z<6$xMKt<;E(nAaKQ|x;N zC(5?n?3KK3q!h)jC#br?MSQ5~ROH_ujB;*1$-pNF2n=Ef z2(thDLBRw6dm~q?i{N9R?fIT)<*Qs=K4PwazZ%VvU@pCaFOWbq6^$`8cv-V*)=9!(~wffqAT0h85(jmhvt3`g!XYq7_pu(SpG zuFo4gz9bs{%})Pe%lop^TI8cg`F#@A=oJtIti85@I0G|4O1So9HM3OjX)lBAVSCYo zNc!rGzKXlPl|}C$?p8lKLiJ$;h3}y3K7d;xwj+16he&AiL^Os-U>abIdB9_^y`TH# zUS%N|z%vlSK_Z${z_JJto+}*4ZW3T+L?1i2$?x40Lis=+@)hM>3k9gH=m>P)CjkH- zrC&k8K<=vx2<|=O02Ls95dJH}J5x|O_z!h2Mn7;@BsJ_0{iHX_YkJdxzuluV*J~nv zZ+(RJ4=@zh^dfdJ9r~Aijm&+v5&I~Xpsfz4n0#e6%-Bk+Wn>UEAW9~lP78vslB;y~ zo1df|t7RsgDAXTT3*RqV<8tcwsXu_45jEVD7L)kuEBJ1qbUd)Eq-P496DbYJ-}BPO zXUZH{e_^Y0XEjZv=quW?TQ;N5JIKV6)dCoj75Gnk5ClN3>>=6re8pbedzbQtGSq7K zGS2*5XXa)F(uorON)mI(=YL`){fdAVXTtXR z?E>gtZZ#A~Wd{?Dh9T=cl@_C|pv$1#asILv1iP+hRKnFAZ)$A5PGi!~sPoXGhR()w z1HEsJtC>BKv>V0f6kr-PbMwil)~(80oiUwtVp(1yoW=XY642$zO00%CSjbM9Hw3~O zN{JssnFCFubzZ++sSh(;EyKsbeW~AV%|fD3h|W2=o>_m1xEg zS9JqIRzw!}X(6J|KG z9-ip9vJlnYdhKBhdc%p#m2DlLL6OW&Dmg0wd4-HxE=9wreebMg&URh&AI%XfWxo<% zTTsB>FK5HKq1$D>O=WW_LG?CzSi#~CA<- zK36RlA;PKAM?0TEf|`sPMp={ELiS6~jYefrI5~=W(mM~EG%)G7oz1DPkV-D58=U=? z>)PhLkx#h7)KFO|W~(XoErM-q##xTUbMp#Qy`e0QL5)aN+Vq_D}m#bjQA)?xQHbUF?>&b> zuiSSvN~gMti(Eo02wSosQnU^i4_LYr-&X zlj%ECr}SkjnA@NUOeSbPL2Np;qvFuYi~>C?<15|-ngY6(2gpwBR7V7+ou@-#=Z&~y zTY=GwE0CR+Y?}`Y2%9L2=FKk9Kk2whbTRSKtBU(Eo~D|o-O}0bFtL?!)y-4o=6d9Q z7EjP$WN{eyMfL53F13MF0~4>;#Cp(@U?a5=Dk7)h(39O}LY9vzi0nbvO%Il_(^ztc zo<&!Fb{9w`PplGJJ58Y0Y|0hqQouVl$XSONKyQmDFJ-CVayp#XYeVVBx|wep9f3+D zvQ4n!gOP{IyZ6JFhNun1$$o%*lY%g3Dz~Z_9-BdMR0b9$Y6rtlQ4^6&(&yc~I1iGo zS2$+!`m^OQ(Z#hke@*Su;D1+v+}2_`&#Q9~ECl**ts zd5);~Z&Y$GY?ngLCZ{N{FS|F49GF0g>0B3-AW>=bKBO%sbO|~TDgQ#DKcRzT5vLtZ zWi;OezJA%rP0L9~x_OMzPuKp!DXOE&(q^0^(}FqzqPTc*_~}(nO*F_?Tt8Q13Buex zQUspuM`!1e-_IhP9V}qyyG&Z-F{fq3c!dvJ4C3rxKB7k_S`SX75X@T8(5SbVQYx%t zCeZ}=>{c)@#SZrel(*pUOSWPr);$ex1I((16?Lz_*$JZrUmPO^*zQjI829Sb6a_x0)g36Wod$piD+WsTlnct7G#;>kCev7^LwzYL1n5)bF?A1y8or;AjG?4Vs zK2_1BkfMEqdD_ww5ie=v5MCpL{TrJNy8)DLx%r z&#XmHhq&O>tyfXJP99TItlVcYe}t>+7)ER@@>LM71QqZ1`tB|JYxf2mld0LT>F-6% zeyR4r9(H^slfuHPIK=E@zN~FH{!t|KOAR})zUFHy*C<1tU_SpC{;DonK{@?!$0AMw zqR!8h>aWX7Iuqh|o*UgBjVYgi;jd%BrR`F;(n*&~{V|a&Ipx($01mxGRR|IcbIlmP z1euEoX;?Gwm@nW97Ig!xY>C_-Pyn#uTqwTanQ~9CqF3(rCSY#@6-gNCFn3U#kmN{T zBmjJ^yR}JP>$vm{rzJz0(;RC|E5l}}IEU*P@5--R^aH<9j{#jsy{Za$t3Y>SgXPRv z;RB~xVJzrmmnWs^K859zwNclqytTpP!@*T!= zH3q9AcVI0dzC(PYg^8upVyP@yF}vlvreE4JcV%YNtUSF)J>trpjeRiIK)>b>1L-Z~ z8qrLt3(X&N`hx3e{5>B)rBO4QH1qTo$6pUv9(}qulWyoho-`6k#*}Rg?;d5l!v%IGJJVBekDVFlZ#etwfuSd$ z3Xf;KI`WL6Yo!llE#z5~U!+((O6HoJhjXT$fO`RrQ`??n9(ZzA(6UZEYcxWBQe2mmB|vYmQa4ZmP(5j#WEsOVNR2R9-EI9hUJfdBpie1 z;2+S%rpd?wDNNCI6O~^fUyj}IhT^bEK2pCtST6P|u6xV85Zl)8 z)-;%p$lE5`W&eJBp#O@P$Pul71x@DB$#CHR5BXT2W|`4%q@Q`xK?n>|wQyh-ru% z;F9*X++b7s7>P`1b*d!UX&Go%wd01Fbqya{(PjIF+=k43+@Q(3Ih*hJ+8HXc@ziXN z?`_1~T50UeYrJxQc4aE%p)?{r{=}HaQ1NI1sp-uFY*#S1Zn>BO_oAIU6xI=X2_eY; zyfm!YTG`#=SQX-p_YZkEYADZy-yE_2Znfy|O9G+61G@;}+V$V1Fck0m*{EBUU+@`*D>9RUFH^nE zxL%5K-x@%Mu5rs-V|pakt$o3FZ@3HwBWJ==Koc%L;QT5UV*_fw+?+qy~5L?@(IK~C3%Bpg^*dCPoO`VD;`j<(SQx=cYuEzJ3Kx9<4tk#9;6m~nFNpj+xdr`sp_liiuQ<%+_icThV{&~Licp|OR9`4yfb0$o7fGOyYqHYE!+r8=2#3HT za~SrGY&Pzj2)9k!Ff74qEn!^Ss%G4@ji+fZlCY9MetCHQZu}9bn92F~ctoQFG_oEwBkwH;L_&wCv)vIBgz2qdfj0G8Nawv#o%MPpxBlw(p1krpHS7RR z`$Yz*{t)EqY)fb@e5dgyY7_+b{ntJi^k)LUc@;Md3x&@Cb6@Lk)++)X0)qU%_rc6) zKpo!zOmD1@_ogvM5agnY7>-T0o`XBf9(~x5m>8QQIw@HgbV=^{r);ujjFZMmo3tF|(LT4oR>XL!ZRy=E4jC5@IbMLd>Z`&`u4=;+d zZ^wm^kTruMN2XAWPRX0y-w3j^F?kZ=fY>Eegh`(Vqr!^WElPad;-uRn!Q_|5(+n(o zN2QyD$48&=5V{qlc#LLea&KI4j0TFoTXv(@n zcXtv#>@z7mYUTCT5~_Ch5VCcLW-p*!9{lp2^ugI?GXGX9vn#aOtv&c6<^zN$0mAQv zk_E^}VF*tXkeJ%iPzGp>@^7*%A&5}#9iS`8J%)W5`Mj)Ss-wD$I}hSHji7EQIB4*b zh(FN^J0^gc%%mZUDNY!DPBvIR}ooqwwyh7X`mXLGVvE#bf9EqQCS;r zN6ckX>nGa>mD;=VL*#o=qk6#S^< z6W3B0EXNXzVuRUm1%)WC)|epi%nijOwwYyzXtmI-1|v^QYL}W2eg{IQVTya`>+zUn z)tUgTF$Ke#F@I9q>kL@?^g`upf?27t0ur+4Zq{+Yk}$@D=~w|U#;IT~7~?TMn4Nwe zD#4;%eIJd1b~d^_0mRPcb_sdL)N7E$ce5!mselG7fY7H6hI>^V06l_2 zL=IRa3;-En6dxYhlAO32lVz6Zyjq6Ws4w2e@mRDFXm zGReM}&?fI0F%D$29} zHP4JZ&oif!F0S4zU-Np0X^d4mnt$TtO0vGQTj}#cLufwTf}v1Z9w>nG~1 zV2ueg9Vu7TpDJ_A`fhu{7wOO~lbh|OL(9$8{WoeF-oHm0M*Bdw^PqFv#3(lv5LM^z z)f}5)Ele!-tg%;JHL){?B~g?V@k1lsE5$B*$K!hrBu@imygQpofyWcGCQ*-H@(1yx z|Kd#8Pd{LrJlQTL_?P+MbnN=rC%{Fw+mM1$@~ra9t4I z!&xVy1ImDP3ZY*8&n7~a*ScZPXT%b^us5?}mn71iJnHNj#+^Y~$k+)>-_x}M@eH_Q z?(Xn35{fdhp;`P0VyRtxt%sno6UikEmn)Za#NM#*!lJ+0=F_xX3(LG?fM2+mHbsIh z4X1$8Y=YGYQ{@UaSCMbJs%8LfD_Mqm@{m#FI_e_is-78poq$y!?A#UE`9q1}MtZXk zfI)9_>lm>GdN7!yL&*d)+t;I~;MlT)N~feGA|));Lt!qfrpUzw&>BedE|8f@I9|XU z>bD{-vhFbMl;UegpuF3b_9f{AKKho?Vh@^vU4nG*2LnM4H zEd&#WdK_UPsLe0cH0X!VX2)^+DJl0fa3Ygq?DPtwi)*5{hXd*^00D7iI`f*k?f3 z*wu(njYNj~q+YSm_sL~Wrp3~mi9-8?ej^mCG_%FVg29kinD?>3{h*E@eM1G35QXP- zQ=WUY5M?!`yJRnsiMlZ(d>GlqueV8#kW!x5FI@Ysw@Y>XQ61@S_99orI1jrJy5~bn zMd&R3qRDQ=D0PPrwosTw5BE+K$`!!B@%bmfy)3-!$yZpUqa7J9KC!`F7{)ZTR5X9s z+DIzSHzc_Ccz9J&3T_buevQV|Mdr&=B627E5I5e?yK*_J`u)!q%B)lo>tyLhW2WsS z5qp*VfX>fj)5 zV`*;x-_iNhlr7~Y72MJMW={qNqFo8eUg*pwl#&B+j3Qi$=mqFoGb@B`qDfQCu7sA{ zXA<9`aBB2;Y9qfr63c)&+qKb*V9PcC*^Rv82Vv(q+mF|`E2MrzVmz5*$|13c!6IZ- zi>{Jl#xYAMyqXgope3uF@Q(Y)l$0SWvLn&;!=@Yl3ep%>;_0BU_huPOnLIiXQeR6(?-dlLs{{utZJyF`F3`@R`*ClesEZAEnPqlDY;}SVS1R z7fby*m$Rzak^8=49GrF#{d4BI4!m=1sNHF|x>@VCljIu!RISg?TnR06R3B_G;@vS7 zSzb~moI}WGpY{~>T-U}ATdZ{$w71ey4?WMTKO%C4|h;X1fykFoJNyujJ_)Xbo zz|6sjU5A`rGd$)-&_E7(76{RmIErVZ8N&Sxn=2w3YVBCrtCz`ctAVe$gWcrt62v4M z6`kE-X$JojsE{$9#mZ`9hOW-Pf_qedGCqv!GzI=X4-xbG}5`%Gc?a0-${Tdx5A`@3y^MQbR*gn;zv=n^q_bYw^bG$>79N|uRn#;X~E;^ z7EwMtcx{QLkpBNi+z#1et&!=CR)jC#{i#vvuQNf&ebg5QdgB-7%dD2h5 z)N|MBd~<0(`4*>Bt+pZf$H!iLdIv4pd-|1+uf^~L2Y_R-B_CP&%7-JuM&um7$RE|n zYQXBmEH_uOi!5_Taz=Z9Q}C0C<*A6;FSf#7Bb)TLTJr8O4f+&>b^+a5QY&=bMtgcB z`M(eN@m6=ssk&9O>R(Phg%$Ufu!O~ld7e%!R$f~|co+=+lxq$K!tgxmq^C>S9?@+c zmV0j2xB$oJtgo?c2ftROCPn3QU(=FEmnO<`%*`(?~Se3Ol9tDni?7 zKRSqT#TsTm(r}m(E?HJuR4gW5gBWB+I$R`*E!O(R%#5@ zJ1w@>CpDL?YmB z!+|#vAAGs(3-qQyr{ae{KaO==8Vty}2k6Uf&RGX>^qE-JKJmaFE{4*iizD5{wJj#3N z@Pfbia)x5aaaUT{F~PZ`8mjj_Qk+0s5dkR9A>McrQrWg7-l*0X-BBd$o@e`8^{A0FPfY!tF}}#lf%(Y{n->BAA337N`XFrE~5JR6UU5j zQ7X-yet0g{ny>A+4AOFOvz=ov*$?tR4OA{g?c+@ygFE5+th)K|L)~})WyX^k%POGy zZAaD}H}$8zdh|SpmQ`y>G<0*v>kgxQRxvC8Q#q5*Ukvc=77xm595Bm|%N{D?+9(yk z%dPNMcvfI1B~EU{AI;p%qAiY2kq=zz=98mkZO{r7FS4z}dQ=H@Y^~2s46WEm)`&pm zy(!GDY};Y2EqJar>nvwQMp&KPO=;k-cYJ{mDuhMZ%xHv{V@q<=O5%DRF{ZZAEfg}S zNz}$Cb72ELtfrd%c3qZ4Nt3b9J;kLxR9I{S!bmvx*!~NEaF#!+9C+W;bX>2_b3)!@ zh*Vv}TG1N=;Zbewti+J?c_$La(4~5uB!?h+Y9;G=?qKalaoQjeG(%@iCN+Rt6uXe8 zyYW4;Sbm7vKf*3jfLY#;UXSz_@%&u}sUym2#81N68lVy$uATR($xx+y;+ZsfS+ zEH=DDvllZ_+_u0b3vr3q z1BF9VWF1*>M|r{_KxKpC6^OBOh}Csmt7kS$K=n=SgO5GJ65LWhE|~RE9LA zxHF%nkP>rMt%y?hxgN%W-3b{kYTZW&^~vUYt%cTCS51#8#X12s6WrB~T64@dmgz8K zabeR@_}?tJ%%9n+W0&9Y874MNldAg55i;fG7TxLJQs2uKDQ+v|`pQKrZh3_Y7hyaK z<#q}k={;4-<H-*c%C4Py4Sxwd zDp?R8BTDRj*VrBsQGIgimHy@LThIAW86fgU?FrHkWVz|<{P=hwnbFfN|9T&ibpz-zFcg(LczapPVmtrXF8I6{ZO|w>n zP8tw%NKE@LtezVuMSkU1zTzrO&YYE=AS~-=3gOy&=;1s30Pg;bKzLeswIOo3kil43 z51m=p66(J zlwL2r#!dF^TC2j|96t>C_YCiG#ssB2DN~iB5Rc0BqzKsYA2D;N`#py*a81Jo$ z7)<;?ny++*P!4pbjKCk`a-JnjH5T&;o|>ZX8|>410%{IC!XK+8(CxZtY`D{ZL;xA$ zzS7Lt_oT?B`_cE!eplg*LZE8cmPxu}UeoxhK0X@gyIcm=r~kUJ zJqyqTcPpSVqmjD68vmqM)GCFD9hXOSvMS19Axg6hf zk{!Bw{aLveknL@H0Kl4@syTr0$9E-B$ZZyEpx+Z!@i$BSOAU+rWGBbw&-Sf-8g$sWa_9j%-(UCzgV5~Z9H|c!VW3q3xUO?GQLEc5R^#7{vXX|M}^HoQZ7qb9#UGy81z8-?!LA0$_%eq&x(EXY)|H|>weX(z)&xD2Uu z8{ug2{@PN<2baC_6DBob^=kin<%B~UE0cfp%we^+ho~>``4&d?YOmFe{2{Y3 zg;0*x=(8=`Rq$`emRZ0VQYA@q{2S95E%0j>cRpF`6GDO+(VKUU05QM*AOZ2Ybz=)K zcQ8;Qu^&93wxMYoO-m199v+e8I*Y?9w2-u7ZFRlTi2Af}w!b_l zc14C)-#?J%W^HP$xvFb>b>zdC!|EA*vz;m?FiBBDjPq%0+CFue)oD&~fHl(e5!fZU zJ-8suZULRA?~J5N+ol@Nb4EImc2;kBU%H|~+MS;&c2!!*k5^=i0&(st-5WfNEnZ;X zi5)MgdK}?sDUHc%(4+Gt#GHV+$Kg8fK3CFWM}`4|qD0Ja$dM4=9oPNy#m}qchA8r! zr^cGz*O17HZmS?F5l?7;2}cI#6)OHoCuvmf8F56r(t;>@%200F6GcP=FzW zL`bXJGbeub&dShGz#KI>6Za%B-Ea96z)8I^Ps?$5UU)M2@OJzC9%5@uF2|BiRl+zS zq$edug*g%A&(G)$Z)bew{xu#5ljnYTJ@~tQNm2{QW*G7n*M_C^PthCk_ADG6&$DcJ zZi?Zm-f{&q-DyPqLzY6&0bd^%5KRP}@P}9Tg=YHvyaB;uLRZ5+Gl>*qE3Lb3_dl zXI7c$^=Vqp)Wz1K8*@?hDZb2M;nQv4Gi1l3E%zImmYb;~*+mJ7X!FAS4SyH028J#2 zRuB!#R@AanO*eu)SjhQo=-6yJF%!v6>ax6lk{Mr9`-g0CwW0f#c;vizFS~M`z!@yQ zIy%^6KBM!};NfoT4-f}Vu+D&%&&&H^V}yva4p}du{;b3#b3f~B>JFwG&bjPVyi#Cy z=5FTs=xdfr8qxS=LG&eo?Uyfj>^-3g)hM*=oRwbLiQe8KBr5#0#?$*v(@k*^MUG*s zikul)knv~+KGgB$Oq}6^tQuhn<=7cR1t3}_`|%RR6o_Rleqii+1(EqNWKg=k!D|N6 zJQJ%LcWnWm2g8<>uqwaf3X%;^T-bbn)yC;3Tx(X|Em?2TJVNk#D3%i#eo6VnDZ}%# zR}Y-B(QWLB(K-^(7Mw8E;VEpUcA-1wr25I%aAK42`_J(&Arbqcg;xPl)C?N$bSUS) zK%agqnAH#v_y8rqVjY9(hHgRB9E1Xb)-f-p^cC({KhMi6Un;>y)0kwbn?aTPz3O#P z8p)FVS^aJzivH*lrGZfvX3sro$Y!?_tckux z70r$aORx?t;L(+(ui$Y&x}rxAaTug>$VM0ISy?1&Jy6dotuvC1Mv6e8P8?I?WVb?` z6T#}tGEKT5)G-aGp%hwPasorcNM}=)V{(%U-JZjHfwA93%W>9WM6IEsY&JfakIOSJ zIg8)9p9wMD_p-P%WZ!rG`LV~g0!#0)4?u8P02y_&7u5h^=D<#w7yj-OQB#hJUZrvH={xrLh17RaF{e+d2OSbYY z3*9AgW~5b8Wz%#UK-fk4Iw)J#sZsK%vv(awe(pV;dD*sN{kdnkx@9tGxecHn`$29& z*p{jn+$?5iGyA>F+bHktL+9RK)&y)RRfM77f%&KoECV-gQ5kMm$isya5rE0HTS_4q z7*bum1uWV2mj<<*+*Gedp=(wti9K>RPYN2k$`0O&`K3q844a((t<*e-D-JEMSD5#_ z(&KY=2-sV_B9RF7U3-Cvp7z-5-!X1V=OrTyon5hMKYU5buKBfR)gFb*0eNr`Y0Dmq zKv^$6ql6aZ9qr2!OT(6;x>%(;&_k7y-kR)ka=+HVO0}uDGhD8k_K|?&%wFJI}R;O`cklo*lxj=`|yGhttzyB=IFvx&q{QEQL+ zvYvTr98=HFwaw4f72F6TD4YOCxSA~l;0sZ|=p!jDF#wsQj6K5&p{Nl1ssZ8K1|TXI z?uP*cg(38u0bs`<__+GSHs~I&3mdi@;pls69^4&LnzTN|Pd!5Bxh0lbwCSQtpt~NnV>oB6!3t! zL^-x8%cOqUyx86ZYV3%jXiD<=!Esq_i4i{#|IG6UIM&(kgSr_?Q}Ceq740^1jUMVp^dm&Yr!sa{j1bSW=ZK$fTb4Q| zKS)0U9nzV`F*U<(OA+eg#14fv@%*w^kJ}L>ntz807HYzg%Zm`-4)TEgMaiG~{;8L^hFJLn+MDIEebIka9DOIDrP13&`lWkA^rP(y zkZRk3Uj%RsC9~gVP?&VhhoX8SKD1>AsW& z>5$Q@Z-H~l=j0rc_@!4w;}TCnhkR~CqtJCv;;!K5s#rOd{^c1@WBJe+`I_t6K<|g| z5Jzj{O0`1Ag_=oC+1;xyv@bTus0F0eoY8PrIj>K)@`ppS-nwbyF=kX)R%Lx{)QEz;*8^w@&F3GGU*io054f9jY`f#8{WX7e7SH`qmK}`LF^-F=I+e zm0h_FJVcOYK#B4SnXuKY9IOkSU*WaPS1+sDb!cvTMz6*V)5eDrZ2#441A{aL9i!?J zcOyp{N@qQW`dX|F;D~GVWx`96t-x`T*FDDHN@0w*i zYP{jfBLwQiZ6>xhBo>Xg6`%9Xugh-Xq1=8%)cpaaQ4{O!NH$o@E40Gn!dpe88|K3Z z_Y;Dstv!p6^ZjUEiKh>UW&^n|U;lqC(3Ru7Al3<7!hbc){%xWCpQ9w00t%Ewf%Ugf z8Xpw1iU#t9MMM67%6RyHlz&^pKx`8@g#T(9`yZ>n=aOI-g#R)8zddB2%1JcBe>y+@ z<_#47cAIhjYY^P0{|q7nWlf+F{;T5uUxqGd|1pFIl}%xTo+j`CE+qd;-QZ&X*Ns3r zllTA=(tqd;Jkq}uJ;0jguSfs_PYMGV=>I}Skiir^0H5<8quePH!hcm){Og|3T>lsW znNdNnQ)q<$H~aB7ko><#NpP0Xe+=P~|8Fh?v^S1T_^;UW|Bm^u2WI-^KcnD464R^z zam|0kcsb;MrcyqQ5BQ_~4<$T<0+Le11-(tv1739hLkR&iP5*)UT124w8G3-F)juM5 zMgm}B`yU7gQk&%ke0KwZt*JopbA+Io*-rohcaVw=!(WjeVBrqpoD%?m+(E8$h5%x( zzb8D9gFPh(Wu6`|=LcGdBm|MV;D8+dik1QYi03w_f3;|!rFneFk-vo}L?EOEZU9o) zUnK>|YJm-K|KCu_4QCH_N!7nK1y z$so}sTfj@^Kg`^cB;Yv*B$`DB68Z53@R1J+{$UP4E&hi=T^0Z!m;QxZ|6C|(86N;& z@mFL4Z7%Zz9;*Jif^xxUP|y+@$Y2E@AYc0rmAxVZ2ygfc$w6>GSphqPAhLdPkp5qI zKKU0i|D7uuXzC|E0Bsg@{L>0>I0sT*wFI;;fX+wB{_7c{QT^*JA}oT0$7rxsw{>jWwr$(CHL*R>GqL%^nPg(yp4hf0w(Z=x^S!sedb_%6ueJ8>bGpu- zK4gE=!rLT>yjqw?mVPQf5 zX)Y2R70ivs6xp<-Rof`nMFPqQYA>;lG)fwyWH~oFAb*AJ`vKkkSfp%N;Sbwby|%dg z8T}b8Wb>3UDuNbN!LXFU{&v3pbm9NFe`WPs7}6O|m?mO3Cj`~mVeu`7=D4pj1`^V$j%II2Y2Z38#sJz8&P(2` zjWTte&|ACL*V{O3EAU(0Bt1_^5W*A+ua!<1e=mw01vYM>Y=_8Pb&ToFs;x~1|J`f7 zY?AfR)Y)PFCC+XaQ}TvpL0`heiV~}#`+d+TVE&1)%ivJyHOQd@GtJ1-y??B|eb3eE zC#eCdewcY=(FEZ~P7aqxMfy~GoGIq8f23&%GcFbJ)9q|FndHj4REFq{xKW*a^7y5t zd6?4Iefg!zkuHJ4% zOHwMayunN-G{&guwqoPv`hi-n)Q(bIk2R!0(>1lJLMaEHS9PXZj@Gnd7bdQpCwv+A z(V-tbc+ES%uZIxVOEaBjv{qw!jg9Cb9y&pRM-vv`rXh1U%GYk4`ll^4j*zn2FqA%d=A9qhSB`SEnJuTg#bv zyJ(g);;1KM6PMgd6ZT61aakbWse! z21a|sW*uz@$$fE=jeO5&BR;C1}M+mUOzX5{@4C9$5tvaygH|<>=JGuDttX|c*Xgv^;8wE%QhO4T>1AboCFT}l;{ey-3eF;)44K!L3pQ~_naGR!jO+UdE>`85q0kq!+6fX-<{wI+ zRUF_kRRle+a`^DLuklYo#4fOwLV_Ry21T5a46gpS^ii1xm(XZeo%^Iioi5Wt5~uh~ z1U)aVWJjooE7YsX?w<;1Z{TxnARr*3Ae_wtSv^P~AU_E~KuCekrdYtZMI=DB zF07xyux`k`~{KojTikl?ts%y3!_ooUc0Am2@y)KX$=NU+nx~Cirvojs!O=PSwZ>%=?E9*I$ zWGnu+#-uUsbN%b52g>x0Q_!=%pCl(hTha#Lv`ZZHEd34)1aRH>pk&=J2LMU|4?iMn zpl)iOTWsI?KglDkZhldH%Bz0rU)*y_zGMd0(EEQ%bADB1eyLA#Yuts|c9&&3(Plel ziZ#4SDwMGl&7l~hyxr)kzrV}!@vL@`9;DB_E-Gs{pjm#HFK%usV0V*^*l zL4zA})ioWHYdWJ7*TSzKN(R)@+9B#%jlGhDSp?JKE4E2q;O9}*k0$FYwoN8a7TdEP zc&ayN&gF8gSjrTTDuPweCpvFTwPwrl(u$T&D;nkSCOlGQhhXD3brsT=;-B+w&HI)g zZOr6-T5CHYueMLGV_!74W~W<6`#3VN)+wvZXDAd3@b4h5-ZYxaH2`v(Ykoh;eC1i+ z8yu-Rk|k8j9oUI_3~%rBhrdosb|?{-L*U844FJ*6kq)ZPl-ki9(5nTpyw;f79`76X znmx{BqgZ(^>q-b-)4E896$g`GML!y|emZAsl=G+F{tQ_wDcTT%2Bx9i6bdf2{K)2q zzKo+Z+X@hs?nlF8-~#xwep^rISLMG@7!(jM9><^tHP9cL^ui zr-q$(!w%cwpI?p1MpCXL4e!RKnyi?c%W)RV)6zFsOvrw(lK?1bIh^QG_2i8gOf_ci z@4j|UREHe3!tyH}%sKk?R&N?;WhwDq2EtOOl_9*#`1l!oQy9!ZIt9uoKk&;v;jJk- zecx0v>&voWxZ_>QP@pHBI5OWS18hwqX}`2atyR;aj<3n^6v%1Psbnbl25CaN`OI&* zuNBM_`bN!TvI3Zlb<;28CY15!%w#G^9m4FnEy79p%bdoDyr4GIP4>Wyo%D~D`6w($ z2$L0md99SK9QS!U(&JYTN|p9NO2eCn8SpmIv*u6~$E?s=JynZGsv3f}a3_yex`L<) z?|83DUcwG%Da@tWML!!@2`Je(tn%LK$5~F@;jQNB!vU1L$dB4&Bn@XT&pnV=9R-S8 zwXj?;(P*bzOCnfv$;YQo^D*(*IvyYj>g8)=Bn30$)^pf(t_P|Pz}0M<9}UFFGkGT! znJEqR(CJo{tSU?-#a9V~qPX@chA{NBt)O{z47h|fb0L$;7=CC`st*o;U(x^ta1@I- zRi#sK+yMN)R;p}?;nQwPZHXGT$-edWe}}hOG#H?S{}Vra+$}qu<(REylE=ZluO#oe zM;^39xovZ|>lW^65l`x+Td%#wxJvD%?;3yJa?RA)->1B1#n7gGNiK45Rw#~L$F60d z$k1;#L6f8QMy#S3PMPgG(-(ei3eRjB$D|U~Vh#AE?<#|&?dc7s~3ETI=NS=1CQD|*ip_V$X z@qw(zMp1(BJ({xLbuEeARSQJ^G7VIoNX4`^3Vk}sExlo1ba6#)8g&t0a}o#t@=RyM zL<_L3Ju9!v#)KY3UxIZ1iT0JA8C3ui63ojfWuY;zpm6HaaIsgcLQK?yKR1HbFfaM33q#Nq$8bvySvYeD$8}$(k9OtkH?sG2xX+zghZ5eiGb=J&=5eRS4Uf7J^gmqRt)Gg zq+%%>DN5&Vlh`&dlOa2iR6992q427gogLZK$It4K>}zUKKgAQT!%#%UdEKX9KEKjA?K7|y!r^p!l7s+u{Z4OE_;-i2?zhcdHxm@*s|-#6WHz>mt?0st61M_1nC zcv!|9{fGxn2Da6yhg4DEb)LOBl-R8(Ri|D=a(AA5SEW_oE_n~G7MdCxDY`476&SlO zzgKG@XwXNH&X>Lu#%QGYEmisghsu|veE8Gk=DCfzF z0uR28B-fCJSBx3nCQtv~a|49VYV<=$Ix-t=@Y-~!9;^?Ps=J!<<+f>7t7jEo?N*6j z+)|_bp*7-@M2&>~c6JN-)L=fGJoPE>IAIQkckiH`malPZBll`8kfF9rHAKP3cS2Li zx+0vZ@O{;YSd?YCL9_BmI-c7oyy~QWAUum^WRkF=}y-)wP+kPmmN6DL2|B_Adt6b)wdHwc_CIvg! zEC~R!p=~*tA!!%orF-9~bC-R1Jgl>8b_*u{yCsHrI@!gcZ8*YJXE>%Lz*SdsO6&p2 z!GKR1ZseDLF}FJtCOsg<|86>|$9pcjz6+8n`9=d5-PK?v%R=EJXf{nDoSExgs<%OY(kwqrbR9G0E7Ffc?M~ zZ#@LpoMp1B)tS;Y#6aGS>@+WYrfDOZ?<=PfdP!@VqBl^$iwd~fk9j3^Hs52Q!^^79 ztFJr2^NTh8!}*M#RYTeXYi@KYg@hO-HQCTjkS~+7p%Voluiog+F||b|U|kkD*AuXsJl6#wib3ua027 z$)3K0iTdp#QyY*9d7E5lymv{C_zUX%?LAL=eluBUH4AzgMvfABwaC!Qw- zDSEU95iiuAUW>0q3r}>%C)2!LjloxJg#7qitqDUe@C3|zELhc63bKUHToa@st6xXy zR-VH`v*|2e+S$XsS=MDT8P7Y0_~$vVjF>pAr1iFYegW#C{Ko9L7p?m*O%`)b%LO@2 z0V@+Gd)JrcQAeyEge?{*-{I(m!xZ!M*;^fuvckpnEnVKmD{Qs24C|g2D$AGtoN6x8 z*Lswn3Qp&h-Jq8uIE?4sBvbMEmdnC!h{*V7YC+XhmcLMBf?306rO;QfSqJPKc06RJ zBIxyh;saRvKM~gS9CH(sFPOKRAKP#5!ZMMUyWaDa+NbwC+Rr`wGyx5y{><}mE8{Qz z`>o-Zf2JYY(iYxkV!&4-k*3`11tXXUq=@5YcBEMcW^v-`UgOxa+cUNV5#*V3NQUQm zB9Zfni7AhUS$}A|MAa+r!Se(&?=W=7Kwo42EC67Y+<44w_2{AskOce$(yf@8N|f}( zt7YkR26^pC<1A!*W5u((Aj)<3wNa-tA=fVfVgQ=SuUzjuzM^A(5W<1KBse`fW1ecY z#qEsxm1nhn$;J4|)uqYPKGxG}k}i6qU5OW!HcnMvM@N=e1C6PlDoWc&W9<+sxoi7- z*a1*EoYw*1)41MSBEJLCQHT#VEMl1kDKpRTk6UFG!J~0uRk>{xM-ea#5&X8P;Hv{> z6+Ve^S2hX-zdbS15vYH(CRWVt-RINQD7vk%Zlw1rnYuxLdEQ(peO?^?${hc1X`~iqnY*<;Jzs2)o4qMBjp%3;~?w^zO;|8|! zx=#~4B2Vvb&G_RISW{qlU1y0>SGW=5GlObbbH1W!#ha z0ZFhLkBwu(2kW(S#KF~VXzn?PUuqeng%Pu&K-GQKphD{chv$c{)_xwJ!_da{^VzeIlP3s8DQ(B=w#W#f?z+tQu^ zq|iezjP=f?nEp!Mb9|aKwdQe`16|QKDvqLx-lhm%Q>3ycGE@X$El|jxsAA2VGf*7VGyv{<@Lb=)##@p$T3Bs~i|`+lUge*^NjWD8P0bOR zFVyTxKEA@D5t}QUKJGyp3s--P(Zd`72!7?pjrA**w#we5@Nw(HEo;b0JKY-GV9HQf z)1_IkWbqf~9LhktNn59fFGSARGz(60JHsbB8ZsGs4-k|(O>Zm6a~W5&bpWP}7%e8~ z{MEYCK>d>1f5(5j$1uIj$X8fZoe2n^`etNWdgI}ruMd%=jKx-jcdN)@=l{n0f_CWY z6ObsTVYWrw{tM4DoM>h(M|~}f$YT8xe)V(@Ikr@pghS8i6omcDf7X;(`16=$o`R16 zrok!%eAcvqmd}9L+S0sHqQ=nNz8kJV^IG8H9b};SYuOWktyw_edEE9ZYfO@gD+!6 z^wTd%C9-FS24~`YOhjjqodC|2jARfWI(p|3xMDoVZhco>-=O$aUfJ$ zGfL6SWU7Vl%u+Elqbz-*qFxeJULFl_^TaZ9bb^n69UNKUS_^|2ri5Bjl6J*jz5GXh zX$0I@%_m`i5ZLM6)VU*9mV^C=>7P4afvY$F?mu3SO@QCmWIq(W?QrqMxum}Vfs=*y z3abRsrU3S03?0_ebS;x%l>X$OJg&*wH>j%}u0YPKh2Qi5-UoMPCVDhi`D z0UVX0JWx&cts#O{;D0}9fzNT&RdXz{$=Y%Zd_$LqW$Fx(Y8caHeo={5^@@WF@y%v% z^8dcp7~8vhAF@LXD8zx+CpBuX zP+C;j_I`0*{O+gU8jqt+A<9iN)KZ&M(Ohy0jN$MN#2Plyt46o$bsS$xHav2D7L{I@ zpddSE?vXzxWIUa>Lhl}gp`fT}FFKgEW_54;U|^)Vl$4kbm;IsrCVjhmi&vcpA^_x; zPu<Gf{}DZO_eSEMWz0pw1^D#V`C309 ze$VH=;YI|ceL4ZX8hy$b@-AKz;45|64pU^3=|L;D#p2k)kFZ|_gFSj&=&A2M7Ji;* zMhBCpuvO>z1{lHGJL$CIrT&yWA(9)(oKIr!3~m>Y7f}km6ZKy!RgQhxrE^$UxT%&1 zrfaq?n-HWc&p~H^HTY$%0gyZ!H*L^8u1M$)AJ0VNga@5E7-;j#-`0_w<|*|BcH#&E zS>Y<*@O571(+p?v3CusMwK!S0jL$K2kEINNi`;eBqQ{j0_yXNgUvr`hsmNv*9C~Z~ z?i3s9w7VJ)QJk>{n=+OGX4@Dqd)}C-F{wbp?C?%mv90ef32*e=faX227j8g-Z8KkI z^`#tknAEP?s1e&^Lcek>pPB5KhKbYXpW3rzY+=Q6UB%5uiHiWrBH99l(@@bpiUxN3 zH$%vtNi>n=0}zr|kF@kZqEZXp&74l}0$+4G%`yyL24JarXa;g~S_JkfNS^P1{%Cg7 z5?TLfzBf?pw(mHX2P8`}m1YDF!M24U1-v+h^-M-IH;+MMnf$KWxXXC(?QRU19$vb7 z!MkG?jrc9NB7dRJizkha@yJcJJS|4ylqsoRZ-DNST;7UDXF7xWZYD4a>1k6o@7i>uimEw8L9T zU?3P=M)}dG{c#_%w}Vzq1YA10&Z)Q7{|RPDX&|15rUjW*QS{>dEU*-Uf(*S>O<2*B z+3z9v$@J?g2OuNhN_2&p-pj=6^Q&iE#W&wWsk#K{oood=lT0{R;HJax`6|qu!YD1* znm6z~Lk!q3(B86!+n`d~%gK?+KA}*Af+@Obe(2@U$k}S_F^$zrlaL7C)C}}43?d(x z#Q%O4SmSMhM4P$Ef))QW5T(mZCg%D|cf~3^R`c`MGyp=kJ)1!hm?b?j&cMqnt0g3( zBqX7gL#b{=sl7!a{V6)>HAB5*@=GWDgDi4gg4q#UoJVHdhBXZI1_Wxbfrlh#IKdmT zf7gQm&B<)RY6q2}U{n8E)KWA(b!pEtE`OmT`V)FYxV~m$HpCk$cmtD%OlcPcDXB;| zahOm7A3&A_FoWrbnIDED$Txr>UznpIK98O2$I*8D@rpDDw~#8hYv?W3n|)mi2Bh008~(Y&4=qDFc8J0|dmK9t4EsKVN0&|5SYcHz}>LxF}5B&^da& z0!E5(76DNoP6!(jLLtKeE29&GvGeVa5;uc#s*@D9$(B*euBl3&QE$22x=2$6jU>u$ zQE#KXYE7}Cd8zzY^9R;PRPoo{)`Ue80@yA2QTJP}iJ4w+39CX>s&#*~K}ZCYDd()fW} zDn~<6273(BtwHEfn|F5~yv2|h_vF5MAs{gtK)>InvtmeQUeZn*pVt1&@ttY>P|oP` zkgnQuuS#kM(@`&?i^a2@gTAN?6V3`Il-6@Ii-Pz_j$L|Z($RLG5zfxh(ef8Z0CyD- zK(wi-`15QR>wB{t`|zX#f%DCGrY$;q=my>aQ>iUC-}1%mR{_acyOq7;9rgEU)Q% zbN1@3{feU1DaGnkp0u5YJ2f3Aei`di*dsws5uMoWC+OWWLd;1m(Ssb=wC{>kOBJWa+vAAxS0ofcT`3 zdsUcdoyb55>e00`OX8)gMfa_LSQ8MA?c&N<1+b$+N3p~?Ajt@fT+2^00$pUzIF*B-8-ZEGUBCWrk4VvGI2c|KYhKM2T7(`xv}Nq#`{l^4nOg< zp2#hxaWlB9AG$2Z(a?EY9APDx2!(3tqrUbIKGf*Y*V^#%&FT9MV$PAHfTjEN%V=qE zDedoqwJ;=F(0UK)r1bg&$8BYTw*40_;O-ubA*x|`KPPWeu>yUTh7PWq51Dj~**S{s z?QLCpI09g_$0s$-j-|x!9IBSr6o1nCmG%A6Iu;_S(&VP=|9tS_n3+qd9^g!b>EX0X z*cLw^3M%V#FVH??HRhOc1gy?oB1@1S(bz!_1s`~Ts)O!9y^3l3&JlM8A2Q*#uFnm^ z8HXLLGd!Z_=q?t&H4hCq-ob~l`6&c$H_DCFquf`##I#~@s3s6b4-^P(4!p8-H5fkO zw*Mh;fn;nI<#Vzuy_c`JJ|J1du|~9$5-3MryxGPSw+JgTZ&#g%1@PeJ7ccs7U_=Z; z^f~AEE|4gt_SpHA{}BtlG%m0UpvN0R08lsN1@L3QNG6CN0Ju*+OGMdhTW4fACPG#$q9GEJ%SM2Gu zK`X-HU3A2JfNr+io0l$02ZNBQTSppPxA@Cupy!a@h0Snm!3cYA3GUaQMGe%4nmzOXgZm*it-E>Mx%(KS7PF zZaMv``j$tBALzakoK#+<{lMpLWI9i9UPuS9JvxC=i&+SeQh(|-sKP!(RABAUuOvbp0 z>7}(Ot{3}ec?h0!HmY_M1IRKcm!p02(V}q?(vuGw6inoJ!wugsX4SZyzb_rE1`lHYWp}`)(kFlu7xC zt0r(kIxH?OuA4&1Xe907kEXR>u&+^6zUv)WJ?o|bXk`e}+TQzE1;wSBhBN}=0F)s} z@^|kbd1?n4W6al0BUkxifnU+1HsIq7fE42-8};taIko3+DS*kE()V(Rj?TP9(!8Mj zav6bR?rfYUnxEvlF+S^W6{=416nZ-;r8oGYfQnnYcM!Cj)7j|SpZfA6zo#%15PI}P-# zffwxz^$so{lYX*^eA#f)&aWsu0CqtFmYXHX372qD9y%~4A)A_Re}4bTjbVZ+y&m|A zqp8C49A);ND{B+}SqF(5|FUJS8)S1AX)x+n^cMS5)IO^uBiZ{y%EjF1wA_4Ho9Q={ z?L}+oxB)g_)4)qP+n(&G1bhHr>j^C(qZbJ7S}LYZ);vOJ%U23 zVJX{oHrIajJ$~rocJY^i0F^lR!Yq@qXj{}AKX|byBlzBUO#P~BJh=`Bvl?9ZK&xq> zjz|47ID95?Gyltqw#AAWhDG^YUn0v`UoPcBYY+l9oMkEa&w^sAc>v}rASK`38WjA6 z*mP9_pa(H24-X3NggR^`)HWVq{u+*^EjD+C_Pdn*%0Kldie=aakt|BNvQcSK1{&*@ zd)E%EwsHV6LZ{Z1S=+oU7Q^AqRjUEncjg1$(;K5pO0p^~65VW?;%qKTicoy8NQUS=5 zVq9;2j(WxDMd^GWMHS>;D3H(E+ASLjA!vN^gGsoBZ<{5&;`&v-hRVV*VFutSCF6YC z)o0e;9?wCjvq=Tus`@2BYko|$#9#q;Q2*d`rU7j%LkV72F~G2I9KrG=HPYH4dWoaJ zu*v1YJz=Bv_L-SV?H+GeX?T6K&*)|{yFG{Cy7;LOo{>gpd~$x0|2_lVrZo9uI=>(G z1%zvUc36rLo;-DM_z6eo?G0CO^?*#GB(OUF3N^#24?WANPc!v}%5Qb%&HokDCnW1* zp9*riXmFFG9zZl%8kQe!4Phjuy(0MNI9BF7Vy+O1{?RWuWrVk`vG3wTKsi_>n7ppI zM^w-W4RxangBvZ<2GN;1CqV~()Sw`wt=CcXY#^sS&$&G!8hxzSj-;`{5nml1;Gm-~ zAzYZ9U{AK+ndsP8X~Pj25W`Kq8MEkF*$HXq{NA*`1Aw178X76$-FpI-bf-~qU_Q+Z zK&^wl9jo5gR`ey>O}D2|rT7qRa@Yh4E(gf}p{67XXT%m$+FE>al;u_|`;n}k~gd0GtQ_Qp8L>^2RL_Il{r zR&A#>1}vDdFV+W16>LH@PZuRN;?Asqq1$q#WZF=@+Np_*GQFwomib`Sq^MQH}eENGKSt|%BAzR{_Vt3m^^P{ z28f(&@mDd!(yA_WJPmYxEYRk}q!xspA-5eVt|aF$%nMeBidd0Hrk3!7<-?$|mHSm( zo}WZSS5uo7^=G0z@eoX{fqQ>KRY5iiKkNKBeSKx0#=+jz=bTJ8)SP(|U1F-`ssz$k zt(KOp&JUJrL$u#yp)P`kXdoH)`cIp84glsi zuB=iJgUPoP=jNo`MWxQxy-Q;M#FSwtO+^YnN!{$M2WU!tFJSKKm1hk zsBz`e-)SKN#t@8u_xzc^kHIW%2s1CRzbA$|SCT|no0tEtILIsSd)(;bcwF>NaZ0+h zel)d#0BW)5D&?a%gEbINbk1)<| zFqdEHHUpj@uHXcBy04V(9gw4EyzCr}vle^^&uz8qcs@BsKkDd@6?|sz%jsF3zP)n3 zR)^~v7i%l<5G#Rhv#`*D-~sZklVOK%WDmk^mDR+mp=C7_)8)4V4`elotvuFFqu?pM%H-FN|WJg9lk zI~+RHiGG^bzftG_qJ}`t_CQ%whj^mJ#1K-XX08-!Fj5Ue68MaGMv?%(z|cA_!^sG| znHabP%Ms#Jeb(njDMu8kF*A-CG6bNn&q+J>oA5_X*Sq?uw!+F9-gGl958-CtP3_+W zg2v!$2cw&w-h!?|PG}c~C_+w15t5L4g}E1!V)%ks5DMEB5`DNsR$sNtO*?Vt`Uw4m zi**n)y(aoV#3Byud=&a1{n*!)JJhVX*l`km7rML z#`HZ6w&yEHuREevWN}Kq*}k(jK=+KJCEdDyyQz4_3Kk3F^(%xGgN6P;g3c@G8I{G6 z*O@nmZJhLmhuvl|(B`#$_i%}(P^!nU9%G0lX;FQxDK{V zcKSOmW5=nixe3@xXRZ!*+F$gr?!~|1< z{*Mj|1!3sLC=i!GBdS|8J7NwlGkM>0eOp-=P0WsQy>b4d;J? zpn+;DEMNw5|7gYv7Z{8paCXH43`6;^Ap`2JvVb{i{dKYdyH@GI0`!4_mdrr-RTLo2 z8Xnkpqra2@XtKrwwqOO!TvG<)um+y3X@dD%1I5<)!78nRfOSJKZaZL&8!qr^T?y>i z2^i={0EG6%{x?X}1|C>|%U_8eNWXvr-1$qlT!B0OH2=J~At(s{_tu4h6yJfWn;Kxq zK7S24aBNcotl9q`+=xH}wk)9lHMj7<%6false - - - gradle-official-repository - Gradle Official Repository - https://repo.gradle.org/gradle/libs-releases-local/ - - - - - - - io.quarkus - quarkus-bom - ${project.version} - pom - - - io.quarkus - quarkus-bom-deployment - ${project.version} - pom - - io.quarkus @@ -70,11 +47,6 @@ io.quarkus quarkus-platform-descriptor-resolver-json - - - org.apache.maven - maven-model - diff --git a/devtools/gradle/settings.gradle b/devtools/gradle/settings.gradle index 92bb81329cd94..c1491d22dda20 100644 --- a/devtools/gradle/settings.gradle +++ b/devtools/gradle/settings.gradle @@ -1 +1,14 @@ +plugins { + id "com.gradle.enterprise" version "3.1" +} + +gradleEnterprise { + buildScan { + // plugin configuration + //See also: https://docs.gradle.com/enterprise/gradle-plugin/ + termsOfServiceUrl = 'https://gradle.com/terms-of-service'; + termsOfServiceAgree = 'yes' + } +} + rootProject.name = 'quarkus-gradle-plugin' diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 66ca0a091f4be..0fd6560503181 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -100,8 +100,8 @@ private void registerTasks(Project project) { } private void verifyGradleVersion() { - if (GradleVersion.current().compareTo(GradleVersion.version("5.0")) < 0) { - throw new GradleException("Quarkus plugin requires Gradle 5.0 or later. Current version is: " + + if (GradleVersion.current().compareTo(GradleVersion.version("6.0")) < 0) { + throw new GradleException("Quarkus plugin requires Gradle 6.0 or later. Current version is: " + GradleVersion.current()); } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index ad683b007509b..49cfc22e56c8b 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -233,6 +233,7 @@ public void setDumpProxies(boolean dumpProxies) { this.dumpProxies = dumpProxies; } + @Optional @Input public String getNativeImageXmx() { return nativeImageXmx; diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 4b44d7b7c15af..853a4455d0f40 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -27,7 +27,7 @@ 3.6.2 0.7.6 - 5.6.4 + 6.0.1 From d398f6bd8816e7d94fb5f83dfb59e6a641591e30 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 25 Nov 2019 23:17:36 -0300 Subject: [PATCH 219/602] Add Gradle functional tests Reference: https://guides.gradle.org/testing-gradle-plugins/ --- devtools/gradle/build.gradle | 31 +++++++++++---- devtools/gradle/pom.xml | 11 ++++++ .../gradle/QuarkusPluginFunctionalTest.java | 39 +++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index 83db3821195cb..3371351d5693c 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -18,12 +18,6 @@ repositories { mavenCentral() } -// Add a source set for the functional test suite -sourceSets { - functionalTest { - } -} - dependencies { api gradleApi() implementation "io.quarkus:quarkus-bootstrap-core:${version}" @@ -33,7 +27,7 @@ dependencies { implementation "io.quarkus:quarkus-development-mode:${version}" implementation "io.quarkus:quarkus-creator:${version}" - testImplementation 'org.assertj:assertj-core:3.13.2' + testImplementation 'org.assertj:assertj-core:3.14.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' @@ -67,5 +61,26 @@ gradlePlugin { } } -buildScan { +// Add a source set for the functional test suite +sourceSets { + functionalTest { + } +} + +gradlePlugin.testSourceSets(sourceSets.functionalTest) +configurations.functionalTestImplementation.extendsFrom(configurations.testImplementation) +configurations.functionalTestRuntime.extendsFrom(configurations.testRuntimeOnly) + +// Add a task to run the functional tests +task functionalTest(type: Test) { + description = "Runs functional tests" + group = "verification" + useJUnitPlatform() + testClassesDirs = sourceSets.functionalTest.output.classesDirs + classpath = sourceSets.functionalTest.runtimeClasspath +} + +check { + // Run the functional tests as part of `check` + dependsOn(tasks.functionalTest) } \ No newline at end of file diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index ab68b537eddd9..9233364bb7ee4 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -19,6 +19,8 @@ ./gradlew build false + true + true @@ -77,6 +79,15 @@ + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + + true + + diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java new file mode 100644 index 0000000000000..aa1571997a7e5 --- /dev/null +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -0,0 +1,39 @@ +package io.quarkus.gradle; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +import io.quarkus.cli.commands.CreateProject; +import io.quarkus.cli.commands.writer.FileProjectWriter; +import io.quarkus.generators.BuildTool; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QuarkusPluginFunctionalTest { + + @Test + public void canRunListExtensions(@TempDir File projectRoot) throws IOException { + assertThat(new CreateProject(new FileProjectWriter(projectRoot)) + .groupId("com.acme.foo") + .artifactId("foo") + .version("1.0.0-SNAPSHOT") + .buildTool(BuildTool.GRADLE) + .doCreateProject(new HashMap<>())) + .withFailMessage("Project was not created") + .isTrue(); + + BuildResult build = GradleRunner.create() + .forwardOutput() + .withPluginClasspath() + .withArguments("listExtensions") + .withProjectDir(projectRoot) + .build(); + + assertThat(build.getOutput()).contains("Quarkus - Core"); + } +} From 3459f2c8ab3be1ddb1e3313382c6a38c5972eea7 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 29 Nov 2019 00:03:06 -0300 Subject: [PATCH 220/602] Updated Gradle templates to match plugins DSL --- .../io/quarkus/gradle/tasks/QuarkusDev.java | 18 ---- .../basic-rest/java/build.gradle-template.ftl | 17 +--- .../java/settings.gradle-template.ftl | 16 +--- .../kotlin/build.gradle-template.ftl | 17 +--- .../kotlin/settings.gradle-template.ftl | 16 +--- .../scala/build.gradle-template.ftl | 17 +--- .../scala/settings.gradle-template.ftl | 16 +--- .../cli/commands/file/GradleBuildFile.java | 40 +++------ .../cli/commands/CreateProjectTest.java | 90 +++++++++---------- 9 files changed, 66 insertions(+), 181 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 4bd20de12cfac..1ce37de3b5113 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -36,7 +36,6 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.ProjectDependency; -import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; @@ -249,11 +248,6 @@ public void startDev() { wiringClassesDirectory.mkdirs(); addToClassPaths(classPathManifest, context, wiringClassesDirectory); - //we also want to add the maven plugin jar to the class path - //this allows us to just directly use classes, without messing around copying them - //to the runner jar - addGradlePluginDeps(classPathManifest, context); - //now we need to build a temporary jar to actually run File tempFile = new File(getBuildDir(), extension.finalName() + "-dev.jar"); @@ -394,18 +388,6 @@ private void copyOutputToConsole(InputStream is) { } } - private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext context) { - Configuration conf = getProject().getBuildscript().getConfigurations().getByName("classpath"); - ResolvedDependency quarkusDep = conf.getResolvedConfiguration().getFirstLevelModuleDependencies().stream() - .filter(rd -> "quarkus-gradle-plugin".equals(rd.getModuleName())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Unable to find quarkus-gradle-plugin dependency")); - - quarkusDep.getAllModuleArtifacts().stream() - .map(ra -> ra.getFile()) - .forEach(f -> addToClassPaths(classPathManifest, context, f)); - } - private void addToClassPaths(StringBuilder classPathManifest, DevModeContext context, File file) { if (filesIncludedInClasspath.add(file)) { getProject().getLogger().info("Adding dependency {}", file); diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/build.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/build.gradle-template.ftl index baf04e5c7e4e4..1cf1fab00efa4 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/build.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/build.gradle-template.ftl @@ -1,20 +1,8 @@ -// this block is necessary to make enforcedPlatform work for Quarkus plugin available -// only locally (snapshot) that is also importing the Quarkus BOM -buildscript { - repositories { - mavenLocal() - } - dependencies { - classpath "io.quarkus:quarkus-gradle-plugin:${quarkusPluginVersion}" - } -} - plugins { id 'java' + id 'io.quarkus' } -apply plugin: 'io.quarkus' - repositories { mavenLocal() mavenCentral() @@ -26,9 +14,6 @@ dependencies { testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' - - nativeTestImplementation 'io.quarkus:quarkus-junit5' - nativeTestImplementation 'io.rest-assured:rest-assured' } group '${project_groupId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl index 6dd13f2639657..e8e2968f300b6 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl @@ -1,16 +1,6 @@ pluginManagement { - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == 'io.quarkus') { - useModule("io.quarkus:quarkus-gradle-plugin:${quarkus_version}") - } - } - } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } - rootProject.name='${project_artifactId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl index 4386c38fc3c2f..ecd42c7355c36 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl @@ -1,21 +1,9 @@ -// this block is necessary to make enforcedPlatform work for Quarkus plugin available -// only locally (snapshot) that is also importing the Quarkus BOM -buildscript { - repositories { - mavenLocal() - } - dependencies { - classpath "io.quarkus:quarkus-gradle-plugin:${quarkusPluginVersion}" - } -} - plugins { id 'org.jetbrains.kotlin.jvm' version "${kotlin_version}" id "org.jetbrains.kotlin.plugin.allopen" version "${kotlin_version}" + id 'io.quarkus' } -apply plugin: 'io.quarkus' - repositories { mavenLocal() mavenCentral() @@ -28,9 +16,6 @@ dependencies { testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' - - nativeTestImplementation 'io.quarkus:quarkus-junit5' - nativeTestImplementation 'io.rest-assured:rest-assured' } group '${project_groupId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl index 6dd13f2639657..e8e2968f300b6 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl @@ -1,16 +1,6 @@ pluginManagement { - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == 'io.quarkus') { - useModule("io.quarkus:quarkus-gradle-plugin:${quarkus_version}") - } - } - } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } - rootProject.name='${project_artifactId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl index 43140ec6b6f14..821abd974352e 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl @@ -1,20 +1,8 @@ -// this block is necessary to make enforcedPlatform work for Quarkus plugin available -// only locally (snapshot) that is also importing the Quarkus BOM -buildscript { - repositories { - mavenLocal() - } - dependencies { - classpath "io.quarkus:quarkus-gradle-plugin:${quarkusPluginVersion}" - } -} - plugins { id 'scala' + id 'io.quarkus' } -apply plugin: 'io.quarkus' - repositories { mavenLocal() mavenCentral() @@ -26,9 +14,6 @@ dependencies { testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' - - nativeTestImplementation 'io.quarkus:quarkus-junit5' - nativeTestImplementation 'io.rest-assured:rest-assured' } group '${project_groupId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl index 6dd13f2639657..e8e2968f300b6 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl @@ -1,16 +1,6 @@ pluginManagement { - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == 'io.quarkus') { - useModule("io.quarkus:quarkus-gradle-plugin:${quarkus_version}") - } - } - } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } - rootProject.name='${project_artifactId}' diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java index 63ef0b47a871a..bb6bf433b16f1 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java @@ -49,22 +49,11 @@ public void completeFile(String groupId, String artifactId, String version, Quar private void completeBuildContent(String groupId, String version, QuarkusPlatformDescriptor platform, Properties props) throws IOException { final String buildContent = getModel().getBuildContent(); StringBuilder res = new StringBuilder(buildContent); - if (!buildContent.contains("io.quarkus:quarkus-gradle-plugin")) { - res.append(System.lineSeparator()); - res.append("buildscript {").append(System.lineSeparator()); - res.append(" repositories {").append(System.lineSeparator()); - res.append(" mavenLocal()").append(System.lineSeparator()); - res.append(" }").append(System.lineSeparator()); - res.append(" dependencies {").append(System.lineSeparator()); - res.append(" classpath \"io.quarkus:quarkus-gradle-plugin:") - .append(ToolsUtils.getPluginVersion(props)) - .append("\"") - .append(System.lineSeparator()); - res.append(" }").append(System.lineSeparator()); - res.append("}").append(System.lineSeparator()); - } - if (!buildContent.contains("apply plugin: 'io.quarkus'") && !buildContent.contains("id 'io.quarkus'")) { - res.append(System.lineSeparator()).append("apply plugin: 'io.quarkus'").append(System.lineSeparator()); + if (!buildContent.contains("id 'io.quarkus'")) { + res.append("plugins {"); + res.append(System.lineSeparator()).append(" id 'java'").append(System.lineSeparator()); + res.append(System.lineSeparator()).append(" id 'io.quarkus'").append(System.lineSeparator()); + res.append("}"); } if (!containsBOM(platform.getBomGroupId(), platform.getBomArtifactId())) { res.append(System.lineSeparator()); @@ -92,22 +81,12 @@ private void completeBuildContent(String groupId, String version, QuarkusPlatfor private void completeSettingsContent(String artifactId) throws IOException { final String settingsContent = getModel().getSettingsContent(); - final StringBuilder res = new StringBuilder(settingsContent); - if (!settingsContent.contains("io.quarkus:quarkus-gradle-plugin")) { + final StringBuilder res = new StringBuilder(); + if (!settingsContent.contains("id 'io.quarkus'")) { res.append(System.lineSeparator()); res.append("pluginManagement {").append(System.lineSeparator()); - res.append(" repositories {").append(System.lineSeparator()); - res.append(" mavenLocal()").append(System.lineSeparator()); - res.append(" mavenCentral()").append(System.lineSeparator()); - res.append(" gradlePluginPortal()").append(System.lineSeparator()); - res.append(" }").append(System.lineSeparator()); - res.append(" resolutionStrategy {").append(System.lineSeparator()); - res.append(" eachPlugin {").append(System.lineSeparator()); - res.append(" if (requested.id.id == 'io.quarkus') {").append(System.lineSeparator()); - res.append(" useModule(\"io.quarkus:quarkus-gradle-plugin:${quarkusPluginVersion}\")") - .append(System.lineSeparator()); - res.append(" }").append(System.lineSeparator()); - res.append(" }").append(System.lineSeparator()); + res.append(" plugins {").append(System.lineSeparator()); + res.append(" id 'io.quarkus' version \"${quarkusPluginVersion}\"").append(System.lineSeparator()); res.append(" }").append(System.lineSeparator()); res.append("}").append(System.lineSeparator()); } @@ -115,6 +94,7 @@ private void completeSettingsContent(String artifactId) throws IOException { res.append(System.lineSeparator()).append("rootProject.name='").append(artifactId).append("'") .append(System.lineSeparator()); } + res.append(settingsContent); getModel().setSettingsContent(res.toString()); } diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java index 8fa89db5affc8..e70d3b4b29908 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java @@ -1,8 +1,5 @@ package io.quarkus.cli.commands; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.contentOf; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -28,16 +25,19 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import io.quarkus.cli.commands.file.GradleBuildFile; +import io.quarkus.cli.commands.writer.FileProjectWriter; +import io.quarkus.cli.commands.writer.ZipProjectWriter; +import io.quarkus.maven.utilities.MojoUtils; import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import io.quarkus.cli.commands.file.GradleBuildFile; -import io.quarkus.cli.commands.writer.FileProjectWriter; -import io.quarkus.cli.commands.writer.ZipProjectWriter; -import io.quarkus.maven.utilities.MojoUtils; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.junit.jupiter.api.Assertions.assertTrue; public class CreateProjectTest extends PlatformAwareTestBase { @Test @@ -48,12 +48,12 @@ public void create() throws IOException { .artifactId("basic-rest") .version("1.0.0-SNAPSHOT"); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); final File gitignore = new File(file, ".gitignore"); - Assertions.assertTrue(gitignore.exists()); + assertTrue(gitignore.exists()); final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); - Assertions.assertTrue(gitignoreContent.contains("\ntarget/\n")); + assertTrue(gitignoreContent.contains("\ntarget/\n")); } @Test @@ -66,17 +66,17 @@ public void createGradle() throws IOException { .version("1.0.0-SNAPSHOT") .buildFile(new GradleBuildFile(writer)); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); final File gitignore = new File(file, ".gitignore"); - Assertions.assertTrue(gitignore.exists()); + assertTrue(gitignore.exists()); final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); Assertions.assertFalse(gitignoreContent.contains("\ntarget/\n")); - Assertions.assertTrue(gitignoreContent.contains("\nbuild/")); - Assertions.assertTrue(gitignoreContent.contains("\n.gradle/\n")); + assertTrue(gitignoreContent.contains("\nbuild/")); + assertTrue(gitignoreContent.contains("\n.gradle/\n")); - assertThat(new File(file, "README.md")).exists(); - assertThat(contentOf(new File(file, "README.md"), "UTF-8")).contains("./gradlew"); + assertThat(new File(file, "README.md")).exists(); + assertThat(contentOf(new File(file, "README.md"), "UTF-8")).contains("./gradlew"); } @Test @@ -96,23 +96,21 @@ public void createGradleOnExisting() throws IOException { .version("1.0.0-SNAPSHOT") .buildFile(new GradleBuildFile(writer)); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); final File gitignore = new File(testDir, ".gitignore"); - Assertions.assertTrue(gitignore.exists()); + assertTrue(gitignore.exists()); final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); Assertions.assertFalse(gitignoreContent.contains("\ntarget/\n")); - Assertions.assertTrue(gitignoreContent.contains("\nbuild/")); - Assertions.assertTrue(gitignoreContent.contains("\n.gradle/\n")); - - assertThat(contentOf(new File(testDir, "settings.gradle"), "UTF-8")) - .containsIgnoringCase("io.quarkus:quarkus-gradle-plugin"); + assertTrue(gitignoreContent.contains("\nbuild/")); + assertTrue(gitignoreContent.contains("\n.gradle/\n")); assertThat(contentOf(new File(testDir, "build.gradle"), "UTF-8")) + .contains("id 'io.quarkus'") .contains("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"); final Properties props = new Properties(); - try(InputStream is = Files.newInputStream(testDir.toPath().resolve("gradle.properties"))) { + try (InputStream is = Files.newInputStream(testDir.toPath().resolve("gradle.properties"))) { props.load(is); } Assertions.assertEquals(getBomGroupId(), props.get("quarkusPlatformGroupId")); @@ -120,8 +118,8 @@ public void createGradleOnExisting() throws IOException { Assertions.assertEquals(getBomVersion(), props.get("quarkusPlatformVersion")); assertThat(new File(testDir, "README.md")).exists(); - assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./gradlew"); - } + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./gradlew"); + } @Test public void createOnTopPomWithoutResource() throws IOException { @@ -140,7 +138,7 @@ public void createOnTopPomWithoutResource() throws IOException { .artifactId("wrong") .version("1.0.0-SNAPSHOT"); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); @@ -148,8 +146,8 @@ public void createOnTopPomWithoutResource() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); - assertThat(new File(testDir, "README.md")).exists(); - assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).isFile(); assertThat(new File(testDir, "src/main/java")).isDirectory().matches(f -> { @@ -184,7 +182,7 @@ public void createOnTopPomWithResource() throws IOException { .className("org.foo.MyResource") .version("1.0.0-SNAPSHOT"); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); @@ -192,8 +190,8 @@ public void createOnTopPomWithResource() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); - assertThat(new File(testDir, "README.md")).exists(); - assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).exists(); assertThat(new File(testDir, "src/main/java")).isDirectory(); @@ -229,7 +227,7 @@ public void createOnTopPomWithSpringController() throws IOException { .version("1.0.0-SNAPSHOT") .extensions(extensions); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), MojoUtils.TEMPLATE_PROPERTY_QUARKUS_PLUGIN_VERSION_VALUE, getPluginGroupId()); @@ -237,8 +235,8 @@ public void createOnTopPomWithSpringController() throws IOException { assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); - assertThat(new File(testDir, "README.md")).exists(); - assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); + assertThat(new File(testDir, "README.md")).exists(); + assertThat(contentOf(new File(testDir, "README.md"), "UTF-8")).contains("./mvnw"); assertThat(new File(testDir, "src/main/resources/application.properties")).exists(); assertThat(new File(testDir, "src/main/resources/META-INF/resources/index.html")).exists(); assertThat(new File(testDir, "src/main/java")).isDirectory(); @@ -266,11 +264,11 @@ public void createNewWithCustomizations() throws IOException { properties.put("className", "org.acme.MyResource"); properties.put("extensions", "commons-io:commons-io:2.5"); - Assertions.assertTrue(new CreateProject(new FileProjectWriter(testDir)).groupId("org.acme") - .artifactId("acme") - .version("1.0.0-SNAPSHOT") - .className("org.acme.MyResource") - .doCreateProject(properties)); + assertTrue(new CreateProject(new FileProjectWriter(testDir)).groupId("org.acme") + .artifactId("acme") + .version("1.0.0-SNAPSHOT") + .className("org.acme.MyResource") + .doCreateProject(properties)); assertThat(new File(testDir, "pom.xml")).isFile(); assertThat(new File(testDir, "src/main/java/org/acme/MyResource.java")).isFile(); @@ -338,14 +336,14 @@ public void createZip() throws IOException { file.mkdirs(); File zipFile = new File(file, "project.zip"); try (FileOutputStream fos = new FileOutputStream(zipFile); - ZipOutputStream zos = new ZipOutputStream(fos); - ZipProjectWriter zipWriter = new ZipProjectWriter(zos)) { + ZipOutputStream zos = new ZipOutputStream(fos); + ZipProjectWriter zipWriter = new ZipProjectWriter(zos)) { final CreateProject createProject = new CreateProject(zipWriter).groupId("io.quarkus") .artifactId("basic-rest") .version("1.0.0-SNAPSHOT"); - Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + assertTrue(createProject.doCreateProject(new HashMap<>())); } - Assertions.assertTrue(zipFile.exists()); + assertTrue(zipFile.exists()); File unzipProject = new File(file, "unzipProject"); try (FileInputStream fis = new FileInputStream(zipFile); ZipInputStream zis = new ZipInputStream(fis)) { ZipEntry zipEntry = zis.getNextEntry(); @@ -368,9 +366,9 @@ public void createZip() throws IOException { zis.closeEntry(); } final File gitignore = new File(unzipProject, ".gitignore"); - Assertions.assertTrue(gitignore.exists()); + assertTrue(gitignore.exists()); final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); - Assertions.assertTrue(gitignoreContent.contains("\ntarget/\n")); + assertTrue(gitignoreContent.contains("\ntarget/\n")); } private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { From 21e77312b60ca0c4863c2278dfc295e374f36acf Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 29 Nov 2019 07:57:28 -0300 Subject: [PATCH 221/602] Installing plugin in maven local repo --- devtools/gradle/pom.xml | 36 +++++++++++++++++-- .../io/quarkus/gradle/tasks/QuarkusDev.java | 18 ++++++++++ .../java/settings.gradle-template.ftl | 11 ++++-- .../kotlin/settings.gradle-template.ftl | 11 ++++-- .../scala/settings.gradle-template.ftl | 11 ++++-- .../cli/commands/file/GradleBuildFile.java | 5 +++ 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index 9233364bb7ee4..31ff3fc02b485 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-gradle-plugin + io.quarkus.gradle.plugin pom Quarkus - Gradle Plugin Quarkus - Gradle Plugin @@ -19,8 +19,6 @@ ./gradlew build false - true - true @@ -79,6 +77,38 @@ + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + package + + attach-artifact + + + + + build/libs/quarkus-gradle-plugin-${project.version}.jar + jar + + + build/libs/quarkus-gradle-plugin-${project.version}-javadoc.jar + jar + javadoc + + + build/libs/quarkus-gradle-plugin-${project.version}-sources.jar + jar + sources + + + ${skip.gradle.build} + + + + org.sonatype.plugins diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 1ce37de3b5113..0ecd1f9264eb2 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -36,6 +36,7 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; @@ -248,6 +249,11 @@ public void startDev() { wiringClassesDirectory.mkdirs(); addToClassPaths(classPathManifest, context, wiringClassesDirectory); + //we also want to add the maven plugin jar to the class path + //this allows us to just directly use classes, without messing around copying them + //to the runner jar + addGradlePluginDeps(classPathManifest, context); + //now we need to build a temporary jar to actually run File tempFile = new File(getBuildDir(), extension.finalName() + "-dev.jar"); @@ -388,6 +394,18 @@ private void copyOutputToConsole(InputStream is) { } } + private void addGradlePluginDeps(StringBuilder classPathManifest, DevModeContext context) { + Configuration conf = getProject().getBuildscript().getConfigurations().getByName("classpath"); + ResolvedDependency quarkusDep = conf.getResolvedConfiguration().getFirstLevelModuleDependencies().stream() + .filter(rd -> "io.quarkus.gradle.plugin".equals(rd.getModuleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Unable to find quarkus-gradle-plugin dependency")); + + quarkusDep.getAllModuleArtifacts().stream() + .map(ra -> ra.getFile()) + .forEach(f -> addToClassPaths(classPathManifest, context, f)); + } + private void addToClassPaths(StringBuilder classPathManifest, DevModeContext context, File file) { if (filesIncludedInClasspath.add(file)) { getProject().getLogger().info("Adding dependency {}", file); diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl index e8e2968f300b6..fcc7b839cf47a 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/java/settings.gradle-template.ftl @@ -1,6 +1,11 @@ pluginManagement { - plugins { - id 'io.quarkus' version "${quarkusPluginVersion}" - } + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } rootProject.name='${project_artifactId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl index e8e2968f300b6..fcc7b839cf47a 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/settings.gradle-template.ftl @@ -1,6 +1,11 @@ pluginManagement { - plugins { - id 'io.quarkus' version "${quarkusPluginVersion}" - } + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } rootProject.name='${project_artifactId}' diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl index e8e2968f300b6..fcc7b839cf47a 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/settings.gradle-template.ftl @@ -1,6 +1,11 @@ pluginManagement { - plugins { - id 'io.quarkus' version "${quarkusPluginVersion}" - } + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } } rootProject.name='${project_artifactId}' diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java index bb6bf433b16f1..804ec413d3fdd 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/cli/commands/file/GradleBuildFile.java @@ -85,6 +85,11 @@ private void completeSettingsContent(String artifactId) throws IOException { if (!settingsContent.contains("id 'io.quarkus'")) { res.append(System.lineSeparator()); res.append("pluginManagement {").append(System.lineSeparator()); + res.append(" repositories {").append(System.lineSeparator()); + res.append(" mavenLocal()").append(System.lineSeparator()); + res.append(" mavenCentral()").append(System.lineSeparator()); + res.append(" gradlePluginPortal()").append(System.lineSeparator()); + res.append(" }").append(System.lineSeparator()); res.append(" plugins {").append(System.lineSeparator()); res.append(" id 'io.quarkus' version \"${quarkusPluginVersion}\"").append(System.lineSeparator()); res.append(" }").append(System.lineSeparator()); From f6117f4e0382932ca8234e0edecb8cbe5fd41fe4 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 2 Dec 2019 13:35:34 -0300 Subject: [PATCH 222/602] Fix buildNative not being called in gradle build --- .../java/io/quarkus/gradle/QuarkusPlugin.java | 95 +++++++++++-------- .../gradle/tasks/QuarkusTestNative.java | 4 +- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 0fd6560503181..e423054cb991f 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -8,11 +8,11 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.SourceSetOutput; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.testing.Test; import org.gradle.util.GradleVersion; @@ -26,11 +26,23 @@ import io.quarkus.gradle.tasks.QuarkusTestConfig; import io.quarkus.gradle.tasks.QuarkusTestNative; -/** - * @author Ståle Pedersen - */ public class QuarkusPlugin implements Plugin { + public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions"; + public static final String ADD_EXTENSION_TASK_NAME = "addExtension"; + public static final String QUARKUS_BUILD_TASK_NAME = "quarkusBuild"; + public static final String GENERATE_CONFIG_TASK_NAME = "generateConfig"; + public static final String QUARKUS_DEV_TASK_NAME = "quarkusDev"; + public static final String BUILD_NATIVE_TASK_NAME = "buildNative"; + public static final String TEST_NATIVE_TASK_NAME = "testNative"; + public static final String QUARKUS_TEST_CONFIG_TASK_NAME = "quarkusTestConfig"; + + // this name has to be the same as the directory in which the tests reside + public static final String NATIVE_TEST_SOURCE_SET_NAME = "native-test"; + + public static final String NATIVE_TEST_IMPLEMENTATION_CONFIGURATION_NAME = "nativeTestImplementation"; + public static final String NATIVE_TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "nativeTestRuntimeOnly"; + @Override public void apply(Project project) { verifyGradleVersion(); @@ -42,12 +54,14 @@ public void apply(Project project) { private void registerTasks(Project project) { TaskContainer tasks = project.getTasks(); - tasks.create("listExtensions", QuarkusListExtensions.class); - tasks.create("addExtension", QuarkusAddExtension.class); + tasks.create(LIST_EXTENSIONS_TASK_NAME, QuarkusListExtensions.class); + tasks.create(ADD_EXTENSION_TASK_NAME, QuarkusAddExtension.class); + tasks.create(GENERATE_CONFIG_TASK_NAME, QuarkusGenerateConfig.class); - Task quarkusBuild = tasks.create("quarkusBuild", QuarkusBuild.class); - Task quarkusGenerateConfig = tasks.create("generateConfig", QuarkusGenerateConfig.class); - Task quarkusDev = tasks.create("quarkusDev", QuarkusDev.class); + Task quarkusBuild = tasks.create(QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class); + Task quarkusDev = tasks.create(QUARKUS_DEV_TASK_NAME, QuarkusDev.class); + Task buildNative = tasks.create(BUILD_NATIVE_TASK_NAME, QuarkusNative.class); + Task quarkusTestConfig = tasks.create(QUARKUS_TEST_CONFIG_TASK_NAME, QuarkusTestConfig.class); project.getPlugins().withType( BasePlugin.class, @@ -57,40 +71,39 @@ private void registerTasks(Project project) { javaPlugin -> { Task classesTask = tasks.getByName(JavaPlugin.CLASSES_TASK_NAME); quarkusDev.dependsOn(classesTask); - quarkusBuild.dependsOn(classesTask); - - Task jarTask = tasks.getByName(JavaPlugin.JAR_TASK_NAME); - quarkusBuild.dependsOn(jarTask); + quarkusBuild.dependsOn(classesTask, tasks.getByName(JavaPlugin.JAR_TASK_NAME)); + + buildNative.dependsOn(tasks.getByName(BasePlugin.ASSEMBLE_TASK_NAME)); + + SourceSetContainer sourceSets = project.getConvention().findPlugin(JavaPluginConvention.class) + .getSourceSets(); + SourceSet nativeTestSourceSet = sourceSets.create(NATIVE_TEST_SOURCE_SET_NAME); + SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSet testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); + + nativeTestSourceSet.setCompileClasspath( + nativeTestSourceSet.getCompileClasspath() + .plus(mainSourceSet.getCompileClasspath()) + .plus(testSourceSet.getCompileClasspath())); + + nativeTestSourceSet.setRuntimeClasspath( + nativeTestSourceSet.getRuntimeClasspath() + .plus(mainSourceSet.getRuntimeClasspath()) + .plus(testSourceSet.getRuntimeClasspath())); + + // create a custom configuration to be used for the dependencies of the testNative task + ConfigurationContainer configurations = project.getConfigurations(); + configurations.maybeCreate(NATIVE_TEST_IMPLEMENTATION_CONFIGURATION_NAME) + .extendsFrom(configurations.findByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME)); + configurations.maybeCreate(NATIVE_TEST_RUNTIME_ONLY_CONFIGURATION_NAME) + .extendsFrom(configurations.findByName(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME)); + + Task testNative = tasks.create(TEST_NATIVE_TASK_NAME, QuarkusTestNative.class); + testNative.dependsOn(buildNative); + testNative.setShouldRunAfter(Collections.singletonList(tasks.findByName(JavaPlugin.TEST_TASK_NAME))); + tasks.getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(testNative); }); - Task buildNative = tasks.create("buildNative", QuarkusNative.class); - - // set up the source set for the testNative - JavaPluginConvention javaPlugin = project.getConvention().findPlugin(JavaPluginConvention.class); - if (javaPlugin != null) { - buildNative.dependsOn(tasks.getByName(BasePlugin.ASSEMBLE_TASK_NAME)); - - SourceSetContainer sourceSets = javaPlugin.getSourceSets(); - SourceSet nativeTestSourceSet = sourceSets.create("native-test"); // this name has to be the same as the directory in which the tests reside - SourceSetOutput mainSourceSetOutput = sourceSets.getByName("main").getOutput(); - SourceSetOutput testSourceSetOutput = sourceSets.getByName("test").getOutput(); - nativeTestSourceSet.setCompileClasspath( - nativeTestSourceSet.getCompileClasspath().plus(mainSourceSetOutput).plus(testSourceSetOutput)); - nativeTestSourceSet.setRuntimeClasspath( - nativeTestSourceSet.getRuntimeClasspath().plus(mainSourceSetOutput).plus(testSourceSetOutput)); - - // create a custom configuration to be used for the dependencies of the testNative task - ConfigurationContainer configurations = project.getConfigurations(); - configurations.maybeCreate("nativeTestImplementation").extendsFrom(configurations.findByName("implementation")); - configurations.maybeCreate("nativeTestRuntimeOnly").extendsFrom(configurations.findByName("runtimeOnly")); - - Task testNative = tasks.create("testNative", QuarkusTestNative.class).dependsOn(buildNative); - testNative.setShouldRunAfter(Collections.singletonList(tasks.findByName("test"))); - - tasks.getByName("check").dependsOn(testNative); - } - - final QuarkusTestConfig quarkusTestConfig = tasks.create("quarkusTestConfig", QuarkusTestConfig.class); tasks.withType(Test.class).forEach(t -> { // Quarkus test configuration task which should be executed before any Quarkus test t.dependsOn(quarkusTestConfig); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestNative.java index c043cdfad0c31..45217477ea613 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTestNative.java @@ -5,6 +5,8 @@ import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.testing.Test; +import io.quarkus.gradle.QuarkusPlugin; + public class QuarkusTestNative extends Test { public QuarkusTestNative() { @@ -13,7 +15,7 @@ public QuarkusTestNative() { JavaPluginConvention javaPlugin = getProject().getConvention().getPlugin(JavaPluginConvention.class); SourceSetContainer sourceSets = javaPlugin.getSourceSets(); - SourceSet sourceSet = sourceSets.findByName("native-test"); + SourceSet sourceSet = sourceSets.findByName(QuarkusPlugin.NATIVE_TEST_SOURCE_SET_NAME); setTestClassesDirs(sourceSet.getOutput().getClassesDirs()); setClasspath(sourceSet.getRuntimeClasspath()); From aaf4352e1ca88d8052163f81e2e3c56c1e0abef6 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 2 Dec 2019 16:29:55 -0300 Subject: [PATCH 223/602] Add more Gradle tests --- .../gradle/QuarkusPluginFunctionalTest.java | 38 ++++++++++++++----- .../io/quarkus/gradle/QuarkusPluginTest.java | 7 ++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index aa1571997a7e5..e5b7cc40b6e44 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -9,6 +9,7 @@ import io.quarkus.generators.BuildTool; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -18,14 +19,7 @@ public class QuarkusPluginFunctionalTest { @Test public void canRunListExtensions(@TempDir File projectRoot) throws IOException { - assertThat(new CreateProject(new FileProjectWriter(projectRoot)) - .groupId("com.acme.foo") - .artifactId("foo") - .version("1.0.0-SNAPSHOT") - .buildTool(BuildTool.GRADLE) - .doCreateProject(new HashMap<>())) - .withFailMessage("Project was not created") - .isTrue(); + createProject(projectRoot); BuildResult build = GradleRunner.create() .forwardOutput() @@ -34,6 +28,32 @@ public void canRunListExtensions(@TempDir File projectRoot) throws IOException { .withProjectDir(projectRoot) .build(); + assertThat(build.task(":listExtensions").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(build.getOutput()).contains("Quarkus - Core"); } -} + + @Test + public void canBuild(@TempDir File projectRoot) throws IOException { + createProject(projectRoot); + + BuildResult build = GradleRunner.create() + .forwardOutput() + .withPluginClasspath() + .withArguments("quarkusBuild") + .withProjectDir(projectRoot) + .build(); + + assertThat(build.task(":quarkusBuild").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + + private void createProject(@TempDir File projectRoot) throws IOException { + assertThat(new CreateProject(new FileProjectWriter(projectRoot)) + .groupId("com.acme.foo") + .artifactId("foo") + .version("1.0.0-SNAPSHOT") + .buildTool(BuildTool.GRADLE) + .doCreateProject(new HashMap<>())) + .withFailMessage("Project was not created") + .isTrue(); + } +} \ No newline at end of file diff --git a/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 3270b0c674089..450cf057f435d 100644 --- a/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -1,5 +1,6 @@ package io.quarkus.gradle; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,7 +34,7 @@ public void shouldMakeAssembleDependOnQuarkusBuild() { TaskContainer tasks = project.getTasks(); - assertTrue(tasks.getByName("assemble").getDependsOn().contains(tasks.getByName("quarkusBuild"))); + assertThat(tasks.getByName("assemble").getDependsOn()).contains(tasks.getByName("quarkusBuild")); } @Test @@ -44,7 +45,7 @@ public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { TaskContainer tasks = project.getTasks(); - assertTrue(tasks.getByName("quarkusBuild").getDependsOn().contains(tasks.getByName("classes"))); - assertTrue(tasks.getByName("quarkusDev").getDependsOn().contains(tasks.getByName("classes"))); + assertThat(tasks.getByName("quarkusBuild").getDependsOn()).contains(tasks.getByName("classes")); + assertThat(tasks.getByName("quarkusDev").getDependsOn()).contains(tasks.getByName("classes")); } } From f3fe71143c33003dda185ffd762196101e00b780 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 10:27:04 -0300 Subject: [PATCH 224/602] Remove GradleDependencyArtifact and QuarkusExtDependency --- .../gradle/AppModelGradleResolver.java | 10 +- .../gradle/GradleDependencyArtifact.java | 117 ------------------ .../quarkus/gradle/QuarkusExtDependency.java | 46 ------- 3 files changed, 6 insertions(+), 167 deletions(-) delete mode 100644 devtools/gradle/src/main/java/io/quarkus/gradle/GradleDependencyArtifact.java delete mode 100644 devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java index dfce575967b4c..5a2acc712ff99 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java @@ -25,6 +25,8 @@ import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.file.RegularFile; import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; +import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact; +import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency; import org.gradle.api.provider.Provider; import org.gradle.jvm.tasks.Jar; @@ -74,12 +76,12 @@ public void relink(AppArtifact arg0, Path arg1) throws AppModelResolverException public Path resolve(AppArtifact appArtifact) throws AppModelResolverException { if (!appArtifact.isResolved()) { - final GradleDependencyArtifact dep = new GradleDependencyArtifact(); + final DefaultDependencyArtifact dep = new DefaultDependencyArtifact(); dep.setExtension(appArtifact.getType()); dep.setType(appArtifact.getType()); dep.setName(appArtifact.getArtifactId()); - final QuarkusExtDependency gradleDep = new QuarkusExtDependency(appArtifact.getGroupId(), + final DefaultExternalModuleDependency gradleDep = new DefaultExternalModuleDependency(appArtifact.getGroupId(), appArtifact.getArtifactId(), appArtifact.getVersion(), null); gradleDep.addArtifact(dep); @@ -205,7 +207,7 @@ private Dependency processQuarkusDir(ResolvedArtifact a, Path quarkusDir) { String value = extProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); final String[] split = value.split(":"); - return new QuarkusExtDependency(split[0], split[1], split[2], null); + return new DefaultExternalModuleDependency(split[0], split[1], split[2], null); } private Properties resolveDescriptor(final Path path) { @@ -222,4 +224,4 @@ private Properties resolveDescriptor(final Path path) { } return rtProps; } -} \ No newline at end of file +} diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/GradleDependencyArtifact.java b/devtools/gradle/src/main/java/io/quarkus/gradle/GradleDependencyArtifact.java deleted file mode 100644 index 2c3ad51f02b3f..0000000000000 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/GradleDependencyArtifact.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.quarkus.gradle; - -import org.gradle.api.artifacts.DependencyArtifact; - -public class GradleDependencyArtifact implements DependencyArtifact { - - private String classifier; - private String extension; - private String name; - private String type; - private String url; - - @Override - public String getClassifier() { - return classifier; - } - - @Override - public String getExtension() { - return extension; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getType() { - return type; - } - - @Override - public String getUrl() { - return url; - } - - @Override - public void setClassifier(String arg0) { - this.classifier = arg0; - } - - @Override - public void setExtension(String arg0) { - this.extension = arg0; - } - - @Override - public void setName(String arg0) { - this.name = arg0; - } - - @Override - public void setType(String arg0) { - this.type = arg0; - } - - @Override - public void setUrl(String arg0) { - this.url = arg0; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((classifier == null) ? 0 : classifier.hashCode()); - result = prime * result + ((extension == null) ? 0 : extension.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - result = prime * result + ((url == null) ? 0 : url.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - GradleDependencyArtifact other = (GradleDependencyArtifact) obj; - if (classifier == null) { - if (other.classifier != null) - return false; - } else if (!classifier.equals(other.classifier)) - return false; - if (extension == null) { - if (other.extension != null) - return false; - } else if (!extension.equals(other.extension)) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - if (url == null) { - if (other.url != null) - return false; - } else if (!url.equals(other.url)) - return false; - return true; - } - - @Override - public String toString() { - return "GradleDepArtifact [classifier=" + classifier + ", extension=" + extension + ", name=" + name + ", type=" + type - + ", url=" + url + "]"; - } -} diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java deleted file mode 100644 index 48c10ab328fa5..0000000000000 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.quarkus.gradle; - -import java.util.Set; - -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.DependencyArtifact; -import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; -import org.gradle.api.internal.artifacts.dependencies.AbstractExternalModuleDependency; - -public class QuarkusExtDependency extends AbstractExternalModuleDependency { - - private final String group; - private final String name; - private final String version; - private final String configuration; - - public QuarkusExtDependency(String group, String name, String version, String configuration) { - super(DefaultModuleIdentifier.newId(group, name), version, configuration); - this.group = group; - this.name = name; - this.version = version; - this.configuration = configuration; - } - - @Override - public Set getArtifacts() { - return super.getArtifacts(); - } - - @Override - public ExternalModuleDependency copy() { - QuarkusExtDependency copy = new QuarkusExtDependency(group, name, version, configuration); - final Set artifacts = getArtifacts(); - for (DependencyArtifact a : artifacts) { - copy.addArtifact(a); - } - return copy; - } - - @Override - public boolean contentEquals(Dependency arg0) { - new Exception("contentEquals " + arg0).printStackTrace(); - return true; - } -} From ab418a5f4ed471524f275450ce196876a362803d Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 11:00:58 -0300 Subject: [PATCH 225/602] Fixes test execution --- .../quarkus/gradle/QuarkusPluginFunctionalTest.java | 2 ++ .../main/java/io/quarkus/gradle/QuarkusPlugin.java | 13 ++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index e5b7cc40b6e44..020c945c45c94 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -25,6 +25,7 @@ public void canRunListExtensions(@TempDir File projectRoot) throws IOException { .forwardOutput() .withPluginClasspath() .withArguments("listExtensions") + .withEnvironment(System.getenv()) .withProjectDir(projectRoot) .build(); @@ -40,6 +41,7 @@ public void canBuild(@TempDir File projectRoot) throws IOException { .forwardOutput() .withPluginClasspath() .withArguments("quarkusBuild") + .withEnvironment(System.getenv()) .withProjectDir(projectRoot) .build(); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index e423054cb991f..bfa43323e9fb5 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -102,14 +102,13 @@ private void registerTasks(Project project) { testNative.dependsOn(buildNative); testNative.setShouldRunAfter(Collections.singletonList(tasks.findByName(JavaPlugin.TEST_TASK_NAME))); tasks.getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(testNative); + tasks.withType(Test.class).forEach(t -> { + // Quarkus test configuration task which should be executed before any Quarkus test + t.dependsOn(quarkusTestConfig); + // also make each task use the JUnit platform since it's the only supported test environment + t.useJUnitPlatform(); + }); }); - - tasks.withType(Test.class).forEach(t -> { - // Quarkus test configuration task which should be executed before any Quarkus test - t.dependsOn(quarkusTestConfig); - // also make each task use the JUnit platform since it's the only supported test environment - t.useJUnitPlatform(); - }); } private void verifyGradleVersion() { From e7be92edbe4577fc124ef6b8c22081a17725323d Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 11:07:02 -0300 Subject: [PATCH 226/602] Remove author tags --- .../main/java/io/quarkus/gradle/QuarkusPluginExtension.java | 3 --- .../src/main/java/io/quarkus/gradle/tasks/GradleLogger.java | 3 --- .../main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java | 3 --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java | 3 --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java | 3 --- .../java/io/quarkus/gradle/tasks/QuarkusListExtensions.java | 3 --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java | 3 --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java | 3 --- 8 files changed, 24 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 2232888290986..9224d943e6127 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -9,9 +9,6 @@ import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.resolver.AppModelResolver; -/** - * @author Ståle Pedersen - */ public class QuarkusPluginExtension { private final Project project; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleLogger.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleLogger.java index d8d4037400bc7..54404a76c42e8 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleLogger.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleLogger.java @@ -9,9 +9,6 @@ import org.jboss.logging.LoggerProvider; import org.wildfly.common.Assert; -/** - * @author Ståle Pedersen - */ public class GradleLogger implements LoggerProvider { static final Object[] NO_PARAMS = new Object[0]; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java index 7aa0f171bcd8d..72eefe1294f4a 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java @@ -16,9 +16,6 @@ import io.quarkus.cli.commands.file.GradleBuildFile; import io.quarkus.cli.commands.writer.FileProjectWriter; -/** - * @author Ståle Pedersen - */ public class QuarkusAddExtension extends QuarkusPlatformTask { public QuarkusAddExtension() { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java index 95583b1662ffe..1a7115d7c3507 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java @@ -18,9 +18,6 @@ import io.quarkus.creator.CuratedApplicationCreator; import io.quarkus.creator.phase.augment.AugmentTask; -/** - * @author Ståle Pedersen - */ public class QuarkusBuild extends QuarkusTask { private boolean uberJar; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 0ecd1f9264eb2..b945d6a18efd2 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -60,9 +60,6 @@ import io.quarkus.gradle.QuarkusPluginExtension; import io.quarkus.utilities.JavaBinFinder; -/** - * @author Ståle Pedersen - */ public class QuarkusDev extends QuarkusTask { private Set filesIncludedInClasspath = new HashSet<>(); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index 275c87a8e64b5..7f0f2223f671b 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -11,9 +11,6 @@ import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.gradle.GradleBuildFileFromConnector; -/** - * @author Ståle Pedersen - */ public class QuarkusListExtensions extends QuarkusPlatformTask { private boolean all = true; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java index 49cfc22e56c8b..c62d7443b6076 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusNative.java @@ -24,9 +24,6 @@ import io.quarkus.creator.CuratedApplicationCreator; import io.quarkus.creator.phase.augment.AugmentTask; -/** - * @author Ståle Pedersen - */ public class QuarkusNative extends QuarkusTask { private boolean reportErrorsAtRuntime = false; diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java index 157ba08fad5fc..684e2bee882fe 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java @@ -4,9 +4,6 @@ import io.quarkus.gradle.QuarkusPluginExtension; -/** - * @author Ståle Pedersen - */ public abstract class QuarkusTask extends DefaultTask { private QuarkusPluginExtension extension; From 18f1c18392c63db22f7a785e5561cc546c6397cb Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 15:55:17 -0300 Subject: [PATCH 227/602] CI: Running mvn install so Gradle plugin can also resolve --- ci-templates/stages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index b46b308befa7b..eda4eb34cd444 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -43,7 +43,7 @@ stages: displayName: 'Maven Build' condition: and(ne(variables.CACHE_RESTORED, 'true'), succeeded()) inputs: - goals: 'package' + goals: 'install' mavenOptions: $(MAVEN_OPTS) options: '-B --settings azure-mvn-settings.xml -DskipTests=true -Dno-format -DskipDocs' @@ -73,7 +73,7 @@ stages: displayName: 'Maven Build' condition: and(ne(variables.CACHE_RESTORED, 'true'), succeeded()) inputs: - goals: 'package' + goals: 'install' mavenOptions: $(MAVEN_OPTS) options: '-B --settings azure-mvn-settings.xml -DskipTests=true -Dno-format -DskipDocs' From 3d1d2f4d932919ca8f073ea9d556debfad144095 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 18:29:41 -0300 Subject: [PATCH 228/602] Publish gradle builds when build fails for debugging purposes --- devtools/gradle/settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/gradle/settings.gradle b/devtools/gradle/settings.gradle index c1491d22dda20..4de2a058ed852 100644 --- a/devtools/gradle/settings.gradle +++ b/devtools/gradle/settings.gradle @@ -8,6 +8,7 @@ gradleEnterprise { //See also: https://docs.gradle.com/enterprise/gradle-plugin/ termsOfServiceUrl = 'https://gradle.com/terms-of-service'; termsOfServiceAgree = 'yes' + publishOnFailure() } } From 766e7455cf84e2cf072104524161055820d80d4d Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 3 Dec 2019 19:31:12 -0300 Subject: [PATCH 229/602] Add maven.repo.local if that exists --- .../gradle/QuarkusPluginFunctionalTest.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index 020c945c45c94..b7511c1ef5c99 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -2,7 +2,9 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import io.quarkus.cli.commands.CreateProject; import io.quarkus.cli.commands.writer.FileProjectWriter; @@ -24,8 +26,7 @@ public void canRunListExtensions(@TempDir File projectRoot) throws IOException { BuildResult build = GradleRunner.create() .forwardOutput() .withPluginClasspath() - .withArguments("listExtensions") - .withEnvironment(System.getenv()) + .withArguments(arguments("listExtensions")) .withProjectDir(projectRoot) .build(); @@ -40,14 +41,23 @@ public void canBuild(@TempDir File projectRoot) throws IOException { BuildResult build = GradleRunner.create() .forwardOutput() .withPluginClasspath() - .withArguments("quarkusBuild") - .withEnvironment(System.getenv()) + .withArguments(arguments("quarkusBuild")) .withProjectDir(projectRoot) .build(); assertThat(build.task(":quarkusBuild").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } + private List arguments(String argument) { + List arguments = new ArrayList<>(); + arguments.add(argument); + String mavenRepoLocal = System.getProperty("maven.repo.local", System.getenv("MAVEN_LOCAL_REPO")); + if (mavenRepoLocal != null) { + arguments.add("-Dmaven.repo.local=" + mavenRepoLocal); + } + return arguments; + } + private void createProject(@TempDir File projectRoot) throws IOException { assertThat(new CreateProject(new FileProjectWriter(projectRoot)) .groupId("com.acme.foo") From 089d64edd05092f55d30311a04694ed1b66a2243 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 5 Dec 2019 09:17:18 -0300 Subject: [PATCH 230/602] Gradle 5.x is still supported --- .../gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index bfa43323e9fb5..e6bf01d7b099f 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -112,8 +112,8 @@ private void registerTasks(Project project) { } private void verifyGradleVersion() { - if (GradleVersion.current().compareTo(GradleVersion.version("6.0")) < 0) { - throw new GradleException("Quarkus plugin requires Gradle 6.0 or later. Current version is: " + + if (GradleVersion.current().compareTo(GradleVersion.version("5.0")) < 0) { + throw new GradleException("Quarkus plugin requires Gradle 5.0 or later. Current version is: " + GradleVersion.current()); } } From 880f448d0ff0069a8bcd33f6bd68826e8f779654 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 5 Dec 2019 11:14:35 -0300 Subject: [PATCH 231/602] Fix testNative classpath --- .../src/main/java/io/quarkus/gradle/QuarkusPlugin.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index e6bf01d7b099f..0d871719b055c 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -83,13 +83,13 @@ private void registerTasks(Project project) { nativeTestSourceSet.setCompileClasspath( nativeTestSourceSet.getCompileClasspath() - .plus(mainSourceSet.getCompileClasspath()) - .plus(testSourceSet.getCompileClasspath())); + .plus(mainSourceSet.getOutput()) + .plus(testSourceSet.getOutput())); nativeTestSourceSet.setRuntimeClasspath( nativeTestSourceSet.getRuntimeClasspath() - .plus(mainSourceSet.getRuntimeClasspath()) - .plus(testSourceSet.getRuntimeClasspath())); + .plus(mainSourceSet.getOutput()) + .plus(testSourceSet.getOutput())); // create a custom configuration to be used for the dependencies of the testNative task ConfigurationContainer configurations = project.getConfigurations(); From a1929b6fcc95f14ec7997a3c86f9a5d2c796d5da Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 15 Nov 2019 09:41:17 +1100 Subject: [PATCH 232/602] Add the ability to control the target dir via config --- .../quarkus/deployment/QuarkusAugmentor.java | 6 ++--- .../quarkus/deployment/pkg/PackageConfig.java | 13 +++++++++ .../builditem/BuildSystemTargetBuildItem.java | 27 +++++++++++++++++++ .../pkg/steps/JarResultBuildStep.java | 10 +++++++ 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index 224aad8b9a9d2..83af5936d600c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -31,8 +31,8 @@ import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.runtime.LaunchMode; public class QuarkusAugmentor { @@ -96,7 +96,7 @@ public BuildResult run() throws Exception { .addInitial(LiveReloadBuildItem.class) .addInitial(AdditionalApplicationArchiveBuildItem.class) .addInitial(ExtensionClassLoaderBuildItem.class) - .addInitial(OutputTargetBuildItem.class) + .addInitial(BuildSystemTargetBuildItem.class) .addInitial(CurateOutcomeBuildItem.class); for (Class i : finalResults) { chainBuilder.addFinal(i); @@ -119,7 +119,7 @@ public BuildResult run() throws Exception { .produce(new ShutdownContextBuildItem()) .produce(new LaunchModeBuildItem(launchMode)) .produce(new ExtensionClassLoaderBuildItem(classLoader)) - .produce(new OutputTargetBuildItem(targetDir, baseName)) + .produce(new BuildSystemTargetBuildItem(targetDir, baseName)) .produce(new CurateOutcomeBuildItem(effectiveModel, resolver)); for (Path i : additionalApplicationArchives) { execBuilder.produce(new AdditionalApplicationArchiveBuildItem(i)); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java index 3e922e93ad2cb..9efe7e622399b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java @@ -49,4 +49,17 @@ public class PackageConfig { */ @ConfigItem(defaultValue = "-runner") public String runnerSuffix; + + /** + * The output folder in which to place the output, this is resolved relative to the build + * systems target directory. + */ + @ConfigItem + public Optional outputDirectory; + + /** + * The name of the final artifact + */ + @ConfigItem + public Optional outputName; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java new file mode 100644 index 0000000000000..89bed8dd9461e --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/BuildSystemTargetBuildItem.java @@ -0,0 +1,27 @@ +package io.quarkus.deployment.pkg.builditem; + +import java.nio.file.Path; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * The build systems target directory. This is used to produce {@link OutputTargetBuildItem} + */ +public final class BuildSystemTargetBuildItem extends SimpleBuildItem { + + private final Path outputDirectory; + private final String baseName; + + public BuildSystemTargetBuildItem(Path outputDirectory, String baseName) { + this.outputDirectory = outputDirectory; + this.baseName = baseName; + } + + public Path getOutputDirectory() { + return outputDirectory; + } + + public String getBaseName() { + return baseName; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index be70dcaae3f95..e063859f2461d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -61,6 +61,7 @@ import io.quarkus.deployment.builditem.TransformedClassesBuildItem; import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; +import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.pkg.builditem.JarBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageSourceJarBuildItem; @@ -115,6 +116,15 @@ public class JarResultBuildStep { // makes a subsequent uberJar creation fail in java 8 (but works fine in Java 11) private static final OpenOption[] DEFAULT_OPEN_OPTIONS = { TRUNCATE_EXISTING, WRITE, CREATE }; + @BuildStep + OutputTargetBuildItem outputTarget(BuildSystemTargetBuildItem bst, PackageConfig packageConfig) { + String name = packageConfig.outputName.isPresent() ? packageConfig.outputName.get() : bst.getBaseName(); + Path path = packageConfig.outputDirectory.isPresent() + ? bst.getOutputDirectory().resolve(packageConfig.outputDirectory.get()) + : bst.getOutputDirectory(); + return new OutputTargetBuildItem(path, name); + } + @BuildStep(onlyIf = JarRequired.class) ArtifactResultBuildItem jarOutput(JarBuildItem jarBuildItem) { if (jarBuildItem.getLibraryDir() != null) { From 196295c8422001b65d329f2737322d93f4e6d8db Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 6 Dec 2019 09:56:07 -0300 Subject: [PATCH 233/602] Fixed Gradle config instructions --- docs/src/main/asciidoc/gradle-config.adoc | 45 +++++++++++------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/src/main/asciidoc/gradle-config.adoc b/docs/src/main/asciidoc/gradle-config.adoc index f4a358b42bace..f82b083dd34dc 100644 --- a/docs/src/main/asciidoc/gradle-config.adoc +++ b/docs/src/main/asciidoc/gradle-config.adoc @@ -8,43 +8,42 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] // tag::repositories[] -Quarkus Gradle plugin is not yet published to the https://plugins.gradle.org/[Gradle Plugin Portal], -so you need to add the following at the top of your './settings.gradle' file: +The Quarkus Gradle plugin is published to the https://plugins.gradle.org/plugin/io.quarkus[Gradle Plugin Portal]. + +To use it, add the following to your `build.gradle` file: + [source, groovy, subs=attributes+] ---- -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == 'io.quarkus') { - useModule("io.quarkus:quarkus-gradle-plugin:${requested.version}") - } - } - } +plugins { + id 'java' + id 'io.quarkus' } ---- -Or, if you use the Gradle Kotlin DSL, you need to add the following at the top of your './settings.gradle.kts' file: -[source, kotlin, subs=attributes+] +You also need to add the following at the top of your `settings.gradle` file: +[source, groovy, subs=attributes+] ---- pluginManagement { repositories { mavenCentral() gradlePluginPortal() } - resolutionStrategy { - eachPlugin { - if (requested.id.id == "io.quarkus") { - useModule("io.quarkus:quarkus-gradle-plugin:${requested.version}") - } - } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" } } ---- -This won't be necessary anymore once the Quarkus Gradle plugin is published in the Gradle plugin portal. +NOTE:: the `plugins{}` method in `settings.gradle` is not supported in Gradle 5.x. In this case make sure to explicitly declare the plugin version in the `build.gradle` file like the example below: + +[source, groovy, subs=attributes+] +---- +plugins { + id 'java' + id 'io.quarkus' version '{quarkus-version}' +} +---- + + // end::repositories[] From 6538cb6b64e41c4900624825677866c1777472f3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 10:43:52 +0100 Subject: [PATCH 234/602] Reformat Kogito test POM --- .../resources/projects/simple-kogito/pom.xml | 168 +++++++++--------- 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml index d9420f3d82182..eb95c8415ff81 100644 --- a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml +++ b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml @@ -1,88 +1,90 @@ - - 4.0.0 - org.acme - acme - 1.0-SNAPSHOT - - @project.version@ - 1.8 - UTF-8 - 1.8 - - - - io.quarkus - quarkus-resteasy - ${quarkus.version} - - - io.quarkus - quarkus-arc - ${quarkus.version} - - - io.quarkus - quarkus-jsonp - ${quarkus.version} - - - io.quarkus - quarkus-kogito - ${quarkus.version} - - - io.quarkus - quarkus-junit5 - ${quarkus.version} - test - - - io.rest-assured - rest-assured - 3.3.0 - test - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - build - - - - - - - - - native - - - + 4.0.0 + org.acme + acme + 1.0-SNAPSHOT + + @project.version@ + 1.8 + UTF-8 + 1.8 + + + + io.quarkus + quarkus-resteasy + ${quarkus.version} + + + io.quarkus + quarkus-arc + ${quarkus.version} + + io.quarkus - quarkus-maven-plugin + quarkus-jsonp ${quarkus.version} - - - - native-image - - - true - - - - + + + io.quarkus + quarkus-kogito + ${quarkus.version} + + + io.quarkus + quarkus-junit5 + ${quarkus.version} + test + + + io.rest-assured + rest-assured + 3.3.0 + test + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + - - - - + + + + native + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + + + + \ No newline at end of file From edac39a40abc15e08b9a925fde4d0e717bb80ac9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:03:08 +0100 Subject: [PATCH 235/602] Use Quarkus BOM in kogito-maven test project --- .../resources/projects/simple-kogito/pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml index eb95c8415ff81..bb12a46d7dabd 100644 --- a/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml +++ b/integration-tests/kogito-maven/src/test/resources/projects/simple-kogito/pom.xml @@ -13,37 +13,42 @@ UTF-8 1.8 + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + io.quarkus quarkus-resteasy - ${quarkus.version} io.quarkus quarkus-arc - ${quarkus.version} io.quarkus quarkus-jsonp - ${quarkus.version} io.quarkus quarkus-kogito - ${quarkus.version} io.quarkus quarkus-junit5 - ${quarkus.version} test io.rest-assured rest-assured - 3.3.0 test From 6afe6bab05da35edfb58eb0f435b6f78b9122674 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:21:31 +0100 Subject: [PATCH 236/602] Use Quarkus BOM in Scala test project --- .../resources/projects/classic-scala/pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 bbc9dc394971d..d365c442611e7 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 @@ -14,26 +14,33 @@ 2.12.8 4.1.1 + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + io.quarkus quarkus-resteasy - ${quarkus.version} io.quarkus quarkus-arc - ${quarkus.version} io.quarkus quarkus-scala-deployment - ${quarkus.version} io.quarkus quarkus-undertow-websockets - ${quarkus.version} org.scala-lang @@ -43,13 +50,11 @@ io.quarkus quarkus-junit5 - ${quarkus.version} test io.rest-assured rest-assured - 3.3.0 test From d5fdd333a0ae029126e538b256cf62e3a796c447 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:21:45 +0100 Subject: [PATCH 237/602] Reformat Kotlin test POM --- .../resources/projects/classic-kotlin/pom.xml | 252 +++++++++--------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml index 4d766c8c16462..f1eaa6f2baeb1 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml @@ -1,134 +1,134 @@ - - 4.0.0 - org.acme - acme - 1.0-SNAPSHOT - - @project.version@ - 1.8 - UTF-8 - 1.8 - 1.3.21 - - - - io.quarkus - quarkus-resteasy - ${quarkus.version} - - - io.quarkus - quarkus-arc - ${quarkus.version} - - - io.quarkus - quarkus-kotlin-deployment - ${quarkus.version} - - - io.quarkus - quarkus-undertow-websockets - ${quarkus.version} - - - io.quarkus - quarkus-junit5 - ${quarkus.version} - test - - - io.rest-assured - rest-assured - 3.3.0 - test - - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - ${kotlin.version} - - - - src/main/kotlin - src/test/kotlin - - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - build - - - - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${kotlin.version} - - - compile - - compile - - - - test-compile - - test-compile - - - - - - all-open - - - - - - - - - - - + 4.0.0 + org.acme + acme + 1.0-SNAPSHOT + + @project.version@ + 1.8 + UTF-8 + 1.8 + 1.3.21 + + + + io.quarkus + quarkus-resteasy + ${quarkus.version} + + + io.quarkus + quarkus-arc + ${quarkus.version} + + + io.quarkus + quarkus-kotlin-deployment + ${quarkus.version} + + + io.quarkus + quarkus-undertow-websockets + ${quarkus.version} + + + io.quarkus + quarkus-junit5 + ${quarkus.version} + test + + + io.rest-assured + rest-assured + 3.3.0 + test + + org.jetbrains.kotlin - kotlin-maven-allopen + kotlin-stdlib-jdk8 ${kotlin.version} - - - - - - - - native - + + + + src/main/kotlin + src/test/kotlin - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - native-image - + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + compile + + compile + + + + test-compile + + test-compile + + + - true + + all-open + + + + + + - - - + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + - - - - + + + + native + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + + + + \ No newline at end of file From bc0ed7aec8ce494fd3a4f8968932183e4cc10a6f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:22:52 +0100 Subject: [PATCH 238/602] Use Quarkus BOM in Kotlin test project --- .../resources/projects/classic-kotlin/pom.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml index f1eaa6f2baeb1..e9f751fe8749c 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml @@ -14,37 +14,42 @@ 1.8 1.3.21 + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + io.quarkus quarkus-resteasy - ${quarkus.version} io.quarkus quarkus-arc - ${quarkus.version} io.quarkus quarkus-kotlin-deployment - ${quarkus.version} io.quarkus quarkus-undertow-websockets - ${quarkus.version} io.quarkus quarkus-junit5 - ${quarkus.version} test io.rest-assured rest-assured - 3.3.0 test From 0d078b939ea8300101d17892c5d66958bb2f3d99 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:24:10 +0100 Subject: [PATCH 239/602] Properly use a runtime dependency in Scala test project --- .../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 d365c442611e7..0c5637f737793 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 @@ -36,7 +36,7 @@ io.quarkus - quarkus-scala-deployment + quarkus-scala io.quarkus From 4859101b89405500e2db38fc1655e486cb25f371 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 6 Dec 2019 11:24:32 +0100 Subject: [PATCH 240/602] Properly use a runtime dependency in Kotlin test project --- .../kotlin/src/test/resources/projects/classic-kotlin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml index e9f751fe8749c..cfeb8ed2c6dd2 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml @@ -36,7 +36,7 @@ io.quarkus - quarkus-kotlin-deployment + quarkus-kotlin io.quarkus From 7f9d6c4d8265d7cab385e5cc938ef2855525db4e Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 3 Dec 2019 14:53:43 -0600 Subject: [PATCH 241/602] Upgrade to SmallRye Config 1.5.0 with relocs --- bom/runtime/pom.xml | 18 ++++++++++++++++-- core/runtime/pom.xml | 2 +- tcks/microprofile-opentracing/base/pom.xml | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 02095903a9cff..ba434ede98ff8 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -31,7 +31,7 @@ 1.3.1 1.0 1.3.4 - 1.4.1 + 1.5.0 2.1.0 2.3.1 1.1.20 @@ -1092,8 +1092,22 @@ ${quarkus-security.version} + io.smallrye smallrye-config + + 1.5.0 + + + + io.smallrye + smallrye-config-common + + 1.5.0 + + + io.smallrye.config + smallrye-config ${smallrye-config.version} @@ -1103,7 +1117,7 @@ - io.smallrye + io.smallrye.config smallrye-config-common ${smallrye-config.version} diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 9621dd3d2e2e3..d749efc30781d 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -28,7 +28,7 @@ jakarta.inject-api - io.smallrye + io.smallrye.config smallrye-config diff --git a/tcks/microprofile-opentracing/base/pom.xml b/tcks/microprofile-opentracing/base/pom.xml index f9c3681880819..6cd1cd637bd49 100644 --- a/tcks/microprofile-opentracing/base/pom.xml +++ b/tcks/microprofile-opentracing/base/pom.xml @@ -51,7 +51,7 @@ quarkus-resteasy-jackson - io.smallrye + io.smallrye.config smallrye-config From f6551f839379db74cdb13ab3f6e2c6cf32724d47 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 3 Dec 2019 14:56:43 -0600 Subject: [PATCH 242/602] Introduce YAML configuration extension Fixes #3904, fixes #4568 --- bom/deployment/pom.xml | 5 ++ bom/runtime/pom.xml | 10 +++ extensions/config-yaml/pom.xml | 20 ++++++ .../runtime/config/application.yaml | 2 + extensions/config-yaml/runtime/pom.xml | 41 ++++++++++++ .../yaml/runtime/ApplicationYamlProvider.java | 64 +++++++++++++++++++ ...croprofile.config.spi.ConfigSourceProvider | 1 + .../yaml/runtime/ApplicationYamlTest.java | 52 +++++++++++++++ .../src/test/resources/application.yaml | 2 + extensions/pom.xml | 3 + 10 files changed, 200 insertions(+) create mode 100644 extensions/config-yaml/pom.xml create mode 100644 extensions/config-yaml/runtime/config/application.yaml create mode 100644 extensions/config-yaml/runtime/pom.xml create mode 100644 extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java create mode 100644 extensions/config-yaml/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider create mode 100644 extensions/config-yaml/runtime/src/test/java/io/quarkus/config/yaml/runtime/ApplicationYamlTest.java create mode 100644 extensions/config-yaml/runtime/src/test/resources/application.yaml diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index afca6dec90c28..fb78e7f655742 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -131,6 +131,11 @@ quarkus-artemis-jms-deployment ${project.version} + + io.quarkus + quarkus-config-yaml-deployment + ${project.version} + io.quarkus quarkus-hibernate-validator-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index ba434ede98ff8..8c72523e131dc 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -310,6 +310,11 @@ quarkus-artemis-jms ${project.version} + + io.quarkus + quarkus-config-yaml + ${project.version} + io.quarkus quarkus-elasticsearch-rest-client @@ -1121,6 +1126,11 @@ smallrye-config-common ${smallrye-config.version} + + io.smallrye.config + smallrye-config-source-yaml + ${smallrye-config.version} + io.smallrye smallrye-health diff --git a/extensions/config-yaml/pom.xml b/extensions/config-yaml/pom.xml new file mode 100644 index 0000000000000..ee4ec1f790f7a --- /dev/null +++ b/extensions/config-yaml/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-config-yaml-parent + Quarkus - Configuration - YAML + pom + + runtime + + + diff --git a/extensions/config-yaml/runtime/config/application.yaml b/extensions/config-yaml/runtime/config/application.yaml new file mode 100644 index 0000000000000..2697ced792665 --- /dev/null +++ b/extensions/config-yaml/runtime/config/application.yaml @@ -0,0 +1,2 @@ +file: + system: true \ No newline at end of file diff --git a/extensions/config-yaml/runtime/pom.xml b/extensions/config-yaml/runtime/pom.xml new file mode 100644 index 0000000000000..470c269bdae81 --- /dev/null +++ b/extensions/config-yaml/runtime/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + + io.quarkus + quarkus-config-yaml-parent + 999-SNAPSHOT + + + quarkus-config-yaml + Quarkus - Configuration - YAML - Runtime + + + + io.smallrye.config + smallrye-config-source-yaml + + + io.quarkus + quarkus-core + + + org.eclipse.microprofile.config + microprofile-config-api + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + \ No newline at end of file diff --git a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java new file mode 100644 index 0000000000000..a1ef82911e39d --- /dev/null +++ b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java @@ -0,0 +1,64 @@ +package io.quarkus.config.yaml.runtime; + +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.smallrye.config.source.yaml.YamlConfigSource; + +/** + * + */ +public final class ApplicationYamlProvider implements ConfigSourceProvider { + + static final String APPLICATION_YAML = "application.yaml"; + + public Iterable getConfigSources(final ClassLoader forClassLoader) { + List sources = Collections.emptyList(); + // mirror the in-JAR application.properties + try { + InputStream str = forClassLoader.getResourceAsStream(APPLICATION_YAML); + if (str != null) { + try (Closeable c = str) { + YamlConfigSource configSource = new YamlConfigSource(APPLICATION_YAML, str, 254); + assert sources.isEmpty(); + sources = Collections.singletonList(configSource); + } + } + } catch (IOException e) { + // configuration problem should be thrown + throw new IOError(e); + } + // mirror the on-filesystem application.properties + final Path path = Paths.get("config", APPLICATION_YAML); + if (Files.exists(path)) { + try (InputStream str = Files.newInputStream(path)) { + YamlConfigSource configSource = new YamlConfigSource(APPLICATION_YAML, str, 264); + if (sources.isEmpty()) { + sources = Collections.singletonList(configSource); + } else { + // todo: sources = List.of(sources.get(0), configSource); + sources = Arrays.asList(sources.get(0), configSource); + } + } catch (NoSuchFileException | FileNotFoundException e) { + // skip (race) + } catch (IOException e) { + // configuration problem should be thrown + throw new IOError(e); + } + } + return sources; + } +} diff --git a/extensions/config-yaml/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider b/extensions/config-yaml/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider new file mode 100644 index 0000000000000..e85b2e9dda1c0 --- /dev/null +++ b/extensions/config-yaml/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider @@ -0,0 +1 @@ +io.quarkus.config.yaml.runtime.ApplicationYamlProvider diff --git a/extensions/config-yaml/runtime/src/test/java/io/quarkus/config/yaml/runtime/ApplicationYamlTest.java b/extensions/config-yaml/runtime/src/test/java/io/quarkus/config/yaml/runtime/ApplicationYamlTest.java new file mode 100644 index 0000000000000..9425ea39c2af3 --- /dev/null +++ b/extensions/config-yaml/runtime/src/test/java/io/quarkus/config/yaml/runtime/ApplicationYamlTest.java @@ -0,0 +1,52 @@ +package io.quarkus.config.yaml.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * Test the YAML config provider (plan JUnit). We aren't re-testing the whole config source + * (that's done in SmallRye Config) but we do make sure that both the file system and in-JAR + * properties are being picked up. + */ +public class ApplicationYamlTest { + + static volatile SmallRyeConfig config; + + @BeforeAll + public static void doBefore() { + SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); + builder.addDefaultSources().addDiscoveredConverters().addDiscoveredSources(); + QuarkusConfigFactory.setConfig(config = builder.build()); + Config conf = ConfigProvider.getConfig(); + if (conf != config) { + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + cpr.releaseConfig(conf); + ConfigProvider.getConfig(); + } + System.out.println(System.getProperty("user.dir")); + } + + @Test + public void testBasicApplicationYaml() { + assertEquals("something", ConfigProvider.getConfig().getValue("foo.bar", String.class)); + assertTrue(ConfigProvider.getConfig().getValue("file.system", Boolean.class).booleanValue()); + } + + @AfterAll + public static void doAfter() { + ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + cpr.releaseConfig(config); + config = null; + } +} diff --git a/extensions/config-yaml/runtime/src/test/resources/application.yaml b/extensions/config-yaml/runtime/src/test/resources/application.yaml new file mode 100644 index 0000000000000..5655415edd8aa --- /dev/null +++ b/extensions/config-yaml/runtime/src/test/resources/application.yaml @@ -0,0 +1,2 @@ +foo: + bar: something diff --git a/extensions/pom.xml b/extensions/pom.xml index 6370143a8565f..70ca39af15faa 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -19,6 +19,9 @@ scheduler quartz + + config-yaml + jackson jaxb From 1d0af2581949426d9272b0a031c88f0dc8440263 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 5 Dec 2019 21:34:18 -0300 Subject: [PATCH 243/602] Use body handler to allow request buffering Fixes #5959 This change allows the body handler to be used for Undertow and RESTEasy standalone. When the request is fully buffered it can be consumed multiple times, which allows keycloak to also process it. --- bom/runtime/pom.xml | 2 +- .../KeycloakPolicyEnforcerBuildStep.java | 31 ++++++++++++++++ .../runtime/KeycloakPolicyEnforcerConfig.java | 36 +++++++++--------- .../keycloak/pep/runtime/VertxHttpFacade.java | 14 ++++++- .../standalone/VertxRequestHandler.java | 10 ++++- .../runtime/UndertowDeploymentRecorder.java | 3 +- .../http/deployment/BodyHandlerBuildItem.java | 17 +++++++++ .../RequireBodyHandlerBuildItem.java | 10 +++++ .../http/deployment/VertxHttpProcessor.java | 19 +++++++++- .../vertx/http/runtime/VertxHttpRecorder.java | 37 ++++++++++++++++++- .../vertx/http/runtime}/VertxInputStream.java | 13 ++++++- .../web/deployment/BodyHandlerBuildItem.java | 4 ++ .../web/deployment/VertxWebProcessor.java | 12 +++--- .../vertx/web/runtime/VertxWebRecorder.java | 30 ++------------- .../it/keycloak/ProtectedResource.java | 17 +++++++++ .../src/main/resources/application.properties | 5 +++ .../it/keycloak/PolicyEnforcerTest.java | 28 ++++++++++++++ 17 files changed, 226 insertions(+), 62 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java rename extensions/{resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone => vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime}/VertxInputStream.java (95%) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 8c72523e131dc..aa0963fe0efb0 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -24,7 +24,7 @@ 0.2.0 0.0.12 0.34.0 - 3.0.0.Final + 3.0.1.Final 1.0.0.Final 1.3 1.0.1 diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java index f9775d62682b0..468ddc56e4e7a 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java @@ -1,5 +1,7 @@ package io.quarkus.keycloak.pep.deployment; +import java.util.Map; + import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.annotations.BuildStep; @@ -13,6 +15,7 @@ import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.runtime.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; public class KeycloakPolicyEnforcerBuildStep { @@ -21,6 +24,34 @@ FeatureBuildItem featureBuildItem() { return new FeatureBuildItem(FeatureBuildItem.KEYCLOAK_AUTHORIZATION); } + @BuildStep + RequireBodyHandlerBuildItem requireBody(KeycloakPolicyEnforcerConfig config) { + if (config.policyEnforcer.enable) { + if (isBodyClaimInformationPointDefined(config.policyEnforcer.claimInformationPoint.simpleConfig)) { + return new RequireBodyHandlerBuildItem(); + } + for (KeycloakPolicyEnforcerConfig.KeycloakConfigPolicyEnforcer.PathConfig path : config.policyEnforcer.paths + .values()) { + if (isBodyClaimInformationPointDefined(path.claimInformationPoint.simpleConfig)) { + return new RequireBodyHandlerBuildItem(); + } + } + } + return null; + } + + private boolean isBodyClaimInformationPointDefined(Map> claims) { + for (Map.Entry> entry : claims.entrySet()) { + Map value = entry.getValue(); + + if (value.get(entry.getKey()).contains("request.body")) { + return true; + } + } + + return false; + } + @BuildStep public AdditionalBeanBuildItem beans(KeycloakPolicyEnforcerConfig config) { if (config.policyEnforcer.enable) { diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java index 9f6545124b69f..118ed1247c348 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/KeycloakPolicyEnforcerConfig.java @@ -41,13 +41,13 @@ public static class KeycloakConfigPolicyEnforcer { * Specifies how policies are enforced. */ @ConfigItem(defaultValue = "ENFORCING") - String enforcementMode; + public String enforcementMode; /** * Specifies the paths to protect. */ @ConfigItem - Map paths; + public Map paths; /** * Defines how the policy enforcer should track associations between paths in your application and resources defined in @@ -56,7 +56,7 @@ public static class KeycloakConfigPolicyEnforcer { * protected resources */ @ConfigItem - PathCacheConfig pathCache; + public PathCacheConfig pathCache; /** * Specifies how the adapter should fetch the server for resources associated with paths in your application. If true, @@ -65,14 +65,14 @@ public static class KeycloakConfigPolicyEnforcer { * enforcer is going to fetch resources on-demand accordingly with the path being requested */ @ConfigItem(defaultValue = "true") - boolean lazyLoadPaths; + public boolean lazyLoadPaths; /** * Defines a set of one or more claims that must be resolved and pushed to the Keycloak server in order to make these * claims available to policies */ @ConfigItem - ClaimInformationPointConfig claimInformationPoint; + public ClaimInformationPointConfig claimInformationPoint; /** * Specifies how scopes should be mapped to HTTP methods. If set to true, the policy enforcer will use the HTTP method @@ -80,7 +80,7 @@ public static class KeycloakConfigPolicyEnforcer { * the current request to check whether or not access should be granted */ @ConfigItem - boolean httpMethodAsScope; + public boolean httpMethodAsScope; @ConfigGroup public static class PathConfig { @@ -89,13 +89,13 @@ public static class PathConfig { * The name of a resource on the server that is to be associated with a given path */ @ConfigItem - Optional name; + public Optional name; /** * A URI relative to the application’s context path that should be protected by the policy enforcer */ @ConfigItem - Optional path; + public Optional path; /** * The HTTP methods (for example, GET, POST, PATCH) to protect and how they are associated with the scopes for a @@ -103,14 +103,14 @@ public static class PathConfig { * resource in the server */ @ConfigItem - Map methods; + public Map methods; /** * Specifies how policies are enforced */ @DefaultConverter @ConfigItem(defaultValue = "ENFORCING") - PolicyEnforcerConfig.EnforcementMode enforcementMode; + public PolicyEnforcerConfig.EnforcementMode enforcementMode; /** * Defines a set of one or more claims that must be resolved and pushed to the Keycloak server in order to make @@ -118,7 +118,7 @@ public static class PathConfig { * claims available to policies */ @ConfigItem - ClaimInformationPointConfig claimInformationPoint; + public ClaimInformationPointConfig claimInformationPoint; } @ConfigGroup @@ -128,20 +128,20 @@ public static class MethodConfig { * The name of the HTTP method */ @ConfigItem - String method; + public String method; /** * An array of strings with the scopes associated with the method */ @ConfigItem - List scopes; + public List scopes; /** * A string referencing the enforcement mode for the scopes associated with a method */ @DefaultConverter @ConfigItem(defaultValue = "ALL") - PolicyEnforcerConfig.ScopeEnforcementMode scopesEnforcementMode; + public PolicyEnforcerConfig.ScopeEnforcementMode scopesEnforcementMode; } @ConfigGroup @@ -151,13 +151,13 @@ public static class PathCacheConfig { * Defines the time in milliseconds when the entry should be expired */ @ConfigItem(defaultValue = "1000") - int maxEntries = 1000; + public int maxEntries = 1000; /** * Defines the limit of entries that should be kept in the cache */ @ConfigItem(defaultValue = "30000") - long lifespan = 30000; + public long lifespan = 30000; } @ConfigGroup @@ -167,13 +167,13 @@ public static class ClaimInformationPointConfig { * */ @ConfigItem(name = ConfigItem.PARENT) - Map>> complexConfig; + public Map>> complexConfig; /** * */ @ConfigItem(name = ConfigItem.PARENT) - Map> simpleConfig; + public Map> simpleConfig; } } } diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java index 78a8b5fb934fa..674c7daa8b476 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java @@ -1,6 +1,5 @@ package io.quarkus.keycloak.pep.runtime; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -23,6 +22,7 @@ import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpServerRequest; @@ -122,7 +122,17 @@ public InputStream getInputStream() { @Override public InputStream getInputStream(boolean buffered) { - return new BufferedInputStream(new ByteArrayInputStream(routingContext.getBody().getBytes())); + try { + if (routingContext.getBody() != null) { + return new ByteArrayInputStream(routingContext.getBody().getBytes()); + } + if (routingContext.request().isEnded()) { + return new ByteArrayInputStream(new byte[0]); + } + return new VertxInputStream(request); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Override diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java index d676b8ab814c2..ccd63565410d4 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java @@ -1,5 +1,6 @@ package io.quarkus.resteasy.runtime.standalone; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -19,6 +20,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -61,9 +63,13 @@ public VertxRequestHandler(Vertx vertx, public void handle(RoutingContext request) { // have to create input stream here. Cannot execute in another thread // otherwise request handlers may not get set up before request ends - VertxInputStream is; + InputStream is; try { - is = new VertxInputStream(request.request()); + if (request.getBody() != null) { + is = new ByteArrayInputStream(request.getBody().getBytes()); + } else { + is = new VertxInputStream(request.request()); + } } catch (IOException e) { request.fail(e); return; diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index f9e7394440a1b..d66f8d71f3e48 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -353,7 +353,8 @@ public void run() { return new Handler() { @Override public void handle(RoutingContext event) { - VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event); + VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event, + event.getBody()); Optional maxBodySize = httpConfiguration.limits.maxBodySize; if (maxBodySize.isPresent()) { exchange.setMaxEntitySize(maxBodySize.get().asLongValue()); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java new file mode 100644 index 0000000000000..c5743fcd30385 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +public final class BodyHandlerBuildItem extends SimpleBuildItem { + private final Handler handler; + + public BodyHandlerBuildItem(Handler handler) { + this.handler = handler; + } + + public Handler getHandler() { + return handler; + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java new file mode 100644 index 0000000000000..dbfd8541499a3 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java @@ -0,0 +1,10 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This is a marker that indicates that the body handler should be installed + * on all routes, as an extension requires the request to be fully buffered. + */ +public final class RequireBodyHandlerBuildItem extends MultiBuildItem { +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 2dcebfca9208d..3f79ed6ee306c 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -35,8 +35,10 @@ import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.quarkus.vertx.http.runtime.cors.CORSRecorder; import io.quarkus.vertx.http.runtime.filters.Filter; +import io.vertx.core.Handler; import io.vertx.core.impl.VertxImpl; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; class VertxHttpProcessor { @@ -97,6 +99,12 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder, return new VertxWebRouterBuildItem(router); } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + BodyHandlerBuildItem bodyHandler(VertxHttpRecorder recorder, HttpConfiguration httpConfiguration) { + return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem finalizeRouter( @@ -106,7 +114,9 @@ ServiceStartBuildItem finalizeRouter( List defaultRoutes, List filters, VertxWebRouterBuildItem router, EventLoopCountBuildItem eventLoopCount, HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration, - BuildProducer reflectiveClass, List websocketSubProtocols) + BuildProducer reflectiveClass, List websocketSubProtocols, + List requireBodyHandlerBuildItems, + BodyHandlerBuildItem bodyHandlerBuildItem) throws BuildException, IOException { Optional defaultRoute; if (defaultRoutes == null || defaultRoutes.isEmpty()) { @@ -124,9 +134,14 @@ ServiceStartBuildItem finalizeRouter( .filter(f -> f.getHandler() != null) .map(FilterBuildItem::toFilter).collect(Collectors.toList()); + //if the body handler is required then we know it is installed for all routes, so we don't need to register it here + Handler bodyHandler = !requireBodyHandlerBuildItems.isEmpty() ? bodyHandlerBuildItem.getHandler() + : null; + recorder.finalizeRouter(beanContainer.getValue(), defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), - listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode()); + listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode(), + !requireBodyHandlerBuildItems.isEmpty(), bodyHandler); boolean startVirtual = requireVirtual.isPresent() || httpBuildTimeConfig.virtual; if (startVirtual) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index 4b7b13dfbc37a..69568666ea191 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -35,6 +35,7 @@ import io.quarkus.runtime.Timing; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigInstantiator; +import io.quarkus.runtime.configuration.MemorySize; import io.quarkus.vertx.core.runtime.VertxCoreRecorder; import io.quarkus.vertx.core.runtime.config.VertxConfiguration; import io.quarkus.vertx.http.runtime.filters.Filter; @@ -61,6 +62,7 @@ import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxHttpRecorder { @@ -169,7 +171,8 @@ public void startServer(RuntimeValue vertxRuntimeValue, ShutdownContext s public void finalizeRouter(BeanContainer container, Consumer defaultRouteHandler, List filterList, RuntimeValue vertx, - RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode) { + RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode, boolean requireBodyHandler, + Handler bodyHandler) { // install the default route at the end Router router = runtimeValue.getValue(); @@ -200,6 +203,18 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute container.instance(RouterProducer.class).initialize(resumingRouter); router.route().last().failureHandler(new QuarkusErrorHandler(launchMode.isDevOrTest())); + if (requireBodyHandler) { + //if this is set then everything needs the body handler installed + //TODO: config etc + router.route().order(Integer.MIN_VALUE).handler(new Handler() { + @Override + public void handle(RoutingContext routingContext) { + routingContext.request().resume(); + bodyHandler.handle(routingContext); + } + }); + } + if (rootPath.equals("/")) { if (hotReplacementHandler != null) { router.route().order(-1).handler(hotReplacementHandler); @@ -626,4 +641,24 @@ public static Handler getRootHandler() { return ACTUAL_ROOT; } + public Handler createBodyHandler(HttpConfiguration httpConfiguration) { + BodyHandler bodyHandler = BodyHandler.create(); + Optional maxBodySize = httpConfiguration.limits.maxBodySize; + if (maxBodySize.isPresent()) { + bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); + } + final BodyConfig bodyConfig = httpConfiguration.body; + bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); + bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); + bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); + bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); + bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); + return new Handler() { + @Override + public void handle(RoutingContext event) { + event.request().resume(); + bodyHandler.handle(event); + } + }; + } } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java similarity index 95% rename from extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java rename to extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java index 9c97bc82489f9..357f77de923aa 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxInputStream.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java @@ -1,4 +1,4 @@ -package io.quarkus.resteasy.runtime.standalone; +package io.quarkus.vertx.http.runtime; import java.io.IOException; import java.io.InputStream; @@ -10,6 +10,7 @@ import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; public class VertxInputStream extends InputStream { @@ -80,6 +81,7 @@ public int available() throws IOException { if (finished) { return -1; } + return exchange.readBytesAvailable(); } @@ -199,7 +201,14 @@ public int readBytesAvailable() { if (input1 != null) { return input1.getByteBuf().readableBytes(); } - return 0; + + String length = request.getHeader(HttpHeaders.CONTENT_LENGTH); + + if (length == null) { + return 0; + } + + return Integer.parseInt(length); } } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java index 715dbafcde91b..d8e3e968eef7b 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java @@ -4,6 +4,10 @@ import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; +/** + * use {@link io.quarkus.vertx.http.deployment.BodyHandlerBuildItem} instead + */ +@Deprecated public final class BodyHandlerBuildItem extends SimpleBuildItem { private final Handler handler; diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index 8b98af3df949c..ed69c72f5731e 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -48,9 +48,9 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.vertx.http.deployment.FilterBuildItem; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HandlerType; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.web.Route; import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; @@ -149,9 +149,8 @@ void validateBeanDeployment( } @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) - BodyHandlerBuildItem bodyHandler(VertxWebRecorder recorder, HttpConfiguration httpConfiguration) { - return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + BodyHandlerBuildItem bodyHandler(io.quarkus.vertx.http.deployment.BodyHandlerBuildItem realOne) { + return new BodyHandlerBuildItem(realOne.getHandler()); } @BuildStep @@ -163,9 +162,10 @@ void addAdditionalRoutes( BuildProducer generatedClass, AnnotationProxyBuildItem annotationProxy, BuildProducer reflectiveClasses, - BodyHandlerBuildItem bodyHandler, + io.quarkus.vertx.http.deployment.BodyHandlerBuildItem bodyHandler, BuildProducer routeProducer, - BuildProducer filterProducer) throws IOException { + BuildProducer filterProducer, + List bodyHandlerRequired) throws IOException { ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index ffc08969aa4fb..e7b3051717b6b 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -1,20 +1,15 @@ package io.quarkus.vertx.web.runtime; import java.lang.reflect.InvocationTargetException; -import java.util.Optional; import java.util.function.Function; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.MemorySize; -import io.quarkus.vertx.http.runtime.BodyConfig; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.http.runtime.RouterProducer; import io.quarkus.vertx.web.Route; import io.vertx.core.Handler; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxWebRecorder { @@ -66,33 +61,14 @@ public io.vertx.ext.web.Route apply(Router router) { route.consumes(consumes); } } - route.handler(bodyHandler); + if (bodyHandler != null) { + route.handler(bodyHandler); + } return route; } }; } - public Handler createBodyHandler(HttpConfiguration httpConfiguration) { - BodyHandler bodyHandler = BodyHandler.create(); - Optional maxBodySize = httpConfiguration.limits.maxBodySize; - if (maxBodySize.isPresent()) { - bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); - } - final BodyConfig bodyConfig = httpConfiguration.body; - bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); - bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); - bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); - bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); - bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); - return new Handler() { - @Override - public void handle(RoutingContext event) { - event.request().resume(); - bodyHandler.handle(event); - } - }; - } - private String ensureStartWithSlash(String path) { if (path.startsWith("/")) { return path; diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java index a3ebd29a35ced..beb96fe320251 100644 --- a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -1,20 +1,26 @@ package io.quarkus.it.keycloak; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import javax.inject.Inject; import javax.security.auth.AuthPermission; +import javax.ws.rs.Consumes; import javax.ws.rs.ForbiddenException; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.keycloak.representations.idm.authorization.Permission; import io.quarkus.security.identity.SecurityIdentity; +import io.vertx.core.http.HttpServerRequest; @Path("/api/permission") public class ProtectedResource { @@ -47,4 +53,15 @@ public List claimProtected() { public List httpResponseClaimProtected() { return identity.getAttribute("permissions"); } + + @Path("/body-claim") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public List bodyClaim(Map body, @Context HttpServerRequest request) { + if (body == null && !body.containsKey("from-body")) { + return Collections.emptyList(); + } + return identity.getAttribute("permissions"); + } } diff --git a/integration-tests/keycloak-authorization/src/main/resources/application.properties b/integration-tests/keycloak-authorization/src/main/resources/application.properties index 1aef8e6e968db..2989b9924ea5d 100644 --- a/integration-tests/keycloak-authorization/src/main/resources/application.properties +++ b/integration-tests/keycloak-authorization/src/main/resources/application.properties @@ -32,3 +32,8 @@ quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Au # Disables policy enforcement for a path quarkus.keycloak.policy-enforcer.paths.4.path=/api/public quarkus.keycloak.policy-enforcer.paths.4.enforcement-mode=DISABLED + +# Defines a claim which value is based on the response from an external service +quarkus.keycloak.policy-enforcer.paths.5.path=/api/permission/body-claim +quarkus.keycloak.policy-enforcer.paths.5.claim-information-point.claims.from-body={request.body['/from-body']} + diff --git a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java index 0b87ae3d1a65f..8612f498e253a 100644 --- a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java +++ b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/PolicyEnforcerTest.java @@ -26,6 +26,7 @@ import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; /** * @author Pedro Igor @@ -116,6 +117,7 @@ private static ClientRepresentation createClient(String clientId) { configurePermissionResourcePermission(authorizationSettings); configureClaimBasedPermission(authorizationSettings); configureHttpResponseClaimBasedPermission(authorizationSettings); + configureBodyClaimBasedPermission(authorizationSettings); client.setAuthorizationSettings(authorizationSettings); @@ -155,6 +157,20 @@ private static void configureHttpResponseClaimBasedPermission(ResourceServerRepr "/api/permission/http-response-claim-protected"), policy); } + private static void configureBodyClaimBasedPermission(ResourceServerRepresentation settings) { + PolicyRepresentation policy = createJSPolicy("Body Claim-Based Policy", + "var context = $evaluation.getContext();\n" + + "print(context.getAttributes().toMap());" + + "var attributes = context.getAttributes();\n" + + "\n" + + "if (attributes.containsValue('from-body', 'grant')) {\n" + + " $evaluation.grant();\n" + + "}", + settings); + createPermission(settings, createResource(settings, "Body Claim Protected Resource", + "/api/permission/body-claim"), policy); + } + private static void createPermission(ResourceServerRepresentation settings, ResourceRepresentation resource, PolicyRepresentation policy) { PolicyRepresentation permission = new PolicyRepresentation(); @@ -260,6 +276,18 @@ public void testHttpResponseFromExternalServiceAsClaim() { .statusCode(403); } + @Test + public void testBodyClaim() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .contentType(ContentType.JSON) + .body("{\"from-body\": \"grant\"}") + .when() + .post("/api/permission/body-claim") + .then() + .statusCode(200) + .and().body(Matchers.containsString("Body Claim Protected Resource")); + } + @Test public void testPublicResource() { RestAssured.given() From 612a5b1686ef96e75f8fac237eb5887ebf207a15 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 6 Dec 2019 07:38:40 -0300 Subject: [PATCH 244/602] [fixes #5959] - Temporary fix to content type resolution that fixes processing of json body --- .../keycloak/pep/runtime/VertxHttpFacade.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java index 674c7daa8b476..df2342cf3d153 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java @@ -25,6 +25,7 @@ import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.impl.CookieImpl; @@ -107,7 +108,16 @@ public Cookie getCookie(String cookieName) { @Override public String getHeader(String name) { - return request.getHeader(name); + //TODO: this logic should be removed once KEYCLOAK-12412 is fixed + String value = request.getHeader(name); + + if (name.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE.toString())) { + if (value.indexOf(';') != -1) { + return value.substring(0, value.indexOf(';')); + } + } + + return value; } @Override From 3c3c135fc1a52c272f94bb68316d0ef20ea810c0 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 14:21:41 +0200 Subject: [PATCH 245/602] Add missing Spring Controller templates for Kotlin and Scala Fixes: #5817 --- .../kotlin/spring-controller-template.ftl | 15 +++++++++++++++ .../scala/spring-controller-template.ftl | 12 ++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/spring-controller-template.ftl create mode 100644 devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/spring-controller-template.ftl diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/spring-controller-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/spring-controller-template.ftl new file mode 100644 index 0000000000000..82d56d6c695d3 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/spring-controller-template.ftl @@ -0,0 +1,15 @@ +package ${package_name}; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; + + +@RestController +@RequestMapping("${path}") +class ${class_name} { + + @GetMapping + fun hello() = "hello" +} diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/spring-controller-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/spring-controller-template.ftl new file mode 100644 index 0000000000000..2abac87be5b77 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/spring-controller-template.ftl @@ -0,0 +1,12 @@ +package ${package_name}; + +import org.springframework.web.bind.annotation.{GetMapping, RequestMapping, RestController, PathVariable}; + + +@RestController +@RequestMapping(Array[String]("${path}")) +class ${class_name} { + + @GetMapping + def hello() = "hello" +} From 42ccfc8180a4ab3493c8298df615af3bb71b3eb3 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Tue, 3 Dec 2019 18:30:39 +0000 Subject: [PATCH 246/602] Initial token validation support --- .../io/quarkus/oidc/runtime/OidcConfig.java | 37 ++++++++++++ .../oidc/runtime/OidcIdentityProvider.java | 8 +++ .../io/quarkus/oidc/runtime/OidcRecorder.java | 3 + .../io/quarkus/oidc/runtime/OidcUtils.java | 40 +++++++++++-- .../quarkus/oidc/runtime/OidcUtilsTest.java | 58 +++++++++++++++++++ .../test/resources/tokenArrayAudience.json | 11 ++++ .../src/test/resources/tokenIssuer.json | 11 ++++ .../test/resources/tokenStringAudience.json | 11 ++++ 8 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 extensions/oidc/runtime/src/test/resources/tokenArrayAudience.json create mode 100644 extensions/oidc/runtime/src/test/resources/tokenIssuer.json create mode 100644 extensions/oidc/runtime/src/test/resources/tokenStringAudience.json diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java index aa940299e003e..8706723765ecc 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcConfig.java @@ -1,6 +1,7 @@ package io.quarkus.oidc.runtime; import java.time.Duration; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -58,6 +59,12 @@ public class OidcConfig { @ConfigItem Roles roles; + /** + * Configuration how to validate the token claims. + */ + @ConfigItem + Token token; + /** * Credentials which the OIDC adapter will use to authenticate to the OIDC server. */ @@ -160,4 +167,34 @@ public static class Authentication { @ConfigItem public Optional> scopes; } + + @ConfigGroup + public static class Token { + + /** + * Expected issuer 'iss' claim value + */ + @ConfigItem + public Optional issuer; + + /** + * Expected audience `aud` claim value which may be a string or an array of strings + */ + @ConfigItem + public Optional> audience; + + public static Token fromIssuer(String issuer) { + Token tokenClaims = new Token(); + tokenClaims.issuer = Optional.of(issuer); + tokenClaims.audience = Optional.ofNullable(null); + return tokenClaims; + } + + public static Token fromAudience(String... audience) { + Token tokenClaims = new Token(); + tokenClaims.issuer = Optional.ofNullable(null); + tokenClaims.audience = Optional.of(Arrays.asList(audience)); + return tokenClaims; + } + } } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 929c41b9df181..76fa1c409e3b2 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -9,6 +9,7 @@ import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.consumer.InvalidJwtException; +import io.quarkus.oidc.OIDCException; import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.ForbiddenException; import io.quarkus.security.identity.AuthenticationRequestContext; @@ -59,6 +60,13 @@ public void handle(AsyncResult event) { return; } AccessToken token = event.result(); + try { + OidcUtils.validateClaims(config.token, token.accessToken()); + } catch (OIDCException e) { + result.completeExceptionally(new AuthenticationFailedException(e)); + return; + } + QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(); JsonWebToken jwtPrincipal; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java index 52850a8a437e9..40aa5bc31f514 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java @@ -48,6 +48,9 @@ public void setup(OidcConfig config, OidcBuildTimeConfig btConfig, RuntimeValue< .setAlgorithm("RS256") .setPublicKey(config.publicKey.get())); } + if (config.token.issuer.isPresent()) { + options.setValidateIssuer(false); + } final long connectionDelayInSecs = config.connectionDelay.isPresent() ? config.connectionDelay.get().toMillis() / 1000 : 0; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index a927a3174c055..573746071eb6b 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -1,10 +1,12 @@ package io.quarkus.oidc.runtime; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; + +import org.eclipse.microprofile.jwt.Claims; import io.quarkus.oidc.OIDCException; import io.vertx.core.json.JsonArray; @@ -16,6 +18,28 @@ private OidcUtils() { } + public static boolean validateClaims(OidcConfig.Token tokenConfig, JsonObject json) { + if (tokenConfig.issuer.isPresent()) { + String issuer = json.getString(Claims.iss.name()); + if (!tokenConfig.issuer.get().equals(issuer)) { + throw new OIDCException("Invalid issuer"); + } + } + if (tokenConfig.audience.isPresent()) { + Object claimValue = json.getValue(Claims.aud.name()); + List audience = Collections.emptyList(); + if (claimValue instanceof JsonArray) { + audience = convertJsonArrayToList((JsonArray) claimValue); + } else if (claimValue != null) { + audience = Arrays.asList((String) claimValue); + } + if (!audience.containsAll(tokenConfig.audience.get())) { + throw new OIDCException("Invalid audience"); + } + } + return true; + } + public static List findRoles(String clientId, OidcConfig.Roles rolesConfig, JsonObject json) { // If the user configured a specific path - check and enforce a claim at this path exists if (rolesConfig.getRoleClaimPath().isPresent()) { @@ -23,9 +47,9 @@ public static List findRoles(String clientId, OidcConfig.Roles rolesConf } // Check 'groups' next - List groups = findClaimWithRoles(rolesConfig, "groups", json, false); + List groups = findClaimWithRoles(rolesConfig, Claims.groups.name(), json, false); if (!groups.isEmpty()) { - return groups.stream().map(v -> v.toString()).collect(Collectors.toList()); + return groups; } else { // Finally, check if this token has been issued by Keycloak. // Return an empty or populated list of realm and resource access roles @@ -45,7 +69,7 @@ private static List findClaimWithRoles(OidcConfig.Roles rolesConfig, Str Object claimValue = findClaimValue(claimPath, json, claimPath.split("/"), 0, mustExist); if (claimValue instanceof JsonArray) { - return ((JsonArray) claimValue).stream().map(v -> v.toString()).collect(Collectors.toList()); + return convertJsonArrayToList((JsonArray) claimValue); } else if (claimValue != null) { String sep = rolesConfig.getRoleClaimSeparator().isPresent() ? rolesConfig.getRoleClaimSeparator().get() : " "; return Arrays.asList(claimValue.toString().split(sep)); @@ -71,4 +95,12 @@ private static Object findClaimValue(String claimPath, JsonObject json, String[] return claimValue; } + + private static List convertJsonArrayToList(JsonArray claimValue) { + List list = new ArrayList<>(claimValue.size()); + for (int i = 0; i < claimValue.size(); i++) { + list.add(claimValue.getString(i)); + } + return list; + } } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java index 39cd4e59d39b7..f40e8c67260d4 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java @@ -14,10 +14,68 @@ import org.junit.jupiter.api.Test; +import io.quarkus.oidc.OIDCException; import io.vertx.core.json.JsonObject; public class OidcUtilsTest { + @Test + public void testTokenWithCorrectIssuer() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromIssuer("https://server.example.com"); + InputStream is = getClass().getResourceAsStream("/tokenIssuer.json"); + assertTrue(OidcUtils.validateClaims(tokenClaims, read(is))); + } + + @Test + public void testTokenWithWrongIssuer() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromIssuer("https://servers.example.com"); + InputStream is = getClass().getResourceAsStream("/tokenIssuer.json"); + try { + OidcUtils.validateClaims(tokenClaims, read(is)); + fail("Exception expected: wrong issuer"); + } catch (OIDCException ex) { + // expected + } + } + + @Test + public void testTokenWithCorrectStringAudience() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromAudience("https://quarkus.example.com"); + InputStream is = getClass().getResourceAsStream("/tokenStringAudience.json"); + assertTrue(OidcUtils.validateClaims(tokenClaims, read(is))); + } + + @Test + public void testTokenWithWrongStringAudience() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromIssuer("https://quarkus.examples.com"); + InputStream is = getClass().getResourceAsStream("/tokenStringAudience.json"); + try { + OidcUtils.validateClaims(tokenClaims, read(is)); + fail("Exception expected: wrong audience"); + } catch (OIDCException ex) { + // expected + } + } + + @Test + public void testTokenWithCorrectArrayAudience() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromAudience("https://quarkus.example.com", "frontend_client_id"); + InputStream is = getClass().getResourceAsStream("/tokenArrayAudience.json"); + assertTrue(OidcUtils.validateClaims(tokenClaims, read(is))); + } + + @Test + public void testTokenWithWrongArrayAudience() throws Exception { + OidcConfig.Token tokenClaims = OidcConfig.Token.fromAudience("service_client_id"); + InputStream is = getClass().getResourceAsStream("/tokenArrayAudience.json"); + try { + OidcUtils.validateClaims(tokenClaims, read(is)); + fail("Exception expected: wrong array audience"); + } catch (OIDCException ex) { + // expected + } + } + @Test public void testKeycloakRealmAccessToken() throws Exception { OidcConfig.Roles rolesCfg = OidcConfig.Roles.fromClaimPath(null); diff --git a/extensions/oidc/runtime/src/test/resources/tokenArrayAudience.json b/extensions/oidc/runtime/src/test/resources/tokenArrayAudience.json new file mode 100644 index 0000000000000..4b67b6c123ad8 --- /dev/null +++ b/extensions/oidc/runtime/src/test/resources/tokenArrayAudience.json @@ -0,0 +1,11 @@ +{ + "iss": "https://server.example.com", + "aud": ["https://quarkus.example.com", "frontend_client_id"], + "jti": "a-123", + "sub": "24400320", + "upn": "jdoe@example.com", + "preferred_username": "jdoe", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969 +} diff --git a/extensions/oidc/runtime/src/test/resources/tokenIssuer.json b/extensions/oidc/runtime/src/test/resources/tokenIssuer.json new file mode 100644 index 0000000000000..4e23506295093 --- /dev/null +++ b/extensions/oidc/runtime/src/test/resources/tokenIssuer.json @@ -0,0 +1,11 @@ +{ + "iss": "https://server.example.com", + "jti": "a-123", + "sub": "24400320", + "upn": "jdoe@example.com", + "preferred_username": "jdoe", + "aud": "s6BhdRkqt3", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969 +} diff --git a/extensions/oidc/runtime/src/test/resources/tokenStringAudience.json b/extensions/oidc/runtime/src/test/resources/tokenStringAudience.json new file mode 100644 index 0000000000000..5fc5ce7b6257b --- /dev/null +++ b/extensions/oidc/runtime/src/test/resources/tokenStringAudience.json @@ -0,0 +1,11 @@ +{ + "iss": "https://server.example.com", + "aud": "https://quarkus.example.com", + "jti": "a-123", + "sub": "24400320", + "upn": "jdoe@example.com", + "preferred_username": "jdoe", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969 +} From e9dc9135aa103b10da1f6a1dd64333bf9ffc688c Mon Sep 17 00:00:00 2001 From: Vinicius Ferraz Date: Fri, 22 Nov 2019 13:05:37 -0300 Subject: [PATCH 247/602] New flyway start property: quarkus.flyway.clean-at-start --- docs/src/main/asciidoc/flyway.adoc | 4 ++ ...ayExtensionCleanAndMigrateAtStartTest.java | 51 +++++++++++++++++ .../test/FlywayExtensionCleanAtStartTest.java | 55 +++++++++++++++++++ ...ean-and-migrate-at-start-config.properties | 12 ++++ .../clean-at-start-config.properties | 12 ++++ .../flyway/runtime/FlywayRecorder.java | 4 ++ .../flyway/runtime/FlywayRuntimeConfig.java | 7 +++ .../flyway/FlywayFunctionalityResource.java | 1 + .../it/flyway/FlywayFunctionalityTest.java | 1 + 9 files changed, 147 insertions(+) create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java create mode 100644 extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties create mode 100644 extensions/flyway/deployment/src/test/resources/clean-at-start-config.properties diff --git a/docs/src/main/asciidoc/flyway.adoc b/docs/src/main/asciidoc/flyway.adoc index e7822eee87ab9..4bbe25d9e7785 100644 --- a/docs/src/main/asciidoc/flyway.adoc +++ b/docs/src/main/asciidoc/flyway.adoc @@ -51,6 +51,10 @@ Also, you can customize the Flyway behaviour by using the following properties: **true** to execute Flyway automatically when the application starts, **false** otherwise. + **default:** false +`quarkus.flyway.clean-at-start`:: +**true** to execute Flyway clean command automatically when the application starts, **false** otherwise. + +**default:** false + `quarkus.flyway.locations`:: Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. Unprefixed locations or locations starting with classpath: point to a package on the classpath and may contain both SQL diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java new file mode 100644 index 0000000000000..b01daee09f30a --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java @@ -0,0 +1,51 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionCleanAndMigrateAtStartTest { + // Quarkus built object + @Inject + Flyway flyway; + + @Inject + AgroalDataSource defaultDataSource; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("db/migration/V1.0.0__Quarkus.sql") + .addAsResource("clean-and-migrate-at-start-config.properties", "application.properties")); + + @Test + @DisplayName("Clean and migrate at start correctly") + public void testFlywayConfigInjection() throws SQLException { + + Connection connection = defaultDataSource.getConnection(); + + try (Statement stat = connection.createStatement()) { + try (ResultSet executeQuery = stat.executeQuery("select * from fake_existing_tbl")) { + assertFalse(executeQuery.next(), "Table exists but is not empty"); + } + } + String currentVersion = flyway.info().current().getVersion().toString(); + assertEquals("1.0.0", currentVersion, "Expected to be 1.0.0 as migration runs at start"); + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java new file mode 100644 index 0000000000000..ec0dbb9f80703 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java @@ -0,0 +1,55 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.MigrationInfo; +import org.h2.jdbc.JdbcSQLException; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionCleanAtStartTest { + // Quarkus built object + @Inject + Flyway flyway; + + @Inject + AgroalDataSource defaultDataSource; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("db/migration/V1.0.0__Quarkus.sql") + .addAsResource("clean-at-start-config.properties", "application.properties")); + + @Test + @DisplayName("Clean at start correctly") + public void testFlywayConfigInjection() throws SQLException { + + Connection connection = defaultDataSource.getConnection(); + + try (Statement stat = connection.createStatement()) { + try (ResultSet executeQuery = stat.executeQuery("select * from fake_existing_tbl")) { + fail("fake_existing_tbl should not exist"); + } catch (JdbcSQLException e) { + // expected fake_existing_tbl does not exist + } + } + MigrationInfo current = flyway.info().current(); + assertNull(current, "Info is not null"); + } +} diff --git a/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties new file mode 100644 index 0000000000000..ab9eb76e6397d --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/clean-and-migrate-at-start-config.properties @@ -0,0 +1,12 @@ +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test-quarkus-clean-at-start;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'src/test/resources/h2-init-data.sql' +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.username=sa +quarkus.datasource.password=sa + +# Flyway config properties +quarkus.flyway.clean-at-start=true +quarkus.flyway.migrate-at-start=true +quarkus.flyway.table=test_flyway_history +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=0.0.1 +quarkus.flyway.baseline-description=Initial description for test diff --git a/extensions/flyway/deployment/src/test/resources/clean-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/clean-at-start-config.properties new file mode 100644 index 0000000000000..0f86cfc9137f4 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/clean-at-start-config.properties @@ -0,0 +1,12 @@ +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test-quarkus-clean-at-start;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'src/test/resources/h2-init-data.sql' +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.username=sa +quarkus.datasource.password=sa + +# Flyway config properties +quarkus.flyway.clean-at-start=true +quarkus.flyway.migrate-at-start=false +quarkus.flyway.table=test_flyway_history +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=0.0.1 +quarkus.flyway.baseline-description=Initial description for test diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index c71e570d503c5..76e5dd17416d6 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -21,6 +21,10 @@ public void configureFlywayProperties(FlywayRuntimeConfig flywayRuntimeConfig, B } public void doStartActions(FlywayRuntimeConfig config, BeanContainer container) { + if (config.cleanAtStart) { + Flyway flyway = container.instance(Flyway.class); + flyway.clean(); + } if (config.migrateAtStart) { Flyway flyway = container.instance(Flyway.class); flyway.migrate(); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index a8e3a7c0e0d2d..86936fa2bc697 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -51,6 +51,13 @@ public final class FlywayRuntimeConfig { @ConfigItem public Optional repeatableSqlMigrationPrefix; + /** + * true to execute Flyway clean command automatically when the application starts, false otherwise. + * + */ + @ConfigItem + public boolean cleanAtStart; + /** * true to execute Flyway automatically when the application starts, false otherwise. * diff --git a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java index 201be80dbcc49..a5c2e0796e1c8 100644 --- a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java +++ b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java @@ -27,4 +27,5 @@ public String doMigrateAuto() { "Version is null! Migration was not applied"); return version.toString(); } + } \ No newline at end of file diff --git a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java index 9b0a56c2c41b6..03b2bd8db4b28 100644 --- a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java +++ b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java @@ -17,4 +17,5 @@ public class FlywayFunctionalityTest { public void testFlywayQuarkusFunctionality() { when().get("/flyway/migrate").then().body(is("1.0.1")); } + } From 536f138dc0f75c549f5c14bce44e407951e55db6 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 6 Dec 2019 15:13:14 +0100 Subject: [PATCH 248/602] BOM - exclude jakarta.ejb-api in the jakarta.interceptor-api dependency - there is a bug in v1.2.5, should be fixed in the next version --- bom/runtime/pom.xml | 8 ++++++++ extensions/infinispan-embedded/runtime/pom.xml | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index aa0963fe0efb0..307fcb41c1d64 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -1278,6 +1278,14 @@ jakarta.interceptor jakarta.interceptor-api ${jakarta.interceptor-api.version} + + + + + jakarta.ejb + jakarta.ejb-api + + org.jboss.spec.javax.xml.bind diff --git a/extensions/infinispan-embedded/runtime/pom.xml b/extensions/infinispan-embedded/runtime/pom.xml index db14ebc7fb6e9..c923679a6f8fc 100644 --- a/extensions/infinispan-embedded/runtime/pom.xml +++ b/extensions/infinispan-embedded/runtime/pom.xml @@ -52,6 +52,10 @@ narayana-jta true + + jakarta.transaction + jakarta.transaction-api + org.graalvm.nativeimage svm From 68506dcdd99bc88e89d2d58e042c480f1542447e Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 6 Dec 2019 19:55:32 +0530 Subject: [PATCH 249/602] issue-3592 Make sure the dev mode jar generated through Gradle doesn't run into issues related to "Class-Path" attribute parsing in the Manifest file of the jar --- .../io/quarkus/gradle/tasks/QuarkusDev.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index b945d6a18efd2..17b789afebc30 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -54,7 +54,6 @@ import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.bootstrap.resolver.AppModelResolverException; -import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.dev.DevModeContext; import io.quarkus.dev.DevModeMain; import io.quarkus.gradle.QuarkusPluginExtension; @@ -319,6 +318,8 @@ public void startDev() { context.setFrameworkClassesDir(wiringClassesDirectory.getAbsoluteFile()); context.setCacheDir(new File(getBuildDir(), "transformer-cache").getAbsoluteFile()); + // this is the jar file we will use to launch the dev mode main class + context.setDevModeRunnerJarFile(tempFile); try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { out.putNextEntry(new ZipEntry("META-INF/")); Manifest manifest = new Manifest(); @@ -407,19 +408,9 @@ private void addToClassPaths(StringBuilder classPathManifest, DevModeContext con if (filesIncludedInClasspath.add(file)) { getProject().getLogger().info("Adding dependency {}", file); - URI uri = file.toPath().toAbsolutePath().toUri(); - String path = uri.getRawPath(); - if (PropertyUtils.isWindows()) { - if (path.length() > 2 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':') { - path = "/" + path; - } - } - classPathManifest.append(path); + final URI uri = file.toPath().toAbsolutePath().toUri(); context.getClassPath().add(toUrl(uri)); - if (file.isDirectory()) { - classPathManifest.append("/"); - } - classPathManifest.append(" "); + classPathManifest.append(uri).append(" "); } } From 25f5d904d2ec2d29621d411dc3e63d8b57976a89 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Fri, 6 Dec 2019 16:04:16 +0100 Subject: [PATCH 250/602] Use getHandler helper method in logging tests --- .../logging/AdditionalHandlersTest.java | 18 ++--------- .../logging/AsyncConsoleHandlerTest.java | 17 ++--------- .../quarkus/logging/AsyncFileHandlerTest.java | 17 ++--------- .../logging/AsyncSyslogHandlerTest.java | 17 ++--------- .../quarkus/logging/ConsoleHandlerTest.java | 15 ++-------- .../io/quarkus/logging/FileHandlerTest.java | 16 ++-------- .../quarkus/logging/LoggingTestsHelper.java | 30 +++++++++++++++++++ .../logging/PeriodicRotatingLoggingTest.java | 17 ++--------- .../PeriodicSizeRotatingLoggingTest.java | 17 ++--------- .../logging/SizeRotatingLoggingTest.java | 17 ++--------- .../io/quarkus/logging/SyslogHandlerTest.java | 16 ++-------- .../json/JsonFormatterCustomConfigTest.java | 27 ++--------------- .../json/JsonFormatterDefaultConfigTest.java | 20 ++++++++----- 13 files changed, 64 insertions(+), 180 deletions(-) create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java index 911ccd99d1238..7f154ab1f567c 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AdditionalHandlersTest.java @@ -1,21 +1,17 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.extest.runtime.logging.AdditionalLogHandlerValueFactory.TestHandler; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class AdditionalHandlersTest { @@ -28,17 +24,7 @@ public class AdditionalHandlersTest { @Test public void additionalHandlersConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof TestHandler)) - .findFirst().orElse(null); - assertThat(handler).isNotNull(); + Handler handler = getHandler(TestHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.FINE); TestHandler testHandler = (TestHandler) handler; diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java index 11c64e17dc845..e49cfb1b5df3c 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncConsoleHandlerTest.java @@ -1,22 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.handlers.AsyncHandler; import org.jboss.logmanager.handlers.ConsoleHandler; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class AsyncConsoleHandlerTest { @@ -30,17 +27,7 @@ public class AsyncConsoleHandlerTest { @Test public void asyncConsoleHandlerConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof AsyncHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(AsyncHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.WARNING); AsyncHandler asyncHandler = (AsyncHandler) handler; diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java index 8a2130123f659..f5bf133d2007f 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncFileHandlerTest.java @@ -1,22 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.handlers.AsyncHandler; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.FileHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class AsyncFileHandlerTest { @@ -30,17 +27,7 @@ public class AsyncFileHandlerTest { @Test public void asyncFileHandlerConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof AsyncHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(AsyncHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.INFO); AsyncHandler asyncHandler = (AsyncHandler) handler; diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java index 23bdb08f74a47..85e34a6d821f6 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/AsyncSyslogHandlerTest.java @@ -1,22 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.handlers.AsyncHandler; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SyslogHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class AsyncSyslogHandlerTest { @@ -30,17 +27,7 @@ public class AsyncSyslogHandlerTest { @Test public void asyncSyslogHandlerConfigurationTest() throws NullPointerException { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof AsyncHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(AsyncHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.WARNING); AsyncHandler asyncHandler = (AsyncHandler) handler; diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java index 9207216456445..72be77bf4c898 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/ConsoleHandlerTest.java @@ -1,23 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.ConsoleHandler; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class ConsoleHandlerTest { @@ -30,14 +26,7 @@ public class ConsoleHandlerTest { @Test public void consoleOutputTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (h instanceof ConsoleHandler)) - .findFirst().get(); + Handler handler = getHandler(ConsoleHandler.class); assertThat(handler).isNotNull(); assertThat(handler.getLevel()).isEqualTo(Level.WARNING); diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java index 6af882744b3bc..4c97d6a9e9210 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/FileHandlerTest.java @@ -1,23 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.FileHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class FileHandlerTest { @@ -30,15 +26,7 @@ public class FileHandlerTest { @Test public void fileOutputTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (h instanceof FileHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(FileHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.INFO); Formatter formatter = handler.getFormatter(); diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java new file mode 100644 index 0000000000000..1a6879fb096a6 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/LoggingTestsHelper.java @@ -0,0 +1,30 @@ +package io.quarkus.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; + +import io.quarkus.runtime.logging.InitialConfigurator; + +public class LoggingTestsHelper { + + public static Handler getHandler(Class clazz) { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (clazz.isInstance(h))) + .findFirst().get(); + assertThat(handler).isNotNull(); + return handler; + } +} diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java index 30d714602a24e..2a7e02df80762 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicRotatingLoggingTest.java @@ -1,23 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.PeriodicRotatingFileHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class PeriodicRotatingLoggingTest { @@ -31,16 +27,7 @@ public class PeriodicRotatingLoggingTest { @Test public void periodicRotatingConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof PeriodicRotatingFileHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(PeriodicRotatingFileHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.INFO); Formatter formatter = handler.getFormatter(); diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java index fdf584a222510..ec2def70f9702 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/PeriodicSizeRotatingLoggingTest.java @@ -1,23 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class PeriodicSizeRotatingLoggingTest { @@ -31,16 +27,7 @@ public class PeriodicSizeRotatingLoggingTest { @Test public void periodicSizeRotatingConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof PeriodicSizeRotatingFileHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(PeriodicSizeRotatingFileHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.INFO); Formatter formatter = handler.getFormatter(); diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java index 466ed5d3d3e7a..4e9ff087f668d 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SizeRotatingLoggingTest.java @@ -1,23 +1,19 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SizeRotatingFileHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class SizeRotatingLoggingTest { @@ -31,16 +27,7 @@ public class SizeRotatingLoggingTest { @Test public void sizeRotatingConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof SizeRotatingFileHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(SizeRotatingFileHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.INFO); Formatter formatter = handler.getFormatter(); diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java index a8761b5ae7a4b..6c6423feef649 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/SyslogHandlerTest.java @@ -1,25 +1,21 @@ package io.quarkus.logging; +import static io.quarkus.logging.LoggingTestsHelper.getHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.wildfly.common.net.HostName.getQualifiedHostName; import static org.wildfly.common.os.Process.getProcessName; -import java.util.Arrays; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.PatternFormatter; -import org.jboss.logmanager.handlers.DelayedHandler; import org.jboss.logmanager.handlers.SyslogHandler; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class SyslogHandlerTest { @@ -32,15 +28,7 @@ public class SyslogHandlerTest { @Test public void syslogOutputTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (h instanceof SyslogHandler)) - .findFirst().get(); - assertThat(handler).isNotNull(); + Handler handler = getHandler(SyslogHandler.class); assertThat(handler.getLevel()).isEqualTo(Level.WARNING); Formatter formatter = handler.getFormatter(); diff --git a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java index 4641620751fab..d99145d123d68 100644 --- a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java +++ b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterCustomConfigTest.java @@ -1,23 +1,15 @@ package io.quarkus.logging.json; +import static io.quarkus.logging.json.JsonFormatterDefaultConfigTest.getJsonFormatter; import static org.assertj.core.api.Assertions.assertThat; import java.time.ZoneId; -import java.util.Arrays; -import java.util.logging.Formatter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; import org.jboss.logmanager.formatters.JsonFormatter; import org.jboss.logmanager.formatters.StructuredFormatter; -import org.jboss.logmanager.handlers.ConsoleHandler; -import org.jboss.logmanager.handlers.DelayedHandler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.runtime.logging.InitialConfigurator; import io.quarkus.test.QuarkusUnitTest; public class JsonFormatterCustomConfigTest { @@ -28,22 +20,7 @@ public class JsonFormatterCustomConfigTest { @Test public void jsonFormatterCustomConfigurationTest() { - LogManager logManager = LogManager.getLogManager(); - assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); - - DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; - assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); - assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); - - Handler handler = Arrays.stream(delayedHandler.getHandlers()) - .filter(h -> (h instanceof ConsoleHandler)) - .findFirst().orElse(null); - assertThat(handler).isNotNull(); - assertThat(handler.getLevel()).isEqualTo(Level.WARNING); - - Formatter formatter = handler.getFormatter(); - assertThat(formatter).isInstanceOf(JsonFormatter.class); - JsonFormatter jsonFormatter = (JsonFormatter) formatter; + JsonFormatter jsonFormatter = getJsonFormatter(); assertThat(jsonFormatter.isPrettyPrint()).isTrue(); assertThat(jsonFormatter.getDateTimeFormatter().toString()) .isEqualTo("Value(DayOfMonth)' 'Text(MonthOfYear,SHORT)' 'Value(Year,4,19,EXCEEDS_PAD)"); diff --git a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java index fd15d5d34f79f..9fa56df1e4cbd 100644 --- a/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java +++ b/extensions/logging-json/deployment/src/test/java/io/quarkus/logging/json/JsonFormatterDefaultConfigTest.java @@ -29,6 +29,17 @@ public class JsonFormatterDefaultConfigTest { @Test public void jsonFormatterDefaultConfigurationTest() { + JsonFormatter jsonFormatter = getJsonFormatter(); + assertThat(jsonFormatter.isPrettyPrint()).isFalse(); + assertThat(jsonFormatter.getDateTimeFormatter().toString()) + .isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()).toString()); + assertThat(jsonFormatter.getDateTimeFormatter().getZone()).isEqualTo(ZoneId.systemDefault()); + assertThat(jsonFormatter.getExceptionOutputType()).isEqualTo(StructuredFormatter.ExceptionOutputType.DETAILED); + assertThat(jsonFormatter.getRecordDelimiter()).isEqualTo("\n"); + assertThat(jsonFormatter.isPrintDetails()).isFalse(); + } + + public static JsonFormatter getJsonFormatter() { LogManager logManager = LogManager.getLogManager(); assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); @@ -44,13 +55,6 @@ public void jsonFormatterDefaultConfigurationTest() { Formatter formatter = handler.getFormatter(); assertThat(formatter).isInstanceOf(JsonFormatter.class); - JsonFormatter jsonFormatter = (JsonFormatter) formatter; - assertThat(jsonFormatter.isPrettyPrint()).isFalse(); - assertThat(jsonFormatter.getDateTimeFormatter().toString()) - .isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()).toString()); - assertThat(jsonFormatter.getDateTimeFormatter().getZone()).isEqualTo(ZoneId.systemDefault()); - assertThat(jsonFormatter.getExceptionOutputType()).isEqualTo(StructuredFormatter.ExceptionOutputType.DETAILED); - assertThat(jsonFormatter.getRecordDelimiter()).isEqualTo("\n"); - assertThat(jsonFormatter.isPrintDetails()).isFalse(); + return (JsonFormatter) formatter; } } From 5621abfa7ae02e18aa65292c7e2bb662a4105dc7 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 6 Dec 2019 10:57:26 -0300 Subject: [PATCH 251/602] Do not use MAVEN_OPTS while building the gradle project --- devtools/gradle/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index 31ff3fc02b485..d41f3f8654bd1 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -64,10 +64,10 @@ ${gradle.task} -Pdescription=${project.description} -S - ${env.MAVEN_OPTS} ${settings.localRepository} + ${env.MAVEN_OPTS} ${skip.gradle.build} From 4ff147d01312cb732a28c2a93613e26aa352faa5 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 21:18:40 +0200 Subject: [PATCH 252/602] Refactor Jackson project to a single module project --- integration-tests/jackson/Readme.md | 124 ----------------- integration-tests/jackson/model/pom.xml | 99 -------------- .../src/main/resources/META-INF/beans.xml | 8 -- integration-tests/jackson/pom.xml | 117 ++++++++++++++-- integration-tests/jackson/service/pom.xml | 126 ------------------ .../src/main/resources/application.properties | 2 - .../InheritedModelWithBuilderResource.java | 4 +- .../it/jackson}/ModelWithBuilderResource.java | 4 +- ...SerializerDeserializerOnFieldResource.java | 4 +- .../it/jackson}/MyObjectMapperCustomizer.java | 2 +- .../jackson}/RegisteredPojoModelResource.java | 4 +- .../model/InheritedModelWithBuilder.java | 2 +- .../model/InheritedModelWithBuilderBase.java | 2 +- .../it/jackson}/model/ModelWithBuilder.java | 2 +- ...lWithSerializerAndDeserializerOnField.java | 2 +- .../jackson}/model/RegisteredPojoModel.java | 2 +- .../InheritedModelWithBuilderResourceIT.java | 2 +- ...InheritedModelWithBuilderResourceTest.java | 4 +- .../jackson}/ModelWithBuilderResourceIT.java | 2 +- .../ModelWithBuilderResourceTest.java | 4 +- ...lizerAndDeserializerOnFieldResourceIT.java | 2 +- ...zerAndDeserializerOnFieldResourceTest.java | 4 +- .../it/jackson}/ObjectMapperModulesTest.java | 2 +- .../RegisteredPojoModelResourceIT.java | 2 +- .../RegisteredPojoModelResourceTest.java | 4 +- .../model/InheritedModelWithBuilderTest.java | 2 +- .../jackson}/model/ModelWithBuilderTest.java | 2 +- .../model/RegisteredPojoModelTest.java | 2 +- 28 files changed, 133 insertions(+), 403 deletions(-) delete mode 100644 integration-tests/jackson/Readme.md delete mode 100644 integration-tests/jackson/model/pom.xml delete mode 100644 integration-tests/jackson/model/src/main/resources/META-INF/beans.xml delete mode 100644 integration-tests/jackson/service/pom.xml delete mode 100644 integration-tests/jackson/service/src/main/resources/application.properties rename integration-tests/jackson/{service/src/main/java/io/quarkus/reproducer => src/main/java/io/quarkus/it/jackson}/InheritedModelWithBuilderResource.java (85%) rename integration-tests/jackson/{service/src/main/java/io/quarkus/reproducer => src/main/java/io/quarkus/it/jackson}/ModelWithBuilderResource.java (85%) rename integration-tests/jackson/{service/src/main/java/io/quarkus/reproducer => src/main/java/io/quarkus/it/jackson}/ModelWithSerializerDeserializerOnFieldResource.java (91%) rename integration-tests/jackson/{service/src/main/java/io/quarkus/reproducer => src/main/java/io/quarkus/it/jackson}/MyObjectMapperCustomizer.java (96%) rename integration-tests/jackson/{service/src/main/java/io/quarkus/reproducer => src/main/java/io/quarkus/it/jackson}/RegisteredPojoModelResource.java (85%) rename integration-tests/jackson/{model/src/main/java/io/quarkus/reproducer/jacksonbuilder => src/main/java/io/quarkus/it/jackson}/model/InheritedModelWithBuilder.java (98%) rename integration-tests/jackson/{model/src/main/java/io/quarkus/reproducer/jacksonbuilder => src/main/java/io/quarkus/it/jackson}/model/InheritedModelWithBuilderBase.java (98%) rename integration-tests/jackson/{model/src/main/java/io/quarkus/reproducer/jacksonbuilder => src/main/java/io/quarkus/it/jackson}/model/ModelWithBuilder.java (98%) rename integration-tests/jackson/{model/src/main/java/io/quarkus/reproducer/jacksonbuilder => src/main/java/io/quarkus/it/jackson}/model/ModelWithSerializerAndDeserializerOnField.java (97%) rename integration-tests/jackson/{model/src/main/java/io/quarkus/reproducer/jacksonbuilder => src/main/java/io/quarkus/it/jackson}/model/RegisteredPojoModel.java (97%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/InheritedModelWithBuilderResourceIT.java (87%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/InheritedModelWithBuilderResourceTest.java (89%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/ModelWithBuilderResourceIT.java (86%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/ModelWithBuilderResourceTest.java (89%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/ModelWithSerializerAndDeserializerOnFieldResourceIT.java (86%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/ModelWithSerializerAndDeserializerOnFieldResourceTest.java (91%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/ObjectMapperModulesTest.java (97%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/RegisteredPojoModelResourceIT.java (86%) rename integration-tests/jackson/{service/src/test/java/io/quarkus/reproducer => src/test/java/io/quarkus/it/jackson}/RegisteredPojoModelResourceTest.java (89%) rename integration-tests/jackson/{model/src/test/java/io/quarkus/reproducer/jacksonbuilder => src/test/java/io/quarkus/it/jackson}/model/InheritedModelWithBuilderTest.java (97%) rename integration-tests/jackson/{model/src/test/java/io/quarkus/reproducer/jacksonbuilder => src/test/java/io/quarkus/it/jackson}/model/ModelWithBuilderTest.java (97%) rename integration-tests/jackson/{model/src/test/java/io/quarkus/reproducer/jacksonbuilder => src/test/java/io/quarkus/it/jackson}/model/RegisteredPojoModelTest.java (95%) diff --git a/integration-tests/jackson/Readme.md b/integration-tests/jackson/Readme.md deleted file mode 100644 index 316091e562645..0000000000000 --- a/integration-tests/jackson/Readme.md +++ /dev/null @@ -1,124 +0,0 @@ -# Quarkus Jackson extension tester - -This project verifies Jackson (de-)serialization support in native mode. - -This project consists of the following modules: - -- model - - This module contains the library with a simple models. This model makes use - of Jackson to support (de-)serialization to JSON. Various forms of models - exists. At the time of writing the following models are present/tested: - - Immutable models using a Builder to construct new instances. - - Simple POJO model being registered for reflection. - - Unit tests are available to prove JVM based JSON (de-)serialization works - properly. - -- service - - This module contains a very simple RESTful resources with only a POST method - to POST new models to this service. A simple unit test is included to verify - correct behaviour for both unit and integration test. - - The following curl command can be used to send POST request to this service: - - ``` - curl -X POST -H "Content-Type: application/json" \ - -d '{"version": 2, "id": "123", "value": "val"}' \ - -v localhost:8080/ - ``` - - -## Build - -To build the project, run the following command from the project root directory: - -``` -mvn clean package -``` - -This build should run correctly showing no errors and no test failures. - -For the remainder make the service module your current working directory: - -``` -cd service -``` - -## Package JVM - -Running a JVM based version of the service can either be done with `quarkus:dev` -or by using the JVM based runner. - -- **Using `quarkus:dev`** - ``` - mvn quarkus:dev - ``` - -- **Using JVM runner** - ``` - java -jar target/service-999-SNAPSHOT-runner.jar - ``` - -In either case posting new model data like described earlier should result in -a successful `201` response code with the posted message in the body. For example: - -``` -~$ curl -X POST -H "Content-Type: application/json" -d '{"version": 2, "id": "123", "value": "val"}' -v localhost:8080/model -Note: Unnecessary use of -X or --request, POST is already inferred. -* Trying 127.0.0.1... -* TCP_NODELAY set -* Connected to localhost (127.0.0.1) port 8080 (#0) -> POST /model HTTP/1.1 -> Host: localhost:8080 -> User-Agent: curl/7.58.0 -> Accept: */* -> Content-Type: application/json -> Content-Length: 43 -> -* upload completely sent off: 43 out of 43 bytes -< HTTP/1.1 201 Created -< Connection: keep-alive -< Content-Type: application/json -< Content-Length: 38 -< Date: Thu, 22 Aug 2019 13:31:48 GMT -< -* Connection #0 to host localhost left intact -{"version":2,"id":"123","value":"val"} -``` - -## Package Native - -Checking proper behaviour can be achieved in the following 2 ways: - -- **Integration test** - - This scenario requires no additional manual steps besides - executing the following command: - - ``` - mvn integration-test verify -Pnative - ``` - - The application will be started automatically and test - scenario's will run. The output will indicate whether the - test ran successfully or not. - - In this scenario it is not possible to post new model data - manually. This can be achieved by using the next scenario. - -- **Native runner** - - Running the native version of the service manually like: - - ``` - mvn package -Pnative - ... - ./target/service-999-SNAPSHOT-runner - ``` - - Now the application is running new model data can be posted - like described earlier. This should result in a successful - `201` response code with the posted message in the response - body. Just like the JVM example given previously. diff --git a/integration-tests/jackson/model/pom.xml b/integration-tests/jackson/model/pom.xml deleted file mode 100644 index a43fe3c70e557..0000000000000 --- a/integration-tests/jackson/model/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - 4.0.0 - - - quarkus-integration-test-jackson-parent - io.quarkus - 999-SNAPSHOT - - - quarkus-integration-test-jackson-model - - Quarkus - Integration Tests - Jackson - model - - - 2.9.9.20190807 - - - - - - com.fasterxml.jackson - jackson-bom - ${jackson.version} - import - pom - - - - - - - io.quarkus - quarkus-arc - - - - - org.slf4j - slf4j-api - - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.module - jackson-module-parameter-names - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.assertj - assertj-core - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - - - - - diff --git a/integration-tests/jackson/model/src/main/resources/META-INF/beans.xml b/integration-tests/jackson/model/src/main/resources/META-INF/beans.xml deleted file mode 100644 index a89ed29a0fed7..0000000000000 --- a/integration-tests/jackson/model/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/integration-tests/jackson/pom.xml b/integration-tests/jackson/pom.xml index 9ef944831065e..b149bf94f997c 100644 --- a/integration-tests/jackson/pom.xml +++ b/integration-tests/jackson/pom.xml @@ -11,23 +11,112 @@ ../ - quarkus-integration-test-jackson-parent - pom - + quarkus-integration-test-jackson Quarkus - Integration Tests - Jackson + Jackson integration tests module + + + + io.quarkus + quarkus-jackson + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.assertj + assertj-core + test + + - - - + + + io.quarkus - quarkus-integration-test-jackson-model + quarkus-maven-plugin ${project.version} - - - + + + + build + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + - - model - service - + + + native + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + diff --git a/integration-tests/jackson/service/pom.xml b/integration-tests/jackson/service/pom.xml deleted file mode 100644 index 2acbe606daa66..0000000000000 --- a/integration-tests/jackson/service/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - 4.0.0 - - - quarkus-integration-test-jackson-parent - io.quarkus - 999-SNAPSHOT - - - quarkus-integration-test-jackson-service - - Quarkus - Integration Tests - Jackson - service - - - - io.quarkus - quarkus-integration-test-jackson-model - - - io.quarkus - quarkus-jackson - - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - org.assertj - assertj-core - test - - - - - - - io.quarkus - quarkus-maven-plugin - ${project.version} - - - - build - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.jboss.logmanager.LogManager - - - - - - - - - native - - - native - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - integration-test - verify - - - - - ${project.build.directory}/${project.build.finalName}-runner - - - - - - - - io.quarkus - quarkus-maven-plugin - ${project.version} - - - native-image - - native-image - - - true - true - ${graalvmHome} - - - - - - - - - diff --git a/integration-tests/jackson/service/src/main/resources/application.properties b/integration-tests/jackson/service/src/main/resources/application.properties deleted file mode 100644 index 3c1ac56a1ad0a..0000000000000 --- a/integration-tests/jackson/service/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -# Configuration file -# key = value \ No newline at end of file diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/InheritedModelWithBuilderResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/InheritedModelWithBuilderResource.java similarity index 85% rename from integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/InheritedModelWithBuilderResource.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/InheritedModelWithBuilderResource.java index e36b0590762e2..401cbe0e0ad26 100644 --- a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/InheritedModelWithBuilderResource.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/InheritedModelWithBuilderResource.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import java.io.IOException; @@ -9,7 +9,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import io.quarkus.reproducer.jacksonbuilder.model.InheritedModelWithBuilder; +import io.quarkus.it.jackson.model.InheritedModelWithBuilder; @Path("/inheritedmodelwithbuilder") public class InheritedModelWithBuilderResource { diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithBuilderResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithBuilderResource.java similarity index 85% rename from integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithBuilderResource.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithBuilderResource.java index f0cc6791011ac..ab68091944306 100644 --- a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithBuilderResource.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithBuilderResource.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import java.io.IOException; @@ -9,7 +9,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import io.quarkus.reproducer.jacksonbuilder.model.ModelWithBuilder; +import io.quarkus.it.jackson.model.ModelWithBuilder; @Path("/modelwithbuilder") public class ModelWithBuilderResource { diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithSerializerDeserializerOnFieldResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithSerializerDeserializerOnFieldResource.java similarity index 91% rename from integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithSerializerDeserializerOnFieldResource.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithSerializerDeserializerOnFieldResource.java index 2f7e247bdeceb..7950042725348 100644 --- a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/ModelWithSerializerDeserializerOnFieldResource.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/ModelWithSerializerDeserializerOnFieldResource.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import java.io.IOException; @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.reproducer.jacksonbuilder.model.ModelWithSerializerAndDeserializerOnField; +import io.quarkus.it.jackson.model.ModelWithSerializerAndDeserializerOnField; @Path("fieldserder") public class ModelWithSerializerDeserializerOnFieldResource { diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/MyObjectMapperCustomizer.java similarity index 96% rename from integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/MyObjectMapperCustomizer.java index 5e625bbcb4aff..fe6e71ac479e2 100644 --- a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/MyObjectMapperCustomizer.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/MyObjectMapperCustomizer.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import javax.inject.Singleton; diff --git a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/RegisteredPojoModelResource.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/RegisteredPojoModelResource.java similarity index 85% rename from integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/RegisteredPojoModelResource.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/RegisteredPojoModelResource.java index ec4922605463b..036bad701712d 100644 --- a/integration-tests/jackson/service/src/main/java/io/quarkus/reproducer/RegisteredPojoModelResource.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/RegisteredPojoModelResource.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import java.io.IOException; @@ -9,7 +9,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import io.quarkus.reproducer.jacksonbuilder.model.RegisteredPojoModel; +import io.quarkus.it.jackson.model.RegisteredPojoModel; @Path("/registeredpojomodel") public class RegisteredPojoModelResource { diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilder.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java similarity index 98% rename from integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilder.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java index a6b290536fc3b..34103d9dcd124 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilder.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import java.io.IOException; diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java similarity index 98% rename from integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java index acba0bad18d96..e1578f0af4c63 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderBase.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import java.util.Optional; diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java similarity index 98% rename from integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java index cf0cddca82bb0..2b04cd2ab37bb 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilder.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import java.io.IOException; diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithSerializerAndDeserializerOnField.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithSerializerAndDeserializerOnField.java similarity index 97% rename from integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithSerializerAndDeserializerOnField.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithSerializerAndDeserializerOnField.java index a625b9da20e23..8cb4ff7f6963d 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithSerializerAndDeserializerOnField.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithSerializerAndDeserializerOnField.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import java.io.IOException; diff --git a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java similarity index 97% rename from integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java rename to integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java index 8e72ae371bed1..7f1a0322d41e6 100644 --- a/integration-tests/jackson/model/src/main/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModel.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import java.io.IOException; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceIT.java similarity index 87% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceIT.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceIT.java index 8542b30166c69..7fa1d13f53463 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceIT.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceIT.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import io.quarkus.test.junit.NativeImageTest; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java similarity index 89% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java index 626fb2c94c597..62ff0aea412e9 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/InheritedModelWithBuilderResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import io.quarkus.reproducer.jacksonbuilder.model.InheritedModelWithBuilder; +import io.quarkus.it.jackson.model.InheritedModelWithBuilder; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceIT.java similarity index 86% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceIT.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceIT.java index f854d2eba289b..742a1fa4e133d 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceIT.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceIT.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import io.quarkus.test.junit.NativeImageTest; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java similarity index 89% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java index 6aa87b373a518..5e50f6690e10e 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithBuilderResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import io.quarkus.reproducer.jacksonbuilder.model.ModelWithBuilder; +import io.quarkus.it.jackson.model.ModelWithBuilder; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceIT.java similarity index 86% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceIT.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceIT.java index 6be3abd5a8037..f8fc807a6548e 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceIT.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceIT.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import io.quarkus.test.junit.NativeImageTest; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceTest.java similarity index 91% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceTest.java index 5b1c7cb33adc6..654283c8b2e6a 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ModelWithSerializerAndDeserializerOnFieldResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithSerializerAndDeserializerOnFieldResourceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.reproducer.jacksonbuilder.model.ModelWithSerializerAndDeserializerOnField; +import io.quarkus.it.jackson.model.ModelWithSerializerAndDeserializerOnField; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ObjectMapperModulesTest.java similarity index 97% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ObjectMapperModulesTest.java index 6b1cfa05e3ac2..01e9d4ee88c6c 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/ObjectMapperModulesTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ObjectMapperModulesTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import static org.assertj.core.api.Assertions.*; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceIT.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceIT.java similarity index 86% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceIT.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceIT.java index 61f2563b53858..a9ca09760ccc5 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceIT.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceIT.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import io.quarkus.test.junit.NativeImageTest; diff --git a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java similarity index 89% rename from integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java index 63f8d65bcabe3..525e589c47a5e 100644 --- a/integration-tests/jackson/service/src/test/java/io/quarkus/reproducer/RegisteredPojoModelResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer; +package io.quarkus.it.jackson; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import io.quarkus.reproducer.jacksonbuilder.model.RegisteredPojoModel; +import io.quarkus.it.jackson.model.RegisteredPojoModel; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest diff --git a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderTest.java similarity index 97% rename from integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderTest.java index 53c6cc47132a1..f7fd5674df2fa 100644 --- a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/InheritedModelWithBuilderTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import static org.assertj.core.api.Assertions.assertThat; diff --git a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilderTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/ModelWithBuilderTest.java similarity index 97% rename from integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilderTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/ModelWithBuilderTest.java index aae001da3a01e..00eb02a4b5a2b 100644 --- a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/ModelWithBuilderTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/ModelWithBuilderTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import static org.assertj.core.api.Assertions.assertThat; diff --git a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModelTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/RegisteredPojoModelTest.java similarity index 95% rename from integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModelTest.java rename to integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/RegisteredPojoModelTest.java index b0aeceb31a8cf..56ebe7d159dca 100644 --- a/integration-tests/jackson/model/src/test/java/io/quarkus/reproducer/jacksonbuilder/model/RegisteredPojoModelTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/model/RegisteredPojoModelTest.java @@ -1,4 +1,4 @@ -package io.quarkus.reproducer.jacksonbuilder.model; +package io.quarkus.it.jackson.model; import static org.assertj.core.api.Assertions.assertThat; From b74ab02c07eda1272e2e0f369a35740c98e32dc4 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 21:35:36 +0200 Subject: [PATCH 253/602] Fix broken jackson native tests Fixes: #5993 --- .../model/InheritedModelWithBuilder.java | 14 +++++++--- .../model/InheritedModelWithBuilderBase.java | 16 ------------ .../it/jackson/model/ModelWithBuilder.java | 13 +++------- .../it/jackson/model/RegisteredPojoModel.java | 13 +++------- ...InheritedModelWithBuilderResourceTest.java | 3 ++- .../jackson/ModelWithBuilderResourceTest.java | 3 ++- .../RegisteredPojoModelResourceTest.java | 3 ++- .../java/io/quarkus/it/jackson/TestUtil.java | 26 +++++++++++++++++++ 8 files changed, 48 insertions(+), 43 deletions(-) create mode 100644 integration-tests/jackson/src/test/java/io/quarkus/it/jackson/TestUtil.java diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java index 34103d9dcd124..27bc35668f85b 100644 --- a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilder.java @@ -4,9 +4,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import io.quarkus.arc.Arc; + /** * Simple model class. */ @@ -34,12 +37,11 @@ private InheritedModelWithBuilder(final Builder builder) { // ------------------------------------------------------------------------- public String toJson() throws IOException { - String json = getObjectMapper().writeValueAsString(this); - return json; + return toJson(getObjectMapper()); } - public static String toJson(final InheritedModelWithBuilder model) throws IOException { - return model.toJson(); + public String toJson(ObjectMapper objectMapper) throws IOException { + return objectMapper.writeValueAsString(this); } public static InheritedModelWithBuilder fromJson(final String json) throws IOException { @@ -96,4 +98,8 @@ public InheritedModelWithBuilder build() { return new InheritedModelWithBuilder(this); } } + + private static ObjectMapper getObjectMapper() { + return Arc.container().instance(ObjectMapper.class).get(); + } } diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java index e1578f0af4c63..bc649a9f4d7f4 100644 --- a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java @@ -5,19 +5,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.arc.Arc; - /** * Model class with inheritance and builder. */ abstract class InheritedModelWithBuilderBase { - // ------------------------------------------------------------------------- - // Class attributes - // ------------------------------------------------------------------------- - - private static ObjectMapper objectMapper; - // ------------------------------------------------------------------------- // Object attributes // ------------------------------------------------------------------------- @@ -89,12 +81,4 @@ public B withVersion(int version) { abstract public T build(); } - // ------------------------------------------------------------------------- - // Private methods - // ------------------------------------------------------------------------- - - protected static ObjectMapper getObjectMapper() { - return Arc.container().instance(ObjectMapper.class).get(); - } - } diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java index 2b04cd2ab37bb..279f4128c1af4 100644 --- a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/ModelWithBuilder.java @@ -17,12 +17,6 @@ @JsonDeserialize(builder = ModelWithBuilder.Builder.class) public class ModelWithBuilder { - // ------------------------------------------------------------------------- - // Class attributes - // ------------------------------------------------------------------------- - - private static ObjectMapper objectMapper; - // ------------------------------------------------------------------------- // Object attributes // ------------------------------------------------------------------------- @@ -46,12 +40,11 @@ private ModelWithBuilder(final Builder builder) { // ------------------------------------------------------------------------- public String toJson() throws IOException { - String json = getObjectMapper().writeValueAsString(this); - return json; + return toJson(getObjectMapper()); } - public static String toJson(final ModelWithBuilder model) throws IOException { - return model.toJson(); + public String toJson(ObjectMapper objectMapper) throws IOException { + return objectMapper.writeValueAsString(this); } public static ModelWithBuilder fromJson(final String json) throws IOException { diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java index 7f1a0322d41e6..4e92989ee387d 100644 --- a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/RegisteredPojoModel.java @@ -13,12 +13,6 @@ @RegisterForReflection public class RegisteredPojoModel { - // ------------------------------------------------------------------------- - // Class attributes - // ------------------------------------------------------------------------- - - private static ObjectMapper objectMapper; - // ------------------------------------------------------------------------- // Object attributes // ------------------------------------------------------------------------- @@ -39,12 +33,11 @@ public RegisteredPojoModel() { // ------------------------------------------------------------------------- public String toJson() throws IOException { - String json = getObjectMapper().writeValueAsString(this); - return json; + return toJson(getObjectMapper()); } - public static String toJson(final RegisteredPojoModel model) throws IOException { - return model.toJson(); + public String toJson(ObjectMapper objectMapper) throws IOException { + return objectMapper.writeValueAsString(this); } public static RegisteredPojoModel fromJson(final String json) throws IOException { diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java index 62ff0aea412e9..49a498d77ea34 100644 --- a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/InheritedModelWithBuilderResourceTest.java @@ -1,5 +1,6 @@ package io.quarkus.it.jackson; +import static io.quarkus.it.jackson.TestUtil.getObjectMapperForTest; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -22,7 +23,7 @@ public void testModelWithBuilder() throws IOException { given() .contentType("application/json") - .body(model.toJson()) + .body(model.toJson(getObjectMapperForTest())) .when().post("/inheritedmodelwithbuilder") .then() .statusCode(201) diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java index 5e50f6690e10e..6af00631f7763 100644 --- a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/ModelWithBuilderResourceTest.java @@ -1,5 +1,6 @@ package io.quarkus.it.jackson; +import static io.quarkus.it.jackson.TestUtil.getObjectMapperForTest; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -22,7 +23,7 @@ public void testModelWithBuilder() throws IOException { given() .contentType("application/json") - .body(model.toJson()) + .body(model.toJson(getObjectMapperForTest())) .when().post("/modelwithbuilder") .then() .statusCode(201) diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java index 525e589c47a5e..d8bd9cf79a01f 100644 --- a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/RegisteredPojoModelResourceTest.java @@ -1,5 +1,6 @@ package io.quarkus.it.jackson; +import static io.quarkus.it.jackson.TestUtil.getObjectMapperForTest; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; @@ -22,7 +23,7 @@ public void testSimplePojoModel() throws IOException { given() .contentType("application/json") - .body(model.toJson()) + .body(model.toJson(getObjectMapperForTest())) .when().post("/registeredpojomodel") .then() .statusCode(201) diff --git a/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/TestUtil.java b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/TestUtil.java new file mode 100644 index 0000000000000..ac37207e796fc --- /dev/null +++ b/integration-tests/jackson/src/test/java/io/quarkus/it/jackson/TestUtil.java @@ -0,0 +1,26 @@ +package io.quarkus.it.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + +final class TestUtil { + + private static volatile ObjectMapper objectMapper; + + private TestUtil() { + } + + // we need this because the ObjectMapper can't be retrieved from Arc in native tests + public static ObjectMapper getObjectMapperForTest() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new ParameterNamesModule()) + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()); + new MyObjectMapperCustomizer().customize(objectMapper); + } + return objectMapper; + } +} From 421f53d1dee904ea87d86ad3b635c38b5e9c61c8 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sat, 7 Dec 2019 03:08:21 +0100 Subject: [PATCH 254/602] Fix Jackson integration test imports --- .../quarkus/it/jackson/model/InheritedModelWithBuilderBase.java | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java index bc649a9f4d7f4..0f81e6aa42bfc 100644 --- a/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java +++ b/integration-tests/jackson/src/main/java/io/quarkus/it/jackson/model/InheritedModelWithBuilderBase.java @@ -3,7 +3,6 @@ import java.util.Optional; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; /** * Model class with inheritance and builder. From 115683c683d8bcb36e4d3c77cbbce9b1315faa18 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Fri, 6 Dec 2019 10:13:44 -0300 Subject: [PATCH 255/602] Running gradle build no longer triggers a native build --- .../gradle/QuarkusPluginFunctionalTest.java | 6 ++++-- .../java/io/quarkus/gradle/QuarkusPlugin.java | 2 -- docs/src/main/asciidoc/gradle-tooling.adoc | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index b7511c1ef5c99..8042cb4dbd070 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -41,11 +41,13 @@ public void canBuild(@TempDir File projectRoot) throws IOException { BuildResult build = GradleRunner.create() .forwardOutput() .withPluginClasspath() - .withArguments(arguments("quarkusBuild")) + .withArguments(arguments("build")) .withProjectDir(projectRoot) .build(); - assertThat(build.task(":quarkusBuild").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(build.task(":build").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + // gradle build should not build the native image + assertThat(build.task(":buildNative")).isNull(); } private List arguments(String argument) { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 0d871719b055c..938af2eb4b916 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -8,7 +8,6 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.plugins.BasePlugin; -import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; @@ -101,7 +100,6 @@ private void registerTasks(Project project) { Task testNative = tasks.create(TEST_NATIVE_TASK_NAME, QuarkusTestNative.class); testNative.dependsOn(buildNative); testNative.setShouldRunAfter(Collections.singletonList(tasks.findByName(JavaPlugin.TEST_TASK_NAME))); - tasks.getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(testNative); tasks.withType(Test.class).forEach(t -> { // Quarkus test configuration task which should be executed before any Quarkus test t.dependsOn(quarkusTestConfig); diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index cd49704f7b322..6260522f28103 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -309,6 +309,18 @@ The native executable would then be produced by executing: ./gradlew buildNative ---- + +== Running native tests + +Run the native tests using: + +[source,shell] +---- +./gradlew testNative +---- + +This task depends on `buildNative`, so it will generate the native image before running the tests. + == Building Uber-Jars Quarkus Gradle plugin supports the generation of Uber-Jars by specifying an `--uber-jar` argument as follows: @@ -329,8 +341,4 @@ The entries are relative to the root of the generated Uber-Jar. You can specify == Building with `./gradlew build` -By default, `./gradlew build` will build the native image also. To skip it, use -``` -./gradlew build -x buildNative -x testNative -``` - +Starting from 1.1.0.Final, `./gradlew build` will no longer build the native image. Use the `buildNative` task explicitly as explained above if needed. From 667003080dd392f2ac9f6b89234841f038b1c902 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sat, 7 Dec 2019 02:02:33 +0100 Subject: [PATCH 256/602] Remove Override annotation from generated bytecode --- .../configuration/RunTimeConfigurationGenerator.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index 7ee50c6ef9901..babf8be64c67c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -578,10 +578,9 @@ public void run() { // generate run time default values config source class try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(RTDVCS_CLASS_NAME) .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { final ResultHandle keyIter = mc.getMethodParam(0); - // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) - mc.addAnnotation(Override.class); final MethodDescriptor md = generateDefaultValueParse(dvcc, runTimePatternMap, new StringBuilder("getDefaultFor")); if (md != null) { @@ -606,10 +605,9 @@ public void run() { // generate build time run time visible default values config source class try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(BTRTDVCS_CLASS_NAME) .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { final ResultHandle keyIter = mc.getMethodParam(0); - // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) - mc.addAnnotation(Override.class); final MethodDescriptor md = generateDefaultValueParse(dvcc, buildTimeRunTimePatternMap, new StringBuilder("getDefaultFor")); if (md != null) { From bd316ca0fa3889e6f9847cd51b0c243208c2acbc Mon Sep 17 00:00:00 2001 From: Logan HAUSPIE Date: Sun, 10 Nov 2019 23:15:59 +0100 Subject: [PATCH 257/602] Add readiness health check for Kafka Streams Fixes #4793 --- docs/src/main/asciidoc/kafka-streams.adoc | 101 ++++++++++++++++++ extensions/kafka-streams/deployment/pom.xml | 4 + .../KafkaStreamsBuildTimeConfig.java | 15 +++ .../deployment/KafkaStreamsProcessor.java | 13 +++ extensions/kafka-streams/runtime/pom.xml | 5 + .../runtime/KafkaStreamsTopologyManager.java | 59 +++++++--- .../health/KafkaStreamsStateHealthCheck.java | 33 ++++++ .../health/KafkaStreamsTopicsHealthCheck.java | 67 ++++++++++++ integration-tests/kafka/pom.xml | 4 + .../it/kafka/streams/KafkaStreamsTest.java | 46 +++++++- 10 files changed, 328 insertions(+), 19 deletions(-) create mode 100644 extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsBuildTimeConfig.java create mode 100644 extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsStateHealthCheck.java create mode 100644 extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsTopicsHealthCheck.java diff --git a/docs/src/main/asciidoc/kafka-streams.adoc b/docs/src/main/asciidoc/kafka-streams.adoc index 0bfca2d6d52c1..05bb966698d61 100644 --- a/docs/src/main/asciidoc/kafka-streams.adoc +++ b/docs/src/main/asciidoc/kafka-streams.adoc @@ -1112,6 +1112,107 @@ CMD ["./application", "-Dquarkus.http.host=0.0.0.0", "-Xmx32m"] Now start Docker Compose as described above (don't forget to rebuild the container images). +== Kafka Streams Health Checks + +If you are using the `quarkus-smallrye-health` extension, `quarkus-kafka-streams` will automatically add: + +* a readiness health check to validate that all topics declared in the `quarkus.kafka-streams.topics` property are created, +* a liveness health check based on the Kafka Streams state. + +So when you access the `/health` endpoint of your application you will have information about the state of the Kafka Streams and the available and/or missing topics. + +This is an example of when the status is `DOWN`: +[source, subs=attributes+] +---- +curl -i http://aggregator:8080/health + +HTTP/1.1 503 Service Unavailable +content-type: application/json; charset=UTF-8 +content-length: 454 + +{ + "status": "DOWN", + "checks": [ + { + "name": "Kafka Streams state health check", <1> + "status": "DOWN", + "data": { + "state": "CREATED" + } + }, + { + "name": "Kafka Streams topics health check", <2> + "status": "DOWN", + "data": { + "available_topics": "weather-stations,temperature-values", + "missing_topics": "hygrometry-values" + } + } + ] +} +---- +<1> Liveness health check. Also available at `/health/live` endpoint. +<2> Readiness health check. Also available at `/health/ready` endpoint. + +So as you can see, the status is `DOWN` as soon as one of the `quarkus.kafka-streams.topics` is missing or the Kafka Streams `state` is not `RUNNING`. + +If no topics are available, the `available_topics` key will not be present in the `data` field of the `Kafka Streams topics health check`. +As well as if no topics are missing, the `missing_topics` key will not be present in the `data` field of the `Kafka Streams topics health check`. + +You can of course disable the health check of the `quarkus-kafka-streams` extension by setting the `quarkus.kafka-streams.health.enabled` property to `false` in your `application.properties`. + +Obviously you can create your liveness and readiness probes based on the respective endpoints `/health/live` and `/health/ready`. + +=== Liveness health check + +Here is an example of the liveness check: +``` +curl -i http://aggregator:8080/health/live + +HTTP/1.1 503 Service Unavailable +content-type: application/json; charset=UTF-8 +content-length: 225 + +{ + "status": "DOWN", + "checks": [ + { + "name": "Kafka Streams state health check", + "status": "DOWN", + "data": { + "state": "CREATED" + } + } + ] +} +``` +The `state` is coming from the `KafkaStreams.State` enum. + +=== Readiness health check + +Here is an example of the readiness check: +``` +curl -i http://aggregator:8080/health/ready + +HTTP/1.1 503 Service Unavailable +content-type: application/json; charset=UTF-8 +content-length: 265 + +{ + "status": "DOWN", + "checks": [ + { + "name": "Kafka Streams topics health check", + "status": "DOWN", + "data": { + "missing_topics": "weather-stations,temperature-values" + } + } + ] +} + +``` + == Going Further This guide has shown how you can build stream processing applications using Quarkus and the Kafka Streams APIs, diff --git a/extensions/kafka-streams/deployment/pom.xml b/extensions/kafka-streams/deployment/pom.xml index 45c7e1e03d8a9..b59e7fb29cdc4 100644 --- a/extensions/kafka-streams/deployment/pom.xml +++ b/extensions/kafka-streams/deployment/pom.xml @@ -30,6 +30,10 @@ io.quarkus quarkus-kafka-streams + + io.quarkus + quarkus-smallrye-health-spi + diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsBuildTimeConfig.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsBuildTimeConfig.java new file mode 100644 index 0000000000000..f3aeaa8fc76d7 --- /dev/null +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsBuildTimeConfig.java @@ -0,0 +1,15 @@ +package io.quarkus.kafka.streams.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "kafka-streams", phase = ConfigPhase.BUILD_TIME) +public class KafkaStreamsBuildTimeConfig { + + /** + * Whether or not a health check is published in case the smallrye-health extension is present (defaults to true). + */ + @ConfigItem(name = "health.enabled", defaultValue = "true") + public boolean healthEnabled; +} diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java index dfa9a6085f470..6a7c2f204d35b 100644 --- a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java @@ -33,6 +33,7 @@ import io.quarkus.kafka.streams.runtime.KafkaStreamsRuntimeConfig; import io.quarkus.kafka.streams.runtime.KafkaStreamsTopologyManager; import io.quarkus.runtime.LaunchMode; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; class KafkaStreamsProcessor { @@ -196,4 +197,16 @@ void configureAndLoadRocksDb(KafkaStreamsRecorder recorder, KafkaStreamsRuntimeC AdditionalBeanBuildItem registerBean() { return AdditionalBeanBuildItem.unremovableOf(KafkaStreamsTopologyManager.class); } + + @BuildStep + void addHealthChecks(KafkaStreamsBuildTimeConfig buildTimeConfig, BuildProducer healthChecks) { + healthChecks.produce( + new HealthBuildItem( + "io.quarkus.kafka.streams.runtime.health.KafkaStreamsTopicsHealthCheck", + buildTimeConfig.healthEnabled, "kafka-streams")); + healthChecks.produce( + new HealthBuildItem( + "io.quarkus.kafka.streams.runtime.health.KafkaStreamsStateHealthCheck", + buildTimeConfig.healthEnabled, "kafka-streams")); + } } diff --git a/extensions/kafka-streams/runtime/pom.xml b/extensions/kafka-streams/runtime/pom.xml index 798b2c3e44040..04836450a7c8d 100644 --- a/extensions/kafka-streams/runtime/pom.xml +++ b/extensions/kafka-streams/runtime/pom.xml @@ -34,6 +34,11 @@ org.graalvm.nativeimage svm + + io.quarkus + quarkus-smallrye-health + true + diff --git a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsTopologyManager.java b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsTopologyManager.java index b1ae0f5af5ea6..ba32e5defae3b 100644 --- a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsTopologyManager.java +++ b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/KafkaStreamsTopologyManager.java @@ -2,6 +2,7 @@ import java.net.InetSocketAddress; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -50,6 +51,7 @@ public class KafkaStreamsTopologyManager { private KafkaStreamsRuntimeConfig runtimeConfig; private Instance topology; private Properties properties; + private Map adminClientConfig; KafkaStreamsTopologyManager() { executor = null; @@ -105,10 +107,11 @@ void onStart(@Observes StartupEvent ev) { Properties streamsProperties = getStreamsProperties(properties, bootstrapServersConfig, runtimeConfig); streams = new KafkaStreams(topology.get(), streamsProperties); + adminClientConfig = getAdminClientConfig(bootstrapServersConfig); executor.execute(() -> { try { - waitForTopicsToBeCreated(runtimeConfig.getTrimmedTopics(), bootstrapServersConfig); + waitForTopicsToBeCreated(runtimeConfig.getTrimmedTopics()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; @@ -131,21 +134,9 @@ public KafkaStreams getStreams() { return streams; } - private void waitForTopicsToBeCreated(Collection topicsToAwait, String bootstrapServersConfig) + private void waitForTopicsToBeCreated(Collection topicsToAwait) throws InterruptedException { - final Map config = new HashMap<>(); - config.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServersConfig); - // include other AdminClientConfig(s) that have been configured - for (final String knownAdminClientConfig : AdminClientConfig.configNames()) { - // give preference to admin. first - if (properties.containsKey(StreamsConfig.ADMIN_CLIENT_PREFIX + knownAdminClientConfig)) { - config.put(knownAdminClientConfig, properties.get(StreamsConfig.ADMIN_CLIENT_PREFIX + knownAdminClientConfig)); - } else if (properties.containsKey(knownAdminClientConfig)) { - config.put(knownAdminClientConfig, properties.get(knownAdminClientConfig)); - } - } - - try (AdminClient adminClient = AdminClient.create(config)) { + try (AdminClient adminClient = AdminClient.create(adminClientConfig)) { while (true) { try { ListTopicsResult topics = adminClient.listTopics(); @@ -155,7 +146,7 @@ private void waitForTopicsToBeCreated(Collection topicsToAwait, String b LOGGER.debug("All expected topics created"); return; } else { - HashSet missing = new HashSet<>(topicsToAwait); + Set missing = new HashSet<>(topicsToAwait); missing.removeAll(topicNames); LOGGER.debug("Waiting for topic(s) to be created: " + missing); } @@ -168,6 +159,42 @@ private void waitForTopicsToBeCreated(Collection topicsToAwait, String b } } + public Set getMissingTopics(Collection topicsToCheck) + throws InterruptedException { + HashSet missing = new HashSet<>(topicsToCheck); + + try (AdminClient adminClient = AdminClient.create(adminClientConfig)) { + ListTopicsResult topics = adminClient.listTopics(); + Set topicNames = topics.names().get(10, TimeUnit.SECONDS); + + if (topicNames.containsAll(topicsToCheck)) { + return Collections.EMPTY_SET; + } else { + missing.removeAll(topicNames); + } + } catch (ExecutionException | TimeoutException e) { + LOGGER.error("Failed to get topic names from broker", e); + } + + return missing; + } + + private Map getAdminClientConfig(String bootstrapServersConfig) { + Map adminClientConfig = new HashMap<>(); + adminClientConfig.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServersConfig); + // include other AdminClientConfig(s) that have been configured + for (final String knownAdminClientConfig : AdminClientConfig.configNames()) { + // give preference to admin. first + if (properties.containsKey(StreamsConfig.ADMIN_CLIENT_PREFIX + knownAdminClientConfig)) { + adminClientConfig.put(knownAdminClientConfig, + properties.get(StreamsConfig.ADMIN_CLIENT_PREFIX + knownAdminClientConfig)); + } else if (properties.containsKey(knownAdminClientConfig)) { + adminClientConfig.put(knownAdminClientConfig, properties.get(knownAdminClientConfig)); + } + } + return adminClientConfig; + } + public void setRuntimeConfig(KafkaStreamsRuntimeConfig runtimeConfig) { this.runtimeConfig = runtimeConfig; } diff --git a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsStateHealthCheck.java b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsStateHealthCheck.java new file mode 100644 index 0000000000000..de5d8cdc434a9 --- /dev/null +++ b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsStateHealthCheck.java @@ -0,0 +1,33 @@ +package io.quarkus.kafka.streams.runtime.health; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.kafka.streams.KafkaStreams; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Liveness; + +import io.quarkus.kafka.streams.runtime.KafkaStreamsTopologyManager; + +@Liveness +@ApplicationScoped +public class KafkaStreamsStateHealthCheck implements HealthCheck { + + @Inject + private KafkaStreamsTopologyManager manager; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("Kafka Streams state health check"); + try { + KafkaStreams.State state = manager.getStreams().state(); + responseBuilder.state(state.isRunning()) + .withData("state", state.name()); + } catch (Exception e) { + responseBuilder.down().withData("technical_error", e.getMessage()); + } + return responseBuilder.build(); + } +} diff --git a/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsTopicsHealthCheck.java b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsTopicsHealthCheck.java new file mode 100644 index 0000000000000..c6eb07be0a10a --- /dev/null +++ b/extensions/kafka-streams/runtime/src/main/java/io/quarkus/kafka/streams/runtime/health/KafkaStreamsTopicsHealthCheck.java @@ -0,0 +1,67 @@ +package io.quarkus.kafka.streams.runtime.health; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; +import org.jboss.logging.Logger; + +import io.quarkus.kafka.streams.runtime.KafkaStreamsTopologyManager; + +@Readiness +@ApplicationScoped +public class KafkaStreamsTopicsHealthCheck implements HealthCheck { + + private static final Logger LOGGER = Logger.getLogger(KafkaStreamsTopicsHealthCheck.class.getName()); + + @ConfigProperty(name = "quarkus.kafka-streams.topics") + public List topics; + + // @ConfigProperty(name = "quarkus.kafka-streams.bootstrap-servers") + // public List bootstrapServers; + + @Inject + private KafkaStreamsTopologyManager manager; + + // private String commaSeparatedBootstrapServersConfig; + private List trimmedTopics; + + @PostConstruct + public void init() { + // commaSeparatedBootstrapServersConfig = String.join(",", bootstrapServers); + trimmedTopics = topics.stream().map(String::trim).collect(Collectors.toList()); + } + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Kafka Streams topics health check").up(); + + try { + Set missingTopics = manager.getMissingTopics(trimmedTopics); + List availableTopics = new ArrayList<>(trimmedTopics); + availableTopics.removeAll(missingTopics); + + if (!availableTopics.isEmpty()) { + builder.withData("available_topics", String.join(",", availableTopics)); + } + if (!missingTopics.isEmpty()) { + builder.down().withData("missing_topics", String.join(",", missingTopics)); + } + } catch (InterruptedException e) { + LOGGER.error("error when retrieving missing topics", e); + builder.down().withData("technical_error", e.getMessage()); + } + + return builder.build(); + } +} diff --git a/integration-tests/kafka/pom.xml b/integration-tests/kafka/pom.xml index 73b2db11473d4..629f78a0d5bb7 100644 --- a/integration-tests/kafka/pom.xml +++ b/integration-tests/kafka/pom.xml @@ -75,6 +75,10 @@ kafka_2.12 test + + io.quarkus + quarkus-smallrye-health + diff --git a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java b/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java index 8cc7ac99890bb..1598b816decfc 100644 --- a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java +++ b/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java @@ -8,6 +8,7 @@ import javax.inject.Inject; +import org.apache.http.HttpStatus; import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; @@ -20,6 +21,7 @@ import org.apache.kafka.common.serialization.IntegerDeserializer; import org.apache.kafka.common.serialization.IntegerSerializer; import org.eclipse.microprofile.config.Config; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -37,14 +39,14 @@ public class KafkaStreamsTest { @Inject public Config config; - private static Producer createProducer() { + private static Producer createCustomerProducer() { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:19092"); props.put(ProducerConfig.CLIENT_ID_CONFIG, "streams-test-producer"); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class.getName()); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ObjectMapperSerializer.class.getName()); - return new KafkaProducer(props); + return new KafkaProducer<>(props); } private static Producer createCategoryProducer() { @@ -73,6 +75,8 @@ private static KafkaConsumer createConsumer() { @Test public void testKafkaStreams() throws Exception { + testKafkaStreamsNotAliveAndNotReady(); + produceCustomers(); Consumer consumer = createConsumer(); @@ -114,13 +118,49 @@ record = records.get(3); assertCategoryCount(1, 3); assertCategoryCount(2, 1); + testKafkaStreamsAliveAndReady(); + // explicitly stopping the pipeline *before* the broker is shut down, as it // otherwise will time out RestAssured.post("/kafkastreams/stop"); } + public void testKafkaStreamsNotAliveAndNotReady() throws Exception { + RestAssured.get("/health/ready").then() + .statusCode(HttpStatus.SC_SERVICE_UNAVAILABLE) + .body("checks[0].name", CoreMatchers.is("Kafka Streams topics health check")) + .body("checks[0].status", CoreMatchers.is("DOWN")) + .body("checks[0].data.missing_topics", CoreMatchers.is("streams-test-customers,streams-test-categories")); + + RestAssured.when().get("/health/live").then() + .statusCode(HttpStatus.SC_SERVICE_UNAVAILABLE) + .body("checks[0].name", CoreMatchers.is("Kafka Streams state health check")) + .body("checks[0].status", CoreMatchers.is("DOWN")) + .body("checks[0].data.state", CoreMatchers.is("CREATED")); + + RestAssured.when().get("/health").then() + .statusCode(HttpStatus.SC_SERVICE_UNAVAILABLE); + } + + public void testKafkaStreamsAliveAndReady() throws Exception { + RestAssured.get("/health/ready").then() + .statusCode(HttpStatus.SC_OK) + .body("checks[0].name", CoreMatchers.is("Kafka Streams topics health check")) + .body("checks[0].status", CoreMatchers.is("UP")) + .body("checks[0].data.available_topics", CoreMatchers.is("streams-test-categories,streams-test-customers")); + + RestAssured.when().get("/health/live").then() + .statusCode(HttpStatus.SC_OK) + .body("checks[0].name", CoreMatchers.is("Kafka Streams state health check")) + .body("checks[0].status", CoreMatchers.is("UP")) + .body("checks[0].data.state", CoreMatchers.is("RUNNING")); + + RestAssured.when().get("/health").then() + .statusCode(HttpStatus.SC_OK); + } + private void produceCustomers() { - Producer producer = createProducer(); + Producer producer = createCustomerProducer(); Producer categoryProducer = createCategoryProducer(); From 200071d366241fc8d70be3e8780108537b108d1b Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 14 Nov 2019 15:23:40 +0100 Subject: [PATCH 258/602] Make sure system properties dominate the properties defined in pom.xml when bootstrap LocalWorkspace is used as a WorkspaceReader --- .../maven/BootstrapModelBuilderFactory.java | 42 +++++++++++ .../resolver/maven/MavenModelBuilder.java | 15 +--- .../maven/workspace/LocalProject.java | 22 +----- .../resolver/maven/workspace/ModelUtils.java | 69 +++++++++++++------ .../resolver/CollectDependenciesBase.java | 18 ++++- .../resolver/ResolverSetupCleanup.java | 6 +- .../bootstrap/resolver/TsArtifact.java | 14 ++++ ...verridesPomPropertyDependencyTestCase.java | 54 +++++++++++++++ .../VersionPropertyDependencyTestCase.java | 25 +++++++ 9 files changed, 207 insertions(+), 58 deletions(-) create mode 100644 independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelBuilderFactory.java create mode 100644 independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/SystemPropertyOverridesPomPropertyDependencyTestCase.java create mode 100644 independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/VersionPropertyDependencyTestCase.java diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelBuilderFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelBuilderFactory.java new file mode 100644 index 0000000000000..58f9116325052 --- /dev/null +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelBuilderFactory.java @@ -0,0 +1,42 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.io.File; +import org.apache.maven.model.Model; +import org.apache.maven.model.building.DefaultModelBuilderFactory; +import org.apache.maven.model.building.ModelBuildingRequest; +import org.apache.maven.model.building.ModelProblemCollector; +import org.apache.maven.model.interpolation.ModelInterpolator; +import org.apache.maven.model.validation.ModelValidator; + +import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; + +class BootstrapModelBuilderFactory extends DefaultModelBuilderFactory { + + @Override + protected ModelValidator newModelValidator() { + return new ModelValidator() { + @Override + public void validateRawModel(Model model, ModelBuildingRequest request, ModelProblemCollector problems) { + } + + @Override + public void validateEffectiveModel(Model model, ModelBuildingRequest request, ModelProblemCollector problems) { + }}; + } + + @Override + protected ModelInterpolator newModelInterpolator() { + final ModelInterpolator defaultInterpolator = super.newModelInterpolator(); + return new ModelInterpolator() { + @Override + public Model interpolateModel(Model model, File projectDir, ModelBuildingRequest request, + ModelProblemCollector problems) { + if(projectDir != null && !model.getProperties().isEmpty()) { + // this should be a project from the current workspace + model = ModelUtils.applySystemProperties(model); + } + return defaultInterpolator.interpolateModel(model, projectDir, request, problems); + } + }; + } +} \ No newline at end of file diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenModelBuilder.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenModelBuilder.java index 6c726bd2b457b..fa4c7aa2e8e5c 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenModelBuilder.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenModelBuilder.java @@ -3,15 +3,12 @@ import java.io.File; import org.apache.maven.model.Model; -import org.apache.maven.model.building.DefaultModelBuilderFactory; import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.model.building.ModelBuildingException; import org.apache.maven.model.building.ModelBuildingRequest; import org.apache.maven.model.building.ModelBuildingResult; -import org.apache.maven.model.building.ModelProblemCollector; import org.apache.maven.model.building.Result; import org.apache.maven.model.resolution.WorkspaceModelResolver; -import org.apache.maven.model.validation.ModelValidator; /** * @@ -23,17 +20,7 @@ public class MavenModelBuilder implements ModelBuilder { private final WorkspaceModelResolver modelResolver; public MavenModelBuilder(WorkspaceModelResolver wsModelResolver) { - builder = new DefaultModelBuilderFactory().newInstance() - .setModelValidator(new ModelValidator() { - @Override - public void validateRawModel(Model model, ModelBuildingRequest request, ModelProblemCollector problems) { - } - - @Override - public void validateEffectiveModel(Model model, ModelBuildingRequest request, ModelProblemCollector problems) { - } - }); - + builder = new BootstrapModelBuilderFactory().newInstance(); modelResolver = wsModelResolver; } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java index 121fa51c5fa52..8e001d8607916 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java @@ -127,27 +127,9 @@ private LocalProject(Model rawModel, LocalWorkspace workspace) throws BootstrapE this.rawModel = rawModel; this.dir = rawModel.getProjectDirectory().toPath(); this.workspace = workspace; - final Parent parent = rawModel.getParent(); - String groupId = rawModel.getGroupId(); - if(groupId == null) { - if(parent == null) { - throw new BootstrapException("Failed to determine groupId for " + rawModel.getPomFile()); - } - this.groupId = parent.getGroupId(); - } else { - this.groupId = groupId; - } - + this.groupId = ModelUtils.getGroupId(rawModel); this.artifactId = rawModel.getArtifactId(); - String version = rawModel.getVersion(); - if(version == null) { - if(parent == null) { - throw new BootstrapException("Failed to determine version for " + rawModel.getPomFile()); - } - this.version = parent.getVersion(); - } else { - this.version = version; - } + this.version = ModelUtils.getVersion(rawModel); if(workspace != null) { workspace.addProject(this, rawModel.getPomFile().lastModified()); } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java index 13ae2d374c454..e445f183da6fc 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java @@ -17,6 +17,7 @@ import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.model.io.xpp3.MavenXpp3Writer; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; @@ -24,6 +25,7 @@ import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.util.PropertyUtils; /** @@ -139,27 +141,7 @@ static Model readAppModel(Path appJar) throws IOException { } final Path pomXml = artifactIdPath.resolve("pom.xml"); if (Files.exists(pomXml)) { - final Model model = readModel(pomXml); - Properties props = null; - if(model.getGroupId() == null) { - props = loadPomProps(appJar, artifactIdPath); - final String groupId = props.getProperty("groupId"); - if(groupId == null) { - throw new IOException("Failed to determine groupId for " + appJar); - } - model.setGroupId(groupId); - } - if(model.getVersion() == null) { - if(props == null) { - props = loadPomProps(appJar, artifactIdPath); - } - final String version = props.getProperty("version"); - if(version == null) { - throw new IOException("Failed to determine the artifact version for " + appJar); - } - model.setVersion(version); - } - return model; + return readModel(pomXml); } } } @@ -170,6 +152,51 @@ static Model readAppModel(Path appJar) throws IOException { } } + public static String getGroupId(Model model) { + String groupId = model.getGroupId(); + if(groupId != null) { + return groupId; + } + final Parent parent = model.getParent(); + if (parent != null) { + groupId = parent.getGroupId(); + if(groupId != null) { + return groupId; + } + } + throw new IllegalStateException("Failed to determine groupId for project model"); + } + + public static String getVersion(Model model) { + String version = model.getVersion(); + if(version != null) { + return version; + } + final Parent parent = model.getParent(); + if (parent != null) { + version = parent.getVersion(); + if(version != null) { + return version; + } + } + throw new IllegalStateException("Failed to determine version for project model"); + } + + /** + * If the model contains properties, this method overrides those that appear to be + * defined as system properties. + */ + public static Model applySystemProperties(Model model) { + final Properties props = model.getProperties(); + for(Map.Entry prop : model.getProperties().entrySet()) { + final String systemValue = PropertyUtils.getProperty(prop.getKey().toString()); + if(systemValue != null) { + props.put(prop.getKey(), systemValue); + } + } + return model; + } + private static Properties loadPomProps(Path appJar, Path artifactIdPath) throws IOException { final Path propsPath = artifactIdPath.resolve("pom.properties"); if(!Files.exists(propsPath)) { diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java index 3a6258bc50bc2..2116ee9385a16 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java @@ -43,10 +43,18 @@ public void testCollectedDependencies() throws Exception { expected.addAll(expectedResult); expected.addAll(deploymentDeps); } - final List resolvedDeps = resolver.resolveModel(root.toAppArtifact()).getAllDependencies(); + final List resolvedDeps = getTestResolver().resolveModel(root.toAppArtifact()).getAllDependencies(); assertEquals(expected, resolvedDeps); } + protected BootstrapAppModelResolver getTestResolver() throws Exception { + return resolver; + } + + protected Path getInstallDir(TsArtifact artifact) { + return repoHome.resolve(artifact.getGroupId().replace('.', '/')).resolve(artifact.getArtifactId()).resolve(artifact.getVersion()); + } + protected TsArtifact install(TsArtifact dep, boolean collected) { return install(dep, collected ? "compile" : null); } @@ -141,4 +149,12 @@ protected void addCollectedDeploymentDep(TsArtifact ext) { protected void addManagedDep(TsArtifact dep) { root.addManagedDependency(new TsDependency(dep)); } + + protected void addDep(TsArtifact dep) { + root.addDependency(dep); + } + + protected void setPomProperty(String name, String value) { + root.setPomProperty(name, value); + } } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java index f3f03040a05d1..adb2640cadb9e 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java @@ -5,6 +5,7 @@ import java.util.UUID; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; import io.quarkus.bootstrap.util.IoUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -24,7 +25,7 @@ public class ResolverSetupCleanup { public void setup() throws Exception { workDir = initWorkDir(); repoHome = IoUtils.mkdirs(workDir.resolve("repo")); - resolver = initResolver(); + resolver = initResolver(null); repo = TsRepoBuilder.getInstance(resolver, workDir); } @@ -43,10 +44,11 @@ protected boolean cleanWorkDir() { return true; } - protected BootstrapAppModelResolver initResolver() throws AppModelResolverException { + protected BootstrapAppModelResolver initResolver(LocalWorkspace workspace) throws AppModelResolverException { return new BootstrapAppModelResolver(MavenArtifactResolver.builder() .setRepoHome(repoHome) .setOffline(true) + .setWorkspace(workspace) .build()); } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java index fb1126b1301b9..d6a8de65afbb9 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Properties; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; @@ -60,6 +61,8 @@ public interface ContentProvider { protected ContentProvider content; + protected Properties pomProps; + public String getGroupId() { return groupId; } @@ -165,6 +168,10 @@ public Model getPomModel() { model.setPackaging(type); model.setVersion(version); + if(pomProps != null) { + model.setProperties(pomProps); + } + if(!deps.isEmpty()) { for (TsDependency dep : deps) { model.addDependency(dep.toPomDependency()); @@ -208,6 +215,13 @@ public void install(TsRepoBuilder repoBuilder) { } } + public void setPomProperty(String name, String value) { + if(pomProps == null) { + pomProps = new Properties(); + } + pomProps.setProperty(name, value); + } + @Override public String toString() { final StringBuilder buf = new StringBuilder(128); diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/SystemPropertyOverridesPomPropertyDependencyTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/SystemPropertyOverridesPomPropertyDependencyTestCase.java new file mode 100644 index 0000000000000..0e38e86ece79a --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/SystemPropertyOverridesPomPropertyDependencyTestCase.java @@ -0,0 +1,54 @@ +package io.quarkus.bootstrap.resolver.test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; +import io.quarkus.bootstrap.util.IoUtils; + +/** + * + */ +public class SystemPropertyOverridesPomPropertyDependencyTestCase extends CollectDependenciesBase { + + @Override + protected void setupDependencies() { + + final TsArtifact x12 = new TsArtifact("x", "2"); + final TsArtifact x13 = new TsArtifact("x", "3"); + + install(x12); + install(x13); + + // x.version in pom is 2 + setPomProperty("x.version", "2"); + addDep(new TsArtifact("x", "${x.version}")); + + // the system property of x.version is 3 + System.setProperty("x.version", "3"); + + // it is expected that the system property will dominate + addCollectedDep(x13); + } + + @Override + protected BootstrapAppModelResolver getTestResolver() throws Exception { + + // location of the root in the local maven repo + final Path installDir = getInstallDir(root); + + // here i'm faking a project from which the root could have been installed + final Path projectDir = workDir.resolve("project"); + Files.createDirectories(projectDir); + IoUtils.copy(installDir.resolve(root.toPomArtifact().getArtifactFileName()), projectDir.resolve("pom.xml")); + + // workspace reader for the root project + final LocalWorkspace workspace = LocalProject.loadWorkspace(projectDir).getWorkspace(); + + return initResolver(workspace); + } +} \ No newline at end of file diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/VersionPropertyDependencyTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/VersionPropertyDependencyTestCase.java new file mode 100644 index 0000000000000..494dd121952ae --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/VersionPropertyDependencyTestCase.java @@ -0,0 +1,25 @@ +package io.quarkus.bootstrap.resolver.test; + +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsArtifact; + +/** + * + */ +public class VersionPropertyDependencyTestCase extends CollectDependenciesBase { + + @Override + protected void setupDependencies() { + + final TsArtifact x12 = new TsArtifact("x", "2"); + final TsArtifact x13 = new TsArtifact("x", "3"); + + install(x12); + install(x13); + + setPomProperty("x.version", "2"); + addDep(new TsArtifact("x", "${x.version}")); + + addCollectedDep(x12); + } +} \ No newline at end of file From 92fa203627b28ac6822b71c988051a33820a53f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 6 Dec 2019 11:46:53 +0100 Subject: [PATCH 259/602] Provides default SSL context to allow https introspection endpoint --- .../quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java index 6e4101c005308..023f96b3f2b8d 100644 --- a/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java +++ b/extensions/elytron-security-oauth2/runtime/src/main/java/io/quarkus/elytron/security/oauth2/runtime/OAuth2Recorder.java @@ -39,6 +39,8 @@ public RuntimeValue createRealm(OAuth2Config config) if (config.caCertFile.isPresent()) { validatorBuilder.useSslContext(createSSLContext(config)); + } else { + validatorBuilder.useSslContext(SSLContext.getDefault()); } OAuth2IntrospectValidator validator = validatorBuilder.build(); From babdaa987c0d98958629f565b4d57a540a612a6e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 23:51:05 +0200 Subject: [PATCH 260/602] Try to fix doc issue with anonymous config root --- .../processor/generate_doc/ConfigDocItemScanner.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java index 3bbaf9f63a768..6eed212fa5388 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java @@ -90,6 +90,9 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { if (name.isEmpty()) { name = deriveConfigRootName(clazz.getSimpleName().toString(), configPhase); + } else if (name.endsWith(Constants.DOT + Constants.PARENT)) { + // take into account the root case which would contain characters that can't be used to create the final file + name = name.replace(Constants.DOT + Constants.PARENT, ""); } ConfigRootInfo configRootInfo = new ConfigRootInfo(name, clazz, extensionName, configPhase); From 08be1e4b3f78b0a98799d8f378c26743fbb441c9 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 6 Dec 2019 08:35:49 -0600 Subject: [PATCH 261/602] Allow anonymous config roots Enables #5980 --- .../BuildTimeConfigurationReader.java | 4 ++- .../RunTimeConfigurationGenerator.java | 17 +++++++++---- .../definition/RootDefinition.java | 2 +- .../matching/FieldContainer.java | 12 +++++++-- .../matching/PatternMapBuilder.java | 25 ++++++++++--------- .../src/main/resources/application.properties | 3 +++ .../runtime/config/TopLevelRootConfig.java | 12 +++++++++ .../src/main/resources/application.properties | 3 +++ 8 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TopLevelRootConfig.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java index 4c582eb46afa3..4f10c01ad7b4a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -267,7 +267,9 @@ ReadResult run() { } objectsByRootClass.put(clazz, instance); String rootName = root.getRootName(); - nameBuilder.append('.').append(rootName); + if (!rootName.isEmpty()) { + nameBuilder.append('.').append(rootName); + } readConfigGroup(root, instance, nameBuilder); nameBuilder.setLength(len); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index babf8be64c67c..76bbecb559bec 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -287,7 +287,7 @@ static final class GenerateOperation implements AutoCloseable { clinit.setModifiers(Opcodes.ACC_STATIC); clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); clinitNameBuilder = clinit.newInstance(SB_NEW); - clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load("quarkus.")); + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load("quarkus")); // create the map for build time config source final ResultHandle buildTimeValues = clinit.newInstance(HM_NEW); @@ -329,7 +329,7 @@ static final class GenerateOperation implements AutoCloseable { readConfig = cc.getMethodCreator(C_READ_CONFIG); // the readConfig name builder readConfigNameBuilder = readConfig.newInstance(SB_NEW); - readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus.")); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus")); runTimePatternMap = buildTimeReadResult.getRunTimePatternMap(); accessorFinder = new AccessorFinder(); } @@ -466,6 +466,7 @@ public void run() { .getConstructorFor(MethodDescriptor.ofConstructor(configurationClass)); // specific actions based on config phase + String rootName = root.getRootName(); if (root.getConfigPhase() == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { // config root field is final; we initialize it from clinit cc.getFieldCreator(rootFieldDescriptor) @@ -476,7 +477,10 @@ public void run() { clinit.writeStaticField(rootFieldDescriptor, instance); instanceCache.put(rootFieldDescriptor, instance); // eager init as appropriate - clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load(root.getRootName())); + if (!rootName.isEmpty()) { + clinit.invokeVirtualMethod(SB_APPEND_CHAR, clinitNameBuilder, clinit.load('.')); + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load(rootName)); + } clinit.invokeStaticMethod(initGroup, clinitConfig, clinitNameBuilder, instance); clinit.invokeVirtualMethod(SB_SET_LENGTH, clinitNameBuilder, clInitOldLen); } else if (root.getConfigPhase() == ConfigPhase.RUN_TIME) { @@ -487,8 +491,11 @@ public void run() { final ResultHandle instance = readConfig.invokeStaticMethod(ctor); // assign instance to field readConfig.writeStaticField(rootFieldDescriptor, instance); - readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, - readConfig.load(root.getRootName())); + if (!rootName.isEmpty()) { + readConfig.invokeVirtualMethod(SB_APPEND_CHAR, readConfigNameBuilder, readConfig.load('.')); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, + readConfig.load(rootName)); + } readConfig.invokeStaticMethod(initGroup, runTimeConfig, readConfigNameBuilder, instance); readConfig.invokeVirtualMethod(SB_SET_LENGTH, readConfigNameBuilder, rcOldLen); } else { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java index 636d28efbba31..3fc15bf991d7a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java @@ -52,7 +52,7 @@ public final class RootDefinition extends ClassDefinition { "Config"); } if (rootName.equals(ConfigItem.PARENT)) { - throw reportError(configClass, "Root cannot inherit parent name because it has no parent"); + rootName = ""; } else if (rootName.equals(ConfigItem.ELEMENT_NAME)) { rootName = String.join("", (Iterable) () -> lowerCaseFirst(trimmedSegments.iterator())); } else if (rootName.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java index 820aa00a0a214..cd97ddfc8c9f8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java @@ -32,7 +32,11 @@ StringBuilder getCombinedName(final StringBuilder sb) { } final ClassDefinition enclosing = member.getEnclosingDefinition(); if (enclosing instanceof RootDefinition) { - sb.append(((RootDefinition) enclosing).getRootName().replace('.', ':')); + RootDefinition rootDefinition = (RootDefinition) enclosing; + String rootName = rootDefinition.getRootName(); + if (!rootName.isEmpty()) { + sb.append(rootName.replace('.', ':')); + } } if (sb.length() > 0) { sb.append(':'); @@ -48,7 +52,11 @@ StringBuilder getPropertyName(final StringBuilder sb) { } final ClassDefinition enclosing = member.getEnclosingDefinition(); if (enclosing instanceof RootDefinition) { - sb.append(((RootDefinition) enclosing).getRootName()); + RootDefinition rootDefinition = (RootDefinition) enclosing; + String rootName = rootDefinition.getRootName(); + if (!rootName.isEmpty()) { + sb.append(rootName); + } } final String propertyName = member.getPropertyName(); if (!propertyName.isEmpty()) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java index 53a5190632b11..d19d0aa2b8e3e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java @@ -19,18 +19,19 @@ public static ConfigPatternMap makePatterns(List root for (RootDefinition rootDefinition : rootDefinitions) { final String rootName = rootDefinition.getRootName(); ConfigPatternMap addTo = patternMap, child; - assert !rootName.isEmpty(); - NameIterator ni = new NameIterator(rootName); - assert ni.hasNext(); - do { - final String seg = ni.getNextSegment(); - child = addTo.getChild(seg); - ni.next(); - if (child == null) { - addTo.addChild(seg, child = new ConfigPatternMap<>()); - } - addTo = child; - } while (ni.hasNext()); + if (!rootName.isEmpty()) { + NameIterator ni = new NameIterator(rootName); + assert ni.hasNext(); + do { + final String seg = ni.getNextSegment(); + child = addTo.getChild(seg); + ni.next(); + if (child == null) { + addTo.addChild(seg, child = new ConfigPatternMap<>()); + } + addTo = child; + } while (ni.hasNext()); + } addGroup(addTo, rootDefinition, null); } return patternMap; diff --git a/core/test-extension/deployment/src/main/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties index dba86f4b3a5a1..e102554c65ed9 100644 --- a/core/test-extension/deployment/src/main/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -110,3 +110,6 @@ quarkus.btrt.map-of-numbers.key1=one quarkus.btrt.map-of-numbers.key2=two quarkus.btrt.my-enum=optional quarkus.btrt.my-enums=optional,enum-one,enum-two + +### anonymous root property +quarkus.test-property=foo diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TopLevelRootConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TopLevelRootConfig.java new file mode 100644 index 0000000000000..5e03ca0ecfac7 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TopLevelRootConfig.java @@ -0,0 +1,12 @@ +package io.quarkus.extest.runtime.config; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * + */ +@ConfigRoot(name = ConfigItem.PARENT) +public class TopLevelRootConfig { + String testProperty; +} diff --git a/integration-tests/test-extension/src/main/resources/application.properties b/integration-tests/test-extension/src/main/resources/application.properties index f0e0f2e6a638c..b4436b7721f75 100644 --- a/integration-tests/test-extension/src/main/resources/application.properties +++ b/integration-tests/test-extension/src/main/resources/application.properties @@ -101,3 +101,6 @@ quarkus.btrt.map-of-numbers.key1=one quarkus.btrt.map-of-numbers.key2=two quarkus.btrt.my-enum=optional quarkus.btrt.my-enums=optional,enum-one,enum-two + +### anonymous root property +quarkus.test-property=foo From e6430268e00a747eb450ebf4a543b12419327035 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 20:19:39 +0200 Subject: [PATCH 262/602] Add TopLevelRootConfig (additional test) --- .../quarkus/runtime/TopLevelRootConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/TopLevelRootConfig.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/TopLevelRootConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/TopLevelRootConfig.java new file mode 100644 index 0000000000000..72a3f9b01d62f --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/TopLevelRootConfig.java @@ -0,0 +1,21 @@ +package io.quarkus.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * This is used currently only to suppress warnings about unknown properties + * when the user supplies something like: -Dquarkus.profile=someProfile + * + * TODO refactor code to actually use these values + */ +@ConfigRoot(name = ConfigItem.PARENT, phase = ConfigPhase.RUN_TIME) +public class TopLevelRootConfig { + + /** + * Profile that will be active when Quarkus launches + */ + @ConfigItem(defaultValue = "prod") + String profile; +} From 9ead63778da9ad2aa4e9f36d4eadaf06a4ef940e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 23:39:57 +0200 Subject: [PATCH 263/602] Add Config classes to suppress the annoying warning messages --- .../io/quarkus/deployment/DebugConfig.java | 28 ++++++++++++++++ .../quarkus/deployment/LiveReloadConfig.java | 28 ++++++++++++++++ .../io/quarkus/deployment/PlatformConfig.java | 32 +++++++++++++++++++ .../io/quarkus/deployment/TestConfig.java | 28 ++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/LiveReloadConfig.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/PlatformConfig.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/TestConfig.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java new file mode 100644 index 0000000000000..e54d05c735570 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java @@ -0,0 +1,28 @@ +package io.quarkus.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * This is used currently only to suppress warnings about unknown properties + * when the user supplies something like: -Dquarkus.debug.reflection=true + * + * TODO refactor code to actually use these values + */ +@ConfigRoot +public class DebugConfig { + + /** + * If set to true, writes a list of all reflective classes to META-INF + */ + @ConfigItem(defaultValue = "false") + boolean reflection; + + /** + * If set to a directory, all generated classes will be written into that directory + */ + @ConfigItem + Optional generatedClassesDir; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/LiveReloadConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/LiveReloadConfig.java new file mode 100644 index 0000000000000..b754b3355ba63 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/LiveReloadConfig.java @@ -0,0 +1,28 @@ +package io.quarkus.deployment; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * This is used currently only to suppress warnings about unknown properties + * when the user supplies something like: -Dquarkus.live-reload.password=secret + * + * TODO refactor code to actually use these values + */ +@ConfigRoot +public class LiveReloadConfig { + + /** + * Password used to use to connect to the remote dev-mode application + */ + @ConfigItem + Optional password; + + /** + * URL used to use to connect to the remote dev-mode application + */ + @ConfigItem + Optional url; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/PlatformConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/PlatformConfig.java new file mode 100644 index 0000000000000..bfef4ee947b7a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/PlatformConfig.java @@ -0,0 +1,32 @@ +package io.quarkus.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * This is used currently only to suppress warnings about unknown properties + * when the user supplies something like: -Dquarkus.platform.group-id=someGroup + * + * TODO refactor code to actually use these values + */ +@ConfigRoot +public class PlatformConfig { + + /** + * groupId of the platform to use + */ + @ConfigItem(defaultValue = "io.quarkus") + String groupId; + + /** + * artifactId of the platform to use + */ + @ConfigItem(defaultValue = "quarkus-universe-bom") + String artifactId; + + /** + * version of the platform to use + */ + @ConfigItem(defaultValue = "999-SNAPSHOT") + String version; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/TestConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/TestConfig.java new file mode 100644 index 0000000000000..4f50ec7a479e5 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/TestConfig.java @@ -0,0 +1,28 @@ +package io.quarkus.deployment; + +import java.time.Duration; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * This is used currently only to suppress warnings about unknown properties + * when the user supplies something like: -Dquarkus.test.native-image-profile=someProfile + * + * TODO refactor code to actually use these values + */ +@ConfigRoot +public class TestConfig { + + /** + * Duration to wait for the native image to built during testing + */ + @ConfigItem(defaultValue = "PT5M") + Duration nativeImageWaitTime; + + /** + * The profile to use when testing the native image + */ + @ConfigItem(defaultValue = "prod") + String nativeImageProfile; +} From d8fb1633c6d3892dad3303191fec02245c49978b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 6 Dec 2019 23:48:02 +0200 Subject: [PATCH 264/602] Correctly show active profile --- .../java/io/quarkus/deployment/steps/MainClassBuildStep.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index f86ac81b482e0..149ed2204eaee 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -233,6 +233,8 @@ MainClassBuildItem build(List staticInitTasks, .map(f -> f.getInfo()) .sorted() .collect(Collectors.joining(", "))); + ResultHandle activeProfile = tryBlock + .invokeStaticMethod(ofMethod(ProfileManager.class, "getActiveProfile", String.class)); tryBlock.invokeStaticMethod( ofMethod(Timing.class, "printStartupTime", void.class, String.class, String.class, String.class, String.class, String.class, boolean.class), @@ -240,7 +242,7 @@ MainClassBuildItem build(List staticInitTasks, tryBlock.load(applicationInfo.getVersion()), tryBlock.load(Version.getVersion()), featuresHandle, - tryBlock.load(ProfileManager.getActiveProfile()), + activeProfile, tryBlock.load(LaunchMode.DEVELOPMENT.equals(launchMode.getLaunchMode()))); cb = tryBlock.addCatch(Throwable.class); From 919c454c9cbc5bad2ecf187e867fe4dae4553a45 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sun, 8 Dec 2019 01:41:38 +0100 Subject: [PATCH 265/602] Fix org.apache.kafka.common.utils.Crc32C substitution with JDK 11 --- .../client/deployment/KafkaProcessor.java | 33 ----------------- .../runtime/graal/Crc32CSubstitutions.java | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/Crc32CSubstitutions.java diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index 3a1468169b202..59e22986e8068 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -2,7 +2,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.zip.Checksum; import org.apache.kafka.clients.consumer.RangeAssignor; import org.apache.kafka.clients.consumer.RoundRobinAssignor; @@ -34,17 +33,11 @@ import org.jboss.jandex.DotName; import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.JniBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.kafka.client.serialization.JsonbDeserializer; import io.quarkus.kafka.client.serialization.JsonbSerializer; import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; @@ -75,7 +68,6 @@ public class KafkaProcessor { StringDeserializer.class, FloatDeserializer.class, }; - static final String TARGET_JAVA_9_CHECKSUM_FACTORY = "io.quarkus.kafka.client.generated.Target_Java9ChecksumFactory"; @BuildStep public void build(CombinedIndexBuildItem indexBuildItem, BuildProducer reflectiveClass, @@ -115,29 +107,4 @@ public void build(CombinedIndexBuildItem indexBuildItem, BuildProducer producer) { - // make our own class output to ensure that our step is run. - ClassOutput classOutput = new GeneratedClassGizmoAdaptor(producer, false); - try (ClassCreator cc = ClassCreator.builder().className(TARGET_JAVA_9_CHECKSUM_FACTORY) - .classOutput(classOutput).setFinal(true).superClass(Object.class).build()) { - - cc.addAnnotation("com/oracle/svm/core/annotate/TargetClass").addValue("className", - "org.apache.kafka.common.utils.Crc32C$Java9ChecksumFactory"); - cc.addAnnotation("com/oracle/svm/core/annotate/Substitute"); - - try (MethodCreator mc = cc.getMethodCreator("create", Checksum.class)) { - mc.addAnnotation("com/oracle/svm/core/annotate/Substitute"); - mc.returnValue(mc.newInstance(MethodDescriptor.ofConstructor("java.util.zip.CRC32C"))); - } - } - } } diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/Crc32CSubstitutions.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/Crc32CSubstitutions.java new file mode 100644 index 0000000000000..b591bf9120e2b --- /dev/null +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/Crc32CSubstitutions.java @@ -0,0 +1,35 @@ +package io.quarkus.kafka.client.runtime.graal; + +import java.lang.invoke.MethodHandle; +import java.util.zip.Checksum; + +import org.apache.kafka.common.utils.Crc32C; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK11OrLater; + +/** + * The following substitution replaces the usage of {@code MethodHandle} in {@code Java9ChecksumFactory} with a plain + * constructor invocation when run under GraalVM. This is necessary because the native image generator does not support method + * handles. + */ +@TargetClass(value = Crc32C.class, innerClass = "Java9ChecksumFactory", onlyWith = JDK11OrLater.class) +final class Target_org_apache_kafka_common_utils_Crc32C_Java9ChecksumFactory { + + @Alias + @RecomputeFieldValue(kind = Kind.Reset) + private static MethodHandle CONSTRUCTOR; + + @Substitute + public Checksum create() { + try { + return (Checksum) Class.forName("java.util.zip.CRC32C").getConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} From 70a1a73858ac1538484bcdd849b0bfd057222d19 Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sat, 7 Dec 2019 14:37:56 +0100 Subject: [PATCH 266/602] #5950 Pulling up user creation in Dockerfile for layer caching In case of rebuilding an image, the create user command would run again before, as it came after the more volatile commands for adding dependencies and runner. --- .../src/main/resources/templates/dockerfile-jvm.ftl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl index 314bfe8f159e9..a3c6ea7174d3b 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/dockerfile-jvm.ftl @@ -17,15 +17,18 @@ FROM fabric8/java-alpine-openjdk8-jre:1.6.5 ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV AB_ENABLED=jmx_exporter -COPY ${build_dir}/lib/* /deployments/lib/ -COPY ${build_dir}/*-runner.jar /deployments/app.jar -EXPOSE 8080 -# run with user 1001 and be prepared for be running in OpenShift too +# Be prepared for running in OpenShift too RUN adduser -G root --no-create-home --disabled-password 1001 \ && chown -R 1001 /deployments \ && chmod -R "g+rwX" /deployments \ && chown -R 1001:root /deployments + +COPY ${build_dir}/lib/* /deployments/lib/ +COPY ${build_dir}/*-runner.jar /deployments/app.jar +EXPOSE 8080 + +# run with user 1001 USER 1001 -ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file +ENTRYPOINT [ "/deployments/run-java.sh" ] From 38e14dd62ef8c9d28af0588720c960ee68528006 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 12:34:51 +0100 Subject: [PATCH 267/602] Remove a duplicated space from project creation error message --- .../maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 581dcfebc062d..525dbeb34b7d4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -182,7 +182,7 @@ public void execute() throws MojoExecutionException { projectRoot = new File(outputDirectory, projectArtifactId); if (projectRoot.exists()) { throw new MojoExecutionException("Unable to create the project, " + - " the directory " + projectRoot.getAbsolutePath() + " already exists"); + "the directory " + projectRoot.getAbsolutePath() + " already exists"); } } From 4cedf0520c30e47656ddceff2b8a6f698a879ab7 Mon Sep 17 00:00:00 2001 From: Elegie Date: Wed, 13 Nov 2019 22:55:49 +0100 Subject: [PATCH 268/602] [5360] Translate Windows-Style path in volume mounting, so that it works in legacy Docker Toolbox. --- .../pkg/steps/NativeImageBuildStep.java | 9 ++++-- .../io/quarkus/deployment/util/FileUtil.java | 29 +++++++++++++++++++ .../quarkus/deployment/util/FileUtilTest.java | 28 ++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/FileUtilTest.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index c2c122d68e01e..35cc15a7ba009 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -32,6 +32,7 @@ import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageSourceJarBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.deployment.util.FileUtil; public class NativeImageBuildStep { @@ -82,8 +83,12 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa String containerRuntime = nativeConfig.containerRuntime.orElse("docker"); // E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image" nativeImage = new ArrayList<>(); - Collections.addAll(nativeImage, containerRuntime, "run", "-v", - outputDir.toAbsolutePath() + ":/project:z"); + + String outputPath = outputDir.toAbsolutePath().toString(); + if (IS_WINDOWS) { + outputPath = FileUtil.translateToVolumePath(outputPath); + } + Collections.addAll(nativeImage, containerRuntime, "run", "-v", outputPath + ":/project:z"); if (IS_LINUX) { if ("docker".equals(containerRuntime)) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/FileUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/FileUtil.java index cafb22c254750..24607ced1447e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/FileUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/FileUtil.java @@ -8,6 +8,9 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class FileUtil { @@ -48,4 +51,30 @@ public static byte[] readFileContents(InputStream inputStream) throws IOExceptio } return out.toByteArray(); } + + /** + * Translates a file path from the Windows Style to a syntax accepted by Docker, + * so that volumes be safely mounted in both Docker for Windows and the legacy + * Docker Toolbox. + *

+ * docker run -v //c/foo/bar:/somewhere (...) + *

+ * You should only use this method on Windows-style paths, and not Unix-style + * paths. + * + * @see https://github.com/quarkusio/quarkus/issues/5360 + * @param windowsStylePath A path formatted in Windows-style, e.g. "C:\foo\bar". + * @return A translated path accepted by Docker, e.g. "//c/foo/bar". + */ + public static String translateToVolumePath(String windowsStylePath) { + String translated = windowsStylePath.replace('\\', '/'); + Pattern p = Pattern.compile("^(\\w)(?:$|:(/)?(.*))"); + Matcher m = p.matcher(translated); + if (m.matches()) { + String slash = Optional.ofNullable(m.group(2)).orElse("/"); + String path = Optional.ofNullable(m.group(3)).orElse(""); + return "//" + m.group(1).toLowerCase() + slash + path; + } + return translated; + } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/FileUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/FileUtilTest.java new file mode 100644 index 0000000000000..dcc09761c4738 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/FileUtilTest.java @@ -0,0 +1,28 @@ +package io.quarkus.deployment.util; + +import static io.quarkus.deployment.util.FileUtil.translateToVolumePath; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class FileUtilTest { + + @Test + public void testTranslateToVolumePath() { + // Windows-Style paths are formatted. + assertEquals("//c/", translateToVolumePath("C")); + assertEquals("//c/", translateToVolumePath("C:")); + assertEquals("//c/", translateToVolumePath("C:\\")); + assertEquals("//c/Users", translateToVolumePath("C:\\Users")); + assertEquals("//c/Users/Quarkus/lambdatest-1.0-SNAPSHOT-native-image-source-jar", + translateToVolumePath("C:\\Users\\Quarkus\\lambdatest-1.0-SNAPSHOT-native-image-source-jar")); + + // Side effect for Unix-style path. + assertEquals("//c/Users/Quarkus", translateToVolumePath("c:/Users/Quarkus")); + + // Side effects for fancy inputs - for the sake of documentation. + assertEquals("something/bizarre", translateToVolumePath("something\\bizarre")); + assertEquals("something.bizarre", translateToVolumePath("something.bizarre")); + } + +} From bc9eb6c80f6a4c63960b4b45807cfaa454936537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Tue, 5 Nov 2019 13:19:20 +0100 Subject: [PATCH 269/602] feat: mongodb health check --- docs/src/main/asciidoc/mongodb.adoc | 9 +++++ extensions/mongodb-client/deployment/pom.xml | 4 +++ .../MongoClientBuildTimeConfig.java | 14 ++++++++ .../deployment/MongoClientProcessor.java | 7 ++++ extensions/mongodb-client/runtime/pom.xml | 7 ++++ .../mongodb/health/MongoHealthCheck.java | 35 +++++++++++++++++++ integration-tests/mongodb-client/pom.xml | 5 +++ .../quarkus/it/mongodb/BookResourceTest.java | 10 ++++++ 8 files changed, 91 insertions(+) create mode 100644 extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java create mode 100644 extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/health/MongoHealthCheck.java diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index e8069c6486787..cf65ac4a0fea0 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -474,6 +474,15 @@ public class CodecFruitService { The link:mongodb-panache[MongoDB with Panache] extension facilitates the usage of MongoDB by providing active record style entities (and repositories) like you have in link:hibernate-orm-panache.html[Hibernate ORM with Panache] and focuses on making your entities trivial and fun to write in Quarkus. +== Connection Health Check + +If you are using the `quarkus-smallrye-health` extension, `quarkus-mongodb` will automatically add a readiness health check +to validate the connection to the cluster. + +So when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. + +This behavior can be disabled via the property `quarkus.mongodb.health.enabled`. + == Building a native executable You can use the MongoDB client in a native executable. diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 2b8116cc131ee..f7882d5c3a20d 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -30,6 +30,10 @@ io.quarkus quarkus-vertx-deployment + + io.quarkus + quarkus-smallrye-health-spi + io.quarkus quarkus-mongodb-client diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java new file mode 100644 index 0000000000000..5498842cd34e6 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java @@ -0,0 +1,14 @@ +package io.quarkus.mongodb.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "mongodb", phase = ConfigPhase.BUILD_TIME) +public class MongoClientBuildTimeConfig { + /** + * Whether or not an healtcheck is published in case the smallrye-health extension is present (default to true). + */ + @ConfigItem(name = "health.enabled", defaultValue = "true") + public boolean healthEnabled; +} 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 815f49a604951..7d60b6efdc66a 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 @@ -27,6 +27,7 @@ import io.quarkus.mongodb.runtime.MongoClientProducer; import io.quarkus.mongodb.runtime.MongoClientRecorder; import io.quarkus.runtime.RuntimeValue; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; public class MongoClientProcessor { @@ -68,4 +69,10 @@ MongoClientBuildItem build(BuildProducer feature, MongoClientR RuntimeValue reactiveClient = recorder.configureTheReactiveClient(); return new MongoClientBuildItem(client, reactiveClient); } + + @BuildStep + HealthBuildItem addHealthCheck(MongoClientBuildTimeConfig buildTimeConfig) { + return new HealthBuildItem("io.quarkus.mongodb.health.MongoHealthCheck", + buildTimeConfig.healthEnabled, "mongodb"); + } } diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml index 99ff8c86f87c5..73f183b6df50f 100644 --- a/extensions/mongodb-client/runtime/pom.xml +++ b/extensions/mongodb-client/runtime/pom.xml @@ -36,6 +36,13 @@ mongodb-driver-reactivestreams + + + io.quarkus + quarkus-smallrye-health + true + + org.graalvm.nativeimage svm diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/health/MongoHealthCheck.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/health/MongoHealthCheck.java new file mode 100644 index 0000000000000..a17bfdf4bf6d4 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/health/MongoHealthCheck.java @@ -0,0 +1,35 @@ +package io.quarkus.mongodb.health; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; + +import com.mongodb.client.MongoClient; + +@Readiness +@ApplicationScoped +public class MongoHealthCheck implements HealthCheck { + @Inject + MongoClient mongoClient; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("MongoDB connection health check").up(); + try { + StringBuilder databases = new StringBuilder(); + for (String db : mongoClient.listDatabaseNames()) { + if (databases.length() != 0) { + databases.append(", "); + } + databases.append(db); + } + return builder.withData("databases", databases.toString()).build(); + } catch (Exception e) { + return builder.down().withData("reason", e.getMessage()).build(); + } + } +} diff --git a/integration-tests/mongodb-client/pom.xml b/integration-tests/mongodb-client/pom.xml index fde826f624dcc..511944ca179c9 100644 --- a/integration-tests/mongodb-client/pom.xml +++ b/integration-tests/mongodb-client/pom.xml @@ -24,6 +24,11 @@ quarkus-mongodb-client + + io.quarkus + quarkus-smallrye-health + + io.quarkus quarkus-junit5 diff --git a/integration-tests/mongodb-client/src/test/java/io/quarkus/it/mongodb/BookResourceTest.java b/integration-tests/mongodb-client/src/test/java/io/quarkus/it/mongodb/BookResourceTest.java index cadf9ad86d202..9a069d0adbd90 100644 --- a/integration-tests/mongodb-client/src/test/java/io/quarkus/it/mongodb/BookResourceTest.java +++ b/integration-tests/mongodb-client/src/test/java/io/quarkus/it/mongodb/BookResourceTest.java @@ -1,6 +1,8 @@ package io.quarkus.it.mongodb; import static io.restassured.RestAssured.get; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; import java.io.IOException; import java.util.Arrays; @@ -142,4 +144,12 @@ public void testReactiveClients() { callTheEndpoint("/reactive-books"); } + @Test + public void health() throws Exception { + RestAssured.when().get("/health/ready").then() + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP"), + "checks.name", containsInAnyOrder("MongoDB connection health check")); + } + } From 1821b5d268db46541438d3d9475f05f4a531b4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Wed, 4 Dec 2019 14:21:48 +0100 Subject: [PATCH 270/602] feat: kafka client health check --- docs/src/main/asciidoc/kafka.adoc | 9 +++ extensions/kafka-client/deployment/pom.xml | 4 ++ .../deployment/KafkaBuildTimeConfig.java | 15 +++++ .../client/deployment/KafkaProcessor.java | 7 +++ extensions/kafka-client/runtime/pom.xml | 5 ++ .../kafka/client/health/KafkaHealthCheck.java | 56 +++++++++++++++++++ integration-tests/kafka/pom.xml | 5 ++ .../src/main/resources/application.properties | 4 ++ .../quarkus/it/kafka/KafkaProducerTest.java | 11 ++++ 9 files changed, 116 insertions(+) create mode 100644 extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java create mode 100644 extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index d1eb5b7fcb1ab..bb140b4329cb8 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -308,6 +308,15 @@ You can build the native executable with: ./mvnw package -Pnative ---- +== Kafka Health Check + +If you are using the `quarkus-smallrye-health` extension, `quarkus-kafka` could automatically add a readiness health check +to validate the connection to the broker. This is disabled by default. + +If enabled, when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. + +This behavior can be enabled via the property `quarkus.kafka.health.enabled`. + == Going further This guide has shown how you can interact with Kafka using Quarkus. diff --git a/extensions/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index 51ca51ab83dfc..240d59931c584 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -22,6 +22,10 @@ io.quarkus quarkus-kafka-client + + io.quarkus + quarkus-smallrye-health-spi + io.quarkus quarkus-junit5-internal diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java new file mode 100644 index 0000000000000..87a0bb28e4b0a --- /dev/null +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java @@ -0,0 +1,15 @@ +package io.quarkus.kafka.client.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "kafka", phase = ConfigPhase.BUILD_TIME) +public class KafkaBuildTimeConfig { + /** + * Whether or not an health check is published in case the smallrye-health extension is present (default to false). + * If you enable the health check, you must specify the `kafka.bootstrap.servers` property. + */ + @ConfigItem(name = "health.enabled", defaultValue = "false") + public boolean healthEnabled; +} diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java index 59e22986e8068..cd7f684695632 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaProcessor.java @@ -42,6 +42,7 @@ import io.quarkus.kafka.client.serialization.JsonbSerializer; import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; import io.quarkus.kafka.client.serialization.ObjectMapperSerializer; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; public class KafkaProcessor { @@ -107,4 +108,10 @@ public void build(CombinedIndexBuildItem indexBuildItem, BuildProducerquarkus-jackson true + + io.quarkus + quarkus-smallrye-health + true + org.apache.kafka diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java new file mode 100644 index 0000000000000..d2e3e0e747e66 --- /dev/null +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java @@ -0,0 +1,56 @@ +package io.quarkus.kafka.client.health; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.AdminClientConfig; +import org.apache.kafka.common.Node; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; + +@Readiness +@ApplicationScoped +public class KafkaHealthCheck implements HealthCheck { + @ConfigProperty(name = "kafka.bootstrap.servers", defaultValue = "localhost:9092") + private String bootstrapServer; + + private AdminClient client; + + @PostConstruct + void init() { + Map conf = new HashMap<>(); + conf.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServer); + conf.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "5000"); + client = AdminClient.create(conf); + } + + @PreDestroy + void stop() { + client.close(); + } + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Kafka connection health check").up(); + try { + StringBuilder nodes = new StringBuilder(); + for (Node node : client.describeCluster().nodes().get()) { + if (nodes.length() > 0) { + nodes.append(','); + } + nodes.append(node.host()).append(':').append(node.port()); + } + return builder.withData("nodes", nodes.toString()).build(); + } catch (Exception e) { + return builder.down().withData("reason", e.getMessage()).build(); + } + } +} diff --git a/integration-tests/kafka/pom.xml b/integration-tests/kafka/pom.xml index 629f78a0d5bb7..3b7cfff0b833b 100644 --- a/integration-tests/kafka/pom.xml +++ b/integration-tests/kafka/pom.xml @@ -38,6 +38,11 @@ quarkus-resteasy-jackson + + io.quarkus + quarkus-smallrye-health + + io.quarkus diff --git a/integration-tests/kafka/src/main/resources/application.properties b/integration-tests/kafka/src/main/resources/application.properties index d9f640c3c192c..989f7c32eb318 100644 --- a/integration-tests/kafka/src/main/resources/application.properties +++ b/integration-tests/kafka/src/main/resources/application.properties @@ -6,6 +6,10 @@ quarkus.kafka-streams.bootstrap-servers=localhost:19092 quarkus.kafka-streams.application-id=streams-test-pipeline quarkus.kafka-streams.topics=streams-test-categories,streams-test-customers +# enable health check +quarkus.kafka.health.enabled=true +kafka.bootstrap.servers=localhost:19092 + # streams options kafka-streams.cache.max.bytes.buffering=10240 kafka-streams.commit.interval.ms=1000 diff --git a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/KafkaProducerTest.java b/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/KafkaProducerTest.java index af3add9461855..6e72d362807d9 100644 --- a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/KafkaProducerTest.java +++ b/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/KafkaProducerTest.java @@ -1,5 +1,8 @@ package io.quarkus.it.kafka; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; + import java.time.Duration; import java.util.Collections; import java.util.Properties; @@ -40,4 +43,12 @@ public void test() throws Exception { Assertions.assertEquals(records.value(), "hello"); } + @Test + public void health() throws Exception { + RestAssured.when().get("/health/ready").then() + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP"), + "checks.name", containsInAnyOrder("Kafka connection health check")); + } + } From 6f3ab1ab11d9a15ac5e215bf5db2a184289da05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 29 Nov 2019 10:21:51 +0100 Subject: [PATCH 271/602] feat: neo4j health check --- docs/src/main/asciidoc/neo4j.adoc | 9 ++++++ extensions/neo4j/deployment/pom.xml | 4 +++ .../deployment/Neo4jBuildTimeConfig.java | 14 ++++++++++ .../deployment/Neo4jDriverProcessor.java | 7 +++++ extensions/neo4j/runtime/pom.xml | 5 ++++ .../neo4j/runtime/heath/Neo4jHealthCheck.java | 28 +++++++++++++++++++ integration-tests/neo4j/pom.xml | 6 ++++ .../it/neo4j/Neo4jFunctionalityTest.java | 8 ++++++ 8 files changed, 81 insertions(+) create mode 100644 extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java create mode 100644 extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 0738cf8654a75..82be5901fbb48 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -400,6 +400,15 @@ It can be run with `java -jar target/neo4j-quickstart-1.0-SNAPSHOT-runner.jar`. With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`. Depending on your system, that will take some time. +=== Connection Health Check + +If you are using the `quarkus-smallrye-health` extension, `quarkus-neo4j` will automatically add a readiness health check +to validate the connection to Neo4j. + +So when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. + +This behavior can be disabled via the property `quarkus.neo4j.health.enabled`. + === Explore Cypher and the Graph There are tons of options to model your domain within a Graph. diff --git a/extensions/neo4j/deployment/pom.xml b/extensions/neo4j/deployment/pom.xml index de4f2d59f5af3..9ec3f78fed856 100644 --- a/extensions/neo4j/deployment/pom.xml +++ b/extensions/neo4j/deployment/pom.xml @@ -21,6 +21,10 @@ io.quarkus quarkus-arc-deployment + + io.quarkus + quarkus-smallrye-health-spi + io.quarkus quarkus-neo4j diff --git a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java new file mode 100644 index 0000000000000..e2e1e7512f561 --- /dev/null +++ b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java @@ -0,0 +1,14 @@ +package io.quarkus.neo4j.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "neo4j", phase = ConfigPhase.BUILD_TIME) +public class Neo4jBuildTimeConfig { + /** + * Whether or not an health check is published in case the smallrye-health extension is present (default to true). + */ + @ConfigItem(name = "health.enabled", defaultValue = "true") + public boolean healthEnabled; +} diff --git a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java index f4f7b2e047a9d..da6a6106ed1d3 100644 --- a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java +++ b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java @@ -12,6 +12,7 @@ import io.quarkus.neo4j.runtime.Neo4jConfiguration; import io.quarkus.neo4j.runtime.Neo4jDriverProducer; import io.quarkus.neo4j.runtime.Neo4jDriverRecorder; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; class Neo4jDriverProcessor { @@ -37,4 +38,10 @@ void configureDriverProducer(Neo4jDriverRecorder recorder, BeanContainerBuildIte recorder.configureNeo4jProducer(beanContainerBuildItem.getValue(), configuration, shutdownContext); } + + @BuildStep + HealthBuildItem addHealthCheck(Neo4jBuildTimeConfig buildTimeConfig) { + return new HealthBuildItem("io.quarkus.neo4j.runtime.heath.Neo4jHealthCheck", + buildTimeConfig.healthEnabled, "neo4j"); + } } diff --git a/extensions/neo4j/runtime/pom.xml b/extensions/neo4j/runtime/pom.xml index 3aea33dd25baf..579406480e685 100644 --- a/extensions/neo4j/runtime/pom.xml +++ b/extensions/neo4j/runtime/pom.xml @@ -21,6 +21,11 @@ io.quarkus quarkus-arc + + io.quarkus + quarkus-smallrye-health + true + org.graalvm.nativeimage svm diff --git a/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java new file mode 100644 index 0000000000000..979eceeea446d --- /dev/null +++ b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java @@ -0,0 +1,28 @@ +package io.quarkus.neo4j.runtime.heath; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; +import org.neo4j.driver.Driver; + +@Readiness +@ApplicationScoped +public class Neo4jHealthCheck implements HealthCheck { + @Inject + Driver driver; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Neo4j connection health check").up(); + try { + driver.verifyConnectivity(); + return builder.build(); + } catch (Exception e) { + return builder.down().withData("reason", e.getMessage()).build(); + } + } +} diff --git a/integration-tests/neo4j/pom.xml b/integration-tests/neo4j/pom.xml index f59d9493af8ce..8f02212fe5caf 100644 --- a/integration-tests/neo4j/pom.xml +++ b/integration-tests/neo4j/pom.xml @@ -44,6 +44,12 @@ io.quarkus quarkus-resteasy-jsonb + + + io.quarkus + quarkus-smallrye-health + + io.projectreactor diff --git a/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java b/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java index b6ff136d4399c..ebaaf7ef05917 100644 --- a/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java +++ b/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java @@ -44,4 +44,12 @@ public void testReactiveNeo4jFunctionality() { .then().statusCode(200) .contentType("text/event-stream"); } + + @Test + public void health() throws Exception { + RestAssured.when().get("/health/ready").then() + .body("status", is("UP"), + "checks.status", containsInAnyOrder("UP"), + "checks.name", containsInAnyOrder("Neo4j connection health check")); + } } From c777a8fca798121e722ed46ec8028000fe785ca9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 12:11:22 +0100 Subject: [PATCH 272/602] Various editorializations for the health checks documentation --- docs/src/main/asciidoc/kafka.adoc | 4 ++-- docs/src/main/asciidoc/mongodb.adoc | 2 +- docs/src/main/asciidoc/neo4j.adoc | 2 +- .../quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java | 3 ++- .../mongodb/deployment/MongoClientBuildTimeConfig.java | 2 +- .../io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index bb140b4329cb8..5796514116657 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -310,12 +310,12 @@ You can build the native executable with: == Kafka Health Check -If you are using the `quarkus-smallrye-health` extension, `quarkus-kafka` could automatically add a readiness health check +If you are using the `quarkus-smallrye-health` extension, `quarkus-kafka` can add a readiness health check to validate the connection to the broker. This is disabled by default. If enabled, when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. -This behavior can be enabled via the property `quarkus.kafka.health.enabled`. +This behavior can be enabled by setting the `quarkus.kafka.health.enabled` property to `true` in your `application.properties`. == Going further diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index cf65ac4a0fea0..36920d6143c02 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -481,7 +481,7 @@ to validate the connection to the cluster. So when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. -This behavior can be disabled via the property `quarkus.mongodb.health.enabled`. +This behavior can be disabled by setting the `quarkus.mongodb.health.enabled` property to `false` in your `application.properties`. == Building a native executable diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 82be5901fbb48..90c0a8ea63775 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -407,7 +407,7 @@ to validate the connection to Neo4j. So when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. -This behavior can be disabled via the property `quarkus.neo4j.health.enabled`. +This behavior can be disabled by setting the `quarkus.neo4j.health.enabled` property to `false` in your `application.properties`. === Explore Cypher and the Graph diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java index 87a0bb28e4b0a..58e07f1558acd 100644 --- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java +++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/KafkaBuildTimeConfig.java @@ -7,7 +7,8 @@ @ConfigRoot(name = "kafka", phase = ConfigPhase.BUILD_TIME) public class KafkaBuildTimeConfig { /** - * Whether or not an health check is published in case the smallrye-health extension is present (default to false). + * Whether or not an health check is published in case the smallrye-health extension is present. + *

* If you enable the health check, you must specify the `kafka.bootstrap.servers` property. */ @ConfigItem(name = "health.enabled", defaultValue = "false") diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java index 5498842cd34e6..8352dcaab7b9a 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java @@ -7,7 +7,7 @@ @ConfigRoot(name = "mongodb", phase = ConfigPhase.BUILD_TIME) public class MongoClientBuildTimeConfig { /** - * Whether or not an healtcheck is published in case the smallrye-health extension is present (default to true). + * Whether or not an health check is published in case the smallrye-health extension is present. */ @ConfigItem(name = "health.enabled", defaultValue = "true") public boolean healthEnabled; diff --git a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java index e2e1e7512f561..c968d7f59eae6 100644 --- a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java +++ b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java @@ -7,7 +7,7 @@ @ConfigRoot(name = "neo4j", phase = ConfigPhase.BUILD_TIME) public class Neo4jBuildTimeConfig { /** - * Whether or not an health check is published in case the smallrye-health extension is present (default to true). + * Whether or not an health check is published in case the smallrye-health extension is present. */ @ConfigItem(name = "health.enabled", defaultValue = "true") public boolean healthEnabled; From 7de98da6128d223b1ada53cfdc15be981b51438e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 13:21:08 +0100 Subject: [PATCH 273/602] Separate Kafka and Kafka Streams integration tests --- ci-templates/stages.yml | 3 +- integration-tests/kafka-streams/pom.xml | 148 ++++++++++++++++++ .../io/quarkus/it/kafka/streams/Category.java | 0 .../io/quarkus/it/kafka/streams/Customer.java | 0 .../it/kafka/streams/EnrichedCustomer.java | 0 .../kafka/streams/KafkaStreamsEndpoint.java | 0 .../kafka/streams/KafkaStreamsPipeline.java | 0 .../src/main/resources/application.properties | 13 ++ .../it/kafka/streams/KafkaStreamsITCase.java | 0 .../it/kafka/streams/KafkaStreamsTest.java | 1 - .../it/kafka/streams/KafkaTestResource.java | 41 +++++ integration-tests/kafka/pom.xml | 9 +- .../src/main/resources/application.properties | 10 -- integration-tests/pom.xml | 1 + 14 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 integration-tests/kafka-streams/pom.xml rename integration-tests/{kafka => kafka-streams}/src/main/java/io/quarkus/it/kafka/streams/Category.java (100%) rename integration-tests/{kafka => kafka-streams}/src/main/java/io/quarkus/it/kafka/streams/Customer.java (100%) rename integration-tests/{kafka => kafka-streams}/src/main/java/io/quarkus/it/kafka/streams/EnrichedCustomer.java (100%) rename integration-tests/{kafka => kafka-streams}/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsEndpoint.java (100%) rename integration-tests/{kafka => kafka-streams}/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsPipeline.java (100%) create mode 100644 integration-tests/kafka-streams/src/main/resources/application.properties rename integration-tests/{kafka => kafka-streams}/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsITCase.java (100%) rename integration-tests/{kafka => kafka-streams}/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java (99%) create mode 100644 integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaTestResource.java diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index eda4eb34cd444..68115c5247de9 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -239,11 +239,12 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 20 + timeoutInMinutes: 25 modules: - artemis-core - artemis-jms - kafka + - kafka-streams name: messaging - template: native-build-steps.yaml diff --git a/integration-tests/kafka-streams/pom.xml b/integration-tests/kafka-streams/pom.xml new file mode 100644 index 0000000000000..413cfa4785718 --- /dev/null +++ b/integration-tests/kafka-streams/pom.xml @@ -0,0 +1,148 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-kafka-streams + Quarkus - Integration Tests - Kafka Streams + The Kafka Streams integration tests module + + + true + + + + + io.quarkus + quarkus-integration-test-class-transformer + + + io.quarkus + quarkus-integration-test-shared-library + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jackson + + + + + io.quarkus + quarkus-smallrye-health + + + + + io.quarkus + quarkus-kafka-streams + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.debezium + debezium-core + test + + + io.debezium + debezium-core + test-jar + test + + + org.apache.kafka + kafka_2.12 + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image-it-main + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + diff --git a/integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/Category.java b/integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/Category.java similarity index 100% rename from integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/Category.java rename to integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/Category.java diff --git a/integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/Customer.java b/integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/Customer.java similarity index 100% rename from integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/Customer.java rename to integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/Customer.java diff --git a/integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/EnrichedCustomer.java b/integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/EnrichedCustomer.java similarity index 100% rename from integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/EnrichedCustomer.java rename to integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/EnrichedCustomer.java diff --git a/integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsEndpoint.java b/integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsEndpoint.java similarity index 100% rename from integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsEndpoint.java rename to integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsEndpoint.java diff --git a/integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsPipeline.java b/integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsPipeline.java similarity index 100% rename from integration-tests/kafka/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsPipeline.java rename to integration-tests/kafka-streams/src/main/java/io/quarkus/it/kafka/streams/KafkaStreamsPipeline.java diff --git a/integration-tests/kafka-streams/src/main/resources/application.properties b/integration-tests/kafka-streams/src/main/resources/application.properties new file mode 100644 index 0000000000000..d9f640c3c192c --- /dev/null +++ b/integration-tests/kafka-streams/src/main/resources/application.properties @@ -0,0 +1,13 @@ +quarkus.log.category.kafka.level=WARN +quarkus.log.category.\"org.apache.kafka\".level=WARN +quarkus.log.category.\"org.apache.zookeeper\".level=WARN + +quarkus.kafka-streams.bootstrap-servers=localhost:19092 +quarkus.kafka-streams.application-id=streams-test-pipeline +quarkus.kafka-streams.topics=streams-test-categories,streams-test-customers + +# streams options +kafka-streams.cache.max.bytes.buffering=10240 +kafka-streams.commit.interval.ms=1000 +kafka-streams.metadata.max.age.ms=500 +kafka-streams.auto.offset.reset=earliest diff --git a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsITCase.java b/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsITCase.java similarity index 100% rename from integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsITCase.java rename to integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsITCase.java diff --git a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java b/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java similarity index 99% rename from integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java rename to integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java index 1598b816decfc..114bede2e435e 100644 --- a/integration-tests/kafka/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java +++ b/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaStreamsTest.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.quarkus.it.kafka.KafkaTestResource; import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; import io.quarkus.kafka.client.serialization.ObjectMapperSerializer; import io.quarkus.test.common.QuarkusTestResource; diff --git a/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaTestResource.java b/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaTestResource.java new file mode 100644 index 0000000000000..bdafe00a01d04 --- /dev/null +++ b/integration-tests/kafka-streams/src/test/java/io/quarkus/it/kafka/streams/KafkaTestResource.java @@ -0,0 +1,41 @@ +package io.quarkus.it.kafka.streams; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; + +import io.debezium.kafka.KafkaCluster; +import io.debezium.util.Testing; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class KafkaTestResource implements QuarkusTestResourceLifecycleManager { + + private KafkaCluster kafka; + + @Override + public Map start() { + try { + Properties props = new Properties(); + props.setProperty("zookeeper.connection.timeout.ms", "45000"); + File directory = Testing.Files.createTestingDirectory("kafka-data", true); + kafka = new KafkaCluster().withPorts(2182, 19092) + .addBrokers(1) + .usingDirectory(directory) + .deleteDataUponShutdown(true) + .withKafkaConfiguration(props) + .deleteDataPriorToStartup(true) + .startup(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return Collections.emptyMap(); + } + + @Override + public void stop() { + if (kafka != null) { + kafka.shutdown(); + } + } +} diff --git a/integration-tests/kafka/pom.xml b/integration-tests/kafka/pom.xml index 3b7cfff0b833b..8413ed61f9601 100644 --- a/integration-tests/kafka/pom.xml +++ b/integration-tests/kafka/pom.xml @@ -38,6 +38,7 @@ quarkus-resteasy-jackson + io.quarkus quarkus-smallrye-health @@ -48,10 +49,6 @@ io.quarkus quarkus-kafka-client - - io.quarkus - quarkus-kafka-streams - @@ -80,10 +77,6 @@ kafka_2.12 test - - io.quarkus - quarkus-smallrye-health - diff --git a/integration-tests/kafka/src/main/resources/application.properties b/integration-tests/kafka/src/main/resources/application.properties index 989f7c32eb318..aaa5ea04c4993 100644 --- a/integration-tests/kafka/src/main/resources/application.properties +++ b/integration-tests/kafka/src/main/resources/application.properties @@ -2,16 +2,6 @@ quarkus.log.category.kafka.level=WARN quarkus.log.category.\"org.apache.kafka\".level=WARN quarkus.log.category.\"org.apache.zookeeper\".level=WARN -quarkus.kafka-streams.bootstrap-servers=localhost:19092 -quarkus.kafka-streams.application-id=streams-test-pipeline -quarkus.kafka-streams.topics=streams-test-categories,streams-test-customers - # enable health check quarkus.kafka.health.enabled=true kafka.bootstrap.servers=localhost:19092 - -# streams options -kafka-streams.cache.max.bytes.buffering=10240 -kafka-streams.commit.interval.ms=1000 -kafka-streams.metadata.max.age.ms=500 -kafka-streams.auto.offset.reset=earliest diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 4b5bdea0b3a70..508f7d6d3fe9c 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -29,6 +29,7 @@ infinispan-embedded main kafka + kafka-streams jpa jpa-derby jpa-postgresql From 8c29a8be63135dceae02f5ed5dfc830721bf36db Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 13:29:34 +0100 Subject: [PATCH 274/602] Make the Kafka health check configuration more future proof --- docs/src/main/asciidoc/kafka.adoc | 1 + .../kafka/client/health/KafkaHealthCheck.java | 7 ++++--- .../client/runtime/KafkaRuntimeConfig.java | 21 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaRuntimeConfig.java diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 5796514116657..3989e1958465b 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -316,6 +316,7 @@ to validate the connection to the broker. This is disabled by default. If enabled, when you access the `/health/ready` endpoint of your application you will have information about the connection validation status. This behavior can be enabled by setting the `quarkus.kafka.health.enabled` property to `true` in your `application.properties`. +You also need to point `quarkus.kafka.bootstrap-servers` to your Kafka cluster. == Going further diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java index d2e3e0e747e66..ac717b2d03bca 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/health/KafkaHealthCheck.java @@ -19,15 +19,16 @@ @Readiness @ApplicationScoped public class KafkaHealthCheck implements HealthCheck { - @ConfigProperty(name = "kafka.bootstrap.servers", defaultValue = "localhost:9092") - private String bootstrapServer; + + @ConfigProperty(name = "quarkus.kafka.bootstrap-servers", defaultValue = "localhost:9092") + private String bootstrapServers; private AdminClient client; @PostConstruct void init() { Map conf = new HashMap<>(); - conf.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServer); + conf.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); conf.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "5000"); client = AdminClient.create(conf); } diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaRuntimeConfig.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaRuntimeConfig.java new file mode 100644 index 0000000000000..4172631cc99f8 --- /dev/null +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/KafkaRuntimeConfig.java @@ -0,0 +1,21 @@ +package io.quarkus.kafka.client.runtime; + +import java.net.InetSocketAddress; +import java.util.List; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * For now, this is not used except to avoid a warning for the health check. + */ +@ConfigRoot(name = "kafka", phase = ConfigPhase.RUN_TIME) +public class KafkaRuntimeConfig { + + /** + * A comma-separated list of host:port pairs identifying the Kafka bootstrap server(s) + */ + @ConfigItem(defaultValue = "localhost:9012") + public List bootstrapServers; +} diff --git a/integration-tests/kafka/src/main/resources/application.properties b/integration-tests/kafka/src/main/resources/application.properties index aaa5ea04c4993..c63efc952a604 100644 --- a/integration-tests/kafka/src/main/resources/application.properties +++ b/integration-tests/kafka/src/main/resources/application.properties @@ -4,4 +4,4 @@ quarkus.log.category.\"org.apache.zookeeper\".level=WARN # enable health check quarkus.kafka.health.enabled=true -kafka.bootstrap.servers=localhost:19092 +quarkus.kafka.bootstrap-servers=localhost:19092 From 8e94162947e7ea7787bab50f8e0319db43df9cbe Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 9 Dec 2019 10:36:15 +1100 Subject: [PATCH 275/602] Disable HTMLUnit cache Fixes #5926 --- .../src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index 10961917ae92c..bf18f6101ddf1 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -135,6 +135,7 @@ private static UserRepresentation createUser(String username, String... realmRol @Test public void testCodeFlowNoConsent() throws IOException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -158,6 +159,7 @@ public void testCodeFlowNoConsent() throws IOException { @Test public void testTokenTimeoutLogout() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -188,6 +190,7 @@ public void testTokenTimeoutLogout() throws IOException, InterruptedException { @Test public void testIdTokenInjection() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -210,6 +213,7 @@ public void testIdTokenInjection() throws IOException, InterruptedException { @Test public void testAccessTokenInjection() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -232,6 +236,7 @@ public void testAccessTokenInjection() throws IOException, InterruptedException @Test public void testAccessAndRefreshTokenInjection() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -254,6 +259,7 @@ public void testAccessAndRefreshTokenInjection() throws IOException, Interrupted @Test public void testAccessAndRefreshTokenInjectionWithoutIndexHtml() throws IOException, InterruptedException { try (final WebClient webClient = new WebClient()) { + webClient.getCache().setMaxSize(0); HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh"); assertEquals("Log in to quarkus", page.getTitleText()); From 2b5d48478c34664ba94b6edbe1450d1adb0dcbb5 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 9 Dec 2019 18:04:43 +1100 Subject: [PATCH 276/602] Don't fire onStart event till migrations have happened This makes sure that the DB is ready to used when the application actually starts, and fixes an intermittent CI failure where Quartz would fail to start as the DB would not be ready. --- .../src/main/java/io/quarkus/flyway/FlywayProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index 42613a5575660..0654daac8bf46 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -36,6 +36,7 @@ import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.flyway.runtime.FlywayBuildConfig; import io.quarkus.flyway.runtime.FlywayProducer; @@ -90,12 +91,13 @@ void build(BuildProducer additionalBeanProducer, */ @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - void configureRuntimeProperties(FlywayRecorder recorder, + ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder, FlywayRuntimeConfig flywayRuntimeConfig, BeanContainerBuildItem beanContainer, DataSourceInitializedBuildItem dataSourceInitializedBuildItem) { recorder.configureFlywayProperties(flywayRuntimeConfig, beanContainer.getValue()); recorder.doStartActions(flywayRuntimeConfig, beanContainer.getValue()); + return new ServiceStartBuildItem("flyway"); } private void registerNativeImageResources(BuildProducer resource, From 9844b31b5f49916b01722f21459a1a361521e5b4 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 9 Dec 2019 11:48:30 +1100 Subject: [PATCH 277/602] Read from the process before waiting for exit If you do not read from the Process InputStream is it possible that the process will block if the process buffer is too small to contain the full output. There is no need to call waitFor as reading the InputStream will have the same effect. --- .../io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index 35cc15a7ba009..c4f43fab7b908 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -134,15 +134,15 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeImage = Collections.singletonList(getNativeImageExecutable(graal, java, env).getAbsolutePath()); } - Optional graalVMVersion = Optional.empty(); + final Optional graalVMVersion; try { List versionCommand = new ArrayList<>(nativeImage); versionCommand.add("--version"); Process versionProcess = new ProcessBuilder(versionCommand.toArray(new String[0])) + .redirectErrorStream(true) .start(); - versionProcess.waitFor(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(versionProcess.getInputStream()))) { graalVMVersion = reader.lines().filter((l) -> l.startsWith("GraalVM Version")).findFirst(); } From 25b9f1ff430761ce320371a03b9f1ef36a461920 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Dec 2019 00:30:08 +0200 Subject: [PATCH 278/602] Use same kotlin version everywhere --- bom/runtime/pom.xml | 5 +++++ build-parent/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- .../src/test/resources/projects/classic-kotlin/pom.xml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 307fcb41c1d64..f85ec56a021aa 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -2261,6 +2261,11 @@ kotlin-compiler-embeddable ${kotlin.version} + + org.jetbrains.kotlin + kotlin-compiler + ${kotlin.version} + org.jetbrains.kotlin kotlin-stdlib-jdk8 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 91d4fb175f394..73892e02b9cbe 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -23,7 +23,7 @@ 3.8.1 - 1.3.21 + 1.3.41 2.12.8 4.1.1 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 853a4455d0f40..ba29154062395 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -32,7 +32,7 @@ 3.8.1 - 1.3.21 + 1.3.41 2.12.8 4.1.1 diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml index cfeb8ed2c6dd2..48c5019e568de 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/pom.xml @@ -12,7 +12,7 @@ 1.8 UTF-8 1.8 - 1.3.21 + 1.3.41 From 222845c523018e02e714125feebdada2908d66cc Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Dec 2019 00:30:54 +0200 Subject: [PATCH 279/602] Properly pass Kotlin compiler plugin options to dev-mode Fixes: #2811 --- bom/runtime/pom.xml | 5 -- .../io/quarkus/dev/ClassLoaderCompiler.java | 4 +- .../io/quarkus/dev/CompilationProvider.java | 16 +++++- .../java/io/quarkus/dev/DevModeContext.java | 20 ++++++- .../main/java/io/quarkus/maven/DevMojo.java | 54 +++++++++++++++++++ extensions/kotlin/deployment/pom.xml | 2 +- .../deployment/KotlinCompilationProvider.java | 35 +++++++++++- .../kotlin/maven/it/KotlinDevModeIT.java | 17 ++++-- .../main/kotlin/org/acme/GreetingService.kt | 9 ++++ .../src/main/kotlin/org/acme/HelloResource.kt | 7 ++- 10 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/GreetingService.kt diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index f85ec56a021aa..aeb504afbb2ab 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -2256,11 +2256,6 @@ ${smallrye-converter-api.version} - - org.jetbrains.kotlin - kotlin-compiler-embeddable - ${kotlin.version} - org.jetbrains.kotlin kotlin-compiler diff --git a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java b/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java index 47812a78b586d..e8896a53e5935 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java +++ b/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java @@ -166,7 +166,9 @@ public ClassLoaderCompiler(ClassLoader classLoader, context.getSourceEncoding(), context.getCompilerOptions(), context.getSourceJavaVersion(), - context.getTargetJvmVersion())); + context.getTargetJvmVersion(), + context.getCompilerPluginArtifacts(), + context.getCompilerPluginsOptions())); }); } } diff --git a/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java b/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java index 864d01b938b2a..253494593b6e8 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java +++ b/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java @@ -32,6 +32,8 @@ class Context { private final List compilerOptions; private final String sourceJavaVersion; private final String targetJvmVersion; + private final List compilePluginArtifacts; + private final List compilerPluginOptions; public Context( String name, @@ -42,7 +44,9 @@ public Context( String sourceEncoding, List compilerOptions, String sourceJavaVersion, - String targetJvmVersion) { + String targetJvmVersion, + List compilePluginArtifacts, + List compilerPluginOptions) { this.name = name; this.classpath = classpath; @@ -53,6 +57,8 @@ public Context( this.compilerOptions = compilerOptions == null ? new ArrayList() : compilerOptions; this.sourceJavaVersion = sourceJavaVersion; this.targetJvmVersion = targetJvmVersion; + this.compilePluginArtifacts = compilePluginArtifacts; + this.compilerPluginOptions = compilerPluginOptions; } public String getName() { @@ -90,5 +96,13 @@ public String getSourceJavaVersion() { public String getTargetJvmVersion() { return targetJvmVersion; } + + public List getCompilePluginArtifacts() { + return compilePluginArtifacts; + } + + public List getCompilerPluginOptions() { + return compilerPluginOptions; + } } } diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java index 31f56a5b19ea1..ba3f3d19da602 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java @@ -36,6 +36,9 @@ public class DevModeContext implements Serializable { private String sourceJavaVersion; private String targetJvmVersion; + private List compilerPluginArtifacts; + private List compilerPluginsOptions; + public List getClassPath() { return classPath; } @@ -120,6 +123,22 @@ public void setTargetJvmVersion(String targetJvmVersion) { this.targetJvmVersion = targetJvmVersion; } + public List getCompilerPluginArtifacts() { + return compilerPluginArtifacts; + } + + public void setCompilerPluginArtifacts(List compilerPluginArtifacts) { + this.compilerPluginArtifacts = compilerPluginArtifacts; + } + + public List getCompilerPluginsOptions() { + return compilerPluginsOptions; + } + + public void setCompilerPluginsOptions(List compilerPluginsOptions) { + this.compilerPluginsOptions = compilerPluginsOptions; + } + public File getDevModeRunnerJarFile() { return devModeRunnerJarFile; } @@ -173,5 +192,4 @@ public String getResourcePath() { return resourcePath; } } - } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index f915e64dd7242..ffd53fcdbda0f 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -31,7 +31,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -180,6 +184,9 @@ public class DevMojo extends AbstractMojo { @Component private RepositorySystem repoSystem; + @Component + protected org.apache.maven.repository.RepositorySystem system; + @Component private Invoker invoker; @@ -429,6 +436,7 @@ private void addToClassPaths(StringBuilder classPathManifest, DevModeContext cla class DevModeRunner { + private static final String KOTLIN_MAVEN_PLUGIN_GA = "org.jetbrains.kotlin:kotlin-maven-plugin"; private final List args; private Process process; private Set pomFiles = new HashSet<>(); @@ -512,6 +520,8 @@ void prepare() throws Exception { } } + setKotlinSpecificFlags(devModeContext); + final AppModel appModel; try { final LocalProject localProject; @@ -638,6 +648,50 @@ void prepare() throws Exception { } + private void setKotlinSpecificFlags(DevModeContext devModeContext) { + Plugin kotlinMavenPlugin = null; + for (Plugin plugin : project.getBuildPlugins()) { + if (plugin.getKey().equals(KOTLIN_MAVEN_PLUGIN_GA)) { + kotlinMavenPlugin = plugin; + break; + } + } + + if (kotlinMavenPlugin == null) { + return; + } + + getLog().debug("Kotlin Maven plugin detected"); + + List compilerPluginArtifacts = new ArrayList<>(); + List dependencies = kotlinMavenPlugin.getDependencies(); + for (Dependency dependency : dependencies) { + // TODO do we actually need this to resolve the artifact or can we use the repoSystem? + Artifact artifact = system.createDependencyArtifact(dependency); + ArtifactResolutionResult resolved = system + .resolve(new ArtifactResolutionRequest().setArtifact(artifact)); + + if (resolved.getArtifacts().size() != 1) { + getLog().debug( + "Kotlin compiler plugin " + dependency.getArtifactId() + + " won't be configured for dev-mode because it wasn't configured properly in pom.xml"); + } + Artifact resolvedArtifact = resolved.getArtifacts().iterator().next(); + compilerPluginArtifacts.add(resolvedArtifact.getFile().toPath().toAbsolutePath().toString()); + } + devModeContext.setCompilerPluginArtifacts(compilerPluginArtifacts); + + List options = new ArrayList<>(); + Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) kotlinMavenPlugin.getConfiguration(); + if (compilerPluginConfiguration != null) { + Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.getChild("pluginOptions"); + for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { + options.add(argConfiguration.getValue()); + } + } + devModeContext.setCompilerPluginsOptions(options); + } + public Set getPomFiles() { return pomFiles; } diff --git a/extensions/kotlin/deployment/pom.xml b/extensions/kotlin/deployment/pom.xml index 52d5f703acf46..67622c54c0030 100644 --- a/extensions/kotlin/deployment/pom.xml +++ b/extensions/kotlin/deployment/pom.xml @@ -31,7 +31,7 @@ org.jetbrains.kotlin - kotlin-compiler-embeddable + kotlin-compiler diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java index 99b26a6b486f7..da22bd142f158 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java @@ -6,8 +6,11 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.jboss.logging.Logger; import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation; @@ -20,6 +23,12 @@ public class KotlinCompilationProvider implements CompilationProvider { + private static final Logger log = Logger.getLogger(KotlinCompilationProvider.class); + + // see: https://github.com/JetBrains/kotlin/blob/v1.3.41/libraries/tools/kotlin-maven-plugin/src/main/java/org/jetbrains/kotlin/maven/KotlinCompileMojoBase.java#L192 + private final static Pattern OPTION_PATTERN = Pattern.compile("([^:]+):([^=]+)=(.*)"); + private static final String KOTLIN_PACKAGE = "org.jetbrains.kotlin"; + @Override public Set handledExtensions() { return Collections.singleton(".kt"); @@ -28,6 +37,30 @@ public Set handledExtensions() { @Override public void compile(Set filesToCompile, Context context) { K2JVMCompilerArguments compilerArguments = new K2JVMCompilerArguments(); + if (!context.getCompilePluginArtifacts().isEmpty()) { + compilerArguments.setPluginClasspaths(context.getCompilePluginArtifacts().toArray(new String[0])); + } + if (!context.getCompilerPluginOptions().isEmpty()) { + List sanitizedOptions = new ArrayList<>(context.getCompilerOptions().size()); + for (String rawOption : context.getCompilerPluginOptions()) { + Matcher matcher = OPTION_PATTERN.matcher(rawOption); + if (!matcher.matches()) { + log.warn("Kotlin compiler plugin option " + rawOption + " is invalid"); + } + + String pluginId = matcher.group(1); + if (!pluginId.contains(".")) { + // convert the plugin name to the plugin id by simply removing the dash and adding the kotlin package + // this seems to be the appropriate way of doing things for the plugins that were checked + pluginId = KOTLIN_PACKAGE + "." + pluginId.replace("-", ""); + } + String key = matcher.group(2); + String value = matcher.group(3); + sanitizedOptions.add("plugin:" + pluginId + ":" + key + "=" + value); + + compilerArguments.setPluginOptions(sanitizedOptions.toArray(new String[0])); + } + } compilerArguments.setClasspath( context.getClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator))); compilerArguments.setDestination(context.getOutputDirectory().getAbsolutePath()); @@ -44,7 +77,7 @@ public void compile(Set filesToCompile, Context context) { } if (messageCollector.hasErrors()) { - throw new RuntimeException("Compilation failed" + String.join("\n", messageCollector.getErrors())); + throw new RuntimeException("Compilation failed. " + String.join("\n", messageCollector.getErrors())); } } diff --git a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinDevModeIT.java b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinDevModeIT.java index 552d89f31ae73..e59952b847fb6 100644 --- a/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinDevModeIT.java +++ b/integration-tests/kotlin/src/test/java/io/quarkus/kotlin/maven/it/KotlinDevModeIT.java @@ -22,9 +22,9 @@ public void testThatTheApplicationIsReloadedOnKotlinChange() throws MavenInvocat runAndCheck(); // Edit the "Hello" message. - File source = new File(testDir, "src/main/kotlin/org/acme/HelloResource.kt"); + File jaxRsResource = new File(testDir, "src/main/kotlin/org/acme/HelloResource.kt"); String uuid = UUID.randomUUID().toString(); - filter(source, ImmutableMap.of("return \"hello\"", "return \"" + uuid + "\"")); + filter(jaxRsResource, ImmutableMap.of("return \"hello\"", "return \"" + uuid + "\"")); // Wait until we get "uuid" await() @@ -34,13 +34,22 @@ public void testThatTheApplicationIsReloadedOnKotlinChange() throws MavenInvocat await() .pollDelay(1, TimeUnit.SECONDS) .pollInterval(1, TimeUnit.SECONDS) - .until(source::isFile); + .until(jaxRsResource::isFile); - filter(source, ImmutableMap.of(uuid, "carambar")); + filter(jaxRsResource, ImmutableMap.of(uuid, "carambar")); // Wait until we get "carambar" await() .pollDelay(1, TimeUnit.SECONDS) .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("carambar")); + + File greetingService = new File(testDir, "src/main/kotlin/org/acme/GreetingService.kt"); + String newUuid = UUID.randomUUID().toString(); + filter(greetingService, ImmutableMap.of("\"hello\"", "\"" + newUuid + "\"")); + + // Wait until we get "newUuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello/bean").contains(newUuid)); } } diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/GreetingService.kt b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/GreetingService.kt new file mode 100644 index 0000000000000..d2ef3f867a58f --- /dev/null +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/GreetingService.kt @@ -0,0 +1,9 @@ +package org.acme + +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class GreetingService { + + fun greet() = "hello" +} \ No newline at end of file diff --git a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/HelloResource.kt b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/HelloResource.kt index 8a68128d20c44..0f4ca19358ce1 100644 --- a/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/HelloResource.kt +++ b/integration-tests/kotlin/src/test/resources/projects/classic-kotlin/src/main/kotlin/org/acme/HelloResource.kt @@ -9,7 +9,7 @@ import javax.ws.rs.Produces import javax.ws.rs.core.MediaType @Path("/hello") -class HelloResource { +class HelloResource(val greetingService: GreetingService) { @Inject @ConfigProperty(name = "greeting") @@ -27,4 +27,9 @@ class HelloResource { fun greeting(): String? { return greeting } + + @GET + @Path("/bean") + @Produces(MediaType.TEXT_PLAIN) + fun greetingFromBean() = greetingService.greet() } From 575c3de741862c272c0eb9ef9833f21f03c42268 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Dec 2019 10:06:31 +0200 Subject: [PATCH 280/602] Use more consistent way to resolve the Kotlin compiler plugins --- .../main/java/io/quarkus/maven/DevMojo.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index ffd53fcdbda0f..782afab8e0b52 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -31,9 +31,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; -import org.apache.maven.artifact.resolver.ArtifactResolutionResult; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; @@ -54,7 +51,11 @@ import org.codehaus.plexus.util.xml.Xpp3Dom; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.model.AppModel; @@ -184,9 +185,6 @@ public class DevMojo extends AbstractMojo { @Component private RepositorySystem repoSystem; - @Component - protected org.apache.maven.repository.RepositorySystem system; - @Component private Invoker invoker; @@ -666,18 +664,18 @@ private void setKotlinSpecificFlags(DevModeContext devModeContext) { List compilerPluginArtifacts = new ArrayList<>(); List dependencies = kotlinMavenPlugin.getDependencies(); for (Dependency dependency : dependencies) { - // TODO do we actually need this to resolve the artifact or can we use the repoSystem? - Artifact artifact = system.createDependencyArtifact(dependency); - ArtifactResolutionResult resolved = system - .resolve(new ArtifactResolutionRequest().setArtifact(artifact)); - - if (resolved.getArtifacts().size() != 1) { - getLog().debug( - "Kotlin compiler plugin " + dependency.getArtifactId() - + " won't be configured for dev-mode because it wasn't configured properly in pom.xml"); + try { + ArtifactResult resolvedArtifact = repoSystem.resolveArtifact(repoSession, + new ArtifactRequest() + .setArtifact(new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), + dependency.getClassifier(), dependency.getType(), dependency.getVersion())) + .setRepositories(repos)); + + compilerPluginArtifacts.add(resolvedArtifact.getArtifact().getFile().toPath().toAbsolutePath().toString()); + } catch (ArtifactResolutionException e) { + getLog().warn("Unable to properly setup dev-mode for Kotlin", e); + return; } - Artifact resolvedArtifact = resolved.getArtifacts().iterator().next(); - compilerPluginArtifacts.add(resolvedArtifact.getFile().toPath().toAbsolutePath().toString()); } devModeContext.setCompilerPluginArtifacts(compilerPluginArtifacts); From f308f5175797860f93dd2265d0b07ec821726876 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sun, 8 Dec 2019 20:39:13 +0100 Subject: [PATCH 281/602] Initialize ClassFieldAccessorFactory at run time --- .../quarkus/kogito/deployment/KogitoAssetsProcessor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java index 7e031553e4c48..e040ceaba9d44 100644 --- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java +++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java @@ -20,6 +20,7 @@ import org.drools.compiler.commons.jci.compilers.JavaCompilerSettings; import org.drools.compiler.compiler.io.memory.MemoryFileSystem; import org.drools.compiler.kproject.models.KieModuleModelImpl; +import org.drools.core.base.ClassFieldAccessorFactory; import org.drools.modelcompiler.builder.GeneratedFile; import org.drools.modelcompiler.builder.JavaParserCompiler; import org.jboss.jandex.ClassInfo; @@ -53,6 +54,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.index.IndexingUtil; import io.quarkus.runtime.LaunchMode; @@ -134,6 +136,11 @@ public List reflectiveDMNREST() { return result; } + @BuildStep + public RuntimeInitializedClassBuildItem init() { + return new RuntimeInitializedClassBuildItem(ClassFieldAccessorFactory.class.getName()); + } + @BuildStep(loadsApplicationClasses = true) public void generateModel(ArchiveRootBuildItem root, BuildProducer generatedBeans, From a62f19f2cc5d9d0afacb588194b0198b57b09989 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 17:37:08 +0100 Subject: [PATCH 282/602] Improve the datasource health check feedback --- .../agroal/runtime/health/DataSourceHealthCheck.java | 6 +++--- .../src/test/java/io/quarkus/it/main/HealthTestCase.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java index be14cd5829267..fc091785d7098 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/health/DataSourceHealthCheck.java @@ -41,7 +41,7 @@ protected void init() { @Override public HealthCheckResponse call() { - HealthCheckResponseBuilder builder = HealthCheckResponse.named("Database connection(s) health check").up(); + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Database connections health check").up(); for (Map.Entry dataSource : dataSources.entrySet()) { boolean isDefault = DEFAULT_DS.equals(dataSource.getKey()); try (Connection con = dataSource.getValue().getConnection()) { @@ -49,13 +49,13 @@ public HealthCheckResponse call() { if (!valid) { String data = isDefault ? "validation check failed for the default DataSource" : "validation check failed for DataSource '" + dataSource.getKey() + "'"; - String dsName = isDefault ? "quarkus-default-ds" : dataSource.getKey(); + String dsName = isDefault ? "default" : dataSource.getKey(); builder.down().withData(dsName, data); } } catch (SQLException e) { String data = isDefault ? "Unable to execute the validation check for the default DataSource: " : "Unable to execute the validation check for DataSource '" + dataSource.getKey() + "': "; - String dsName = isDefault ? "quarkus-default-ds" : dataSource.getKey(); + String dsName = isDefault ? "default" : dataSource.getKey(); builder.down().withData(dsName, data + e.getMessage()); } } diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java index c22d695dda256..f7d18d6c59c22 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java @@ -28,7 +28,7 @@ public void testHealthCheck() { .header("Content-Type", containsString("charset=UTF-8")) .body("status", is("UP"), "checks.status", containsInAnyOrder("UP"), - "checks.name", containsInAnyOrder("Database connection(s) health check")); + "checks.name", containsInAnyOrder("Database connections health check")); } finally { RestAssured.reset(); } From 99c9b63213f02a61de105ace9887968b2450b8c8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Dec 2019 12:48:28 +0100 Subject: [PATCH 283/602] Experiment with more Dependabot-managed dependencies For now, Dependabot hasn't been too annoying so let's experiment with a bit more managed dependencies. --- .dependabot/config.yml | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index c8fb357d0ec57..72fdacbf205a3 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -21,6 +21,16 @@ update_configs: dependency_name: "org.eclipse.jgit:org.eclipse.jgit" - match: dependency_name: "io.fabric8:kubernetes-client-bom" + - match: + dependency_name: "org.apache.httpcomponents:httpclient" + - match: + dependency_name: "org.apache.httpcomponents:httpasyncclient" + - match: + dependency_name: "org.apache.httpcomponents:httpcore" + - match: + dependency_name: "org.quartz-scheduler:quartz" + - match: + dependency_name: "com.cronutils:cron-utils" # JDBC Drivers - match: dependency_name: "org.postgresql:postgresql" @@ -32,3 +42,49 @@ update_configs: dependency_name: "org.apache.derby:derbyclient" - match: dependency_name: "com.microsoft.sqlserver:mssql-jdbc" + # Kafka + - match: + dependency_name: "org.apache.kafka:kafka-clients" + - match: + dependency_name: "org.apache.kafka:kafka-streams" + - match: + dependency_name: "org.apache.kafka:kafka_2.12" + - match: + dependency_name: "org.apache.zookeeper:zookeeper" + # Debezium + - match: + dependency_name: "io.debezium:debezium-core" + # Kotlin + - match: + dependency_name: "org.jetbrains.kotlin:kotlin-compiler" + - match: + dependency_name: "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + - match: + dependency_name: "org.jetbrains.kotlin:kotlin-maven-plugin" + - match: + dependency_name: "org.jetbrains.kotlin:kotlin-maven-allopen" + # Scala + - match: + dependency_name: "org.scala-lang:scala-reflect" + - match: + dependency_name: "org.scala-lang:scala-library" + - match: + dependency_name: "org.scala-lang:scala-compiler" + - match: + dependency_name: "net.alchim31.maven:scala-maven-plugin" + # Tika + - match: + dependency_name: "org.apache.tika:tika-parsers" + # Test dependencies + - match: + dependency_name: "io.rest-assured:rest-assured" + - match: + dependency_name: "io.rest-assured:json-schema-validator" + - match: + dependency_name: "org.junit:junit-bom" + - match: + dependency_name: "org.assertj:assertj-core" + - match: + dependency_name: "org.testcontainers:testcontainers" + - match: + dependency_name: "org.testcontainers:junit-jupiter" From d60a5d7af385806ac396689fe6b3f08493d336b7 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Mon, 9 Dec 2019 11:10:34 +0100 Subject: [PATCH 284/602] Close connection in flyway tests --- .../test/FlywayExtensionCleanAndMigrateAtStartTest.java | 4 +--- .../quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java index b01daee09f30a..2fa8feabcea2b 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java @@ -38,9 +38,7 @@ public class FlywayExtensionCleanAndMigrateAtStartTest { @DisplayName("Clean and migrate at start correctly") public void testFlywayConfigInjection() throws SQLException { - Connection connection = defaultDataSource.getConnection(); - - try (Statement stat = connection.createStatement()) { + try (Connection connection = defaultDataSource.getConnection(); Statement stat = connection.createStatement()) { try (ResultSet executeQuery = stat.executeQuery("select * from fake_existing_tbl")) { assertFalse(executeQuery.next(), "Table exists but is not empty"); } diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java index ec0dbb9f80703..501d5fe76cfe3 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java @@ -40,9 +40,7 @@ public class FlywayExtensionCleanAtStartTest { @DisplayName("Clean at start correctly") public void testFlywayConfigInjection() throws SQLException { - Connection connection = defaultDataSource.getConnection(); - - try (Statement stat = connection.createStatement()) { + try (Connection connection = defaultDataSource.getConnection(); Statement stat = connection.createStatement()) { try (ResultSet executeQuery = stat.executeQuery("select * from fake_existing_tbl")) { fail("fake_existing_tbl should not exist"); } catch (JdbcSQLException e) { From 713c3584e64fb4d3f9536f94bef8eb5d8b85e8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Thu, 5 Dec 2019 15:21:22 +0100 Subject: [PATCH 285/602] feat: Introduce GELF log handler --- bom/deployment/pom.xml | 5 + bom/runtime/pom.xml | 11 + .../builditem/FeatureBuildItem.java | 1 + docs/src/main/asciidoc/central-logging.adoc | 398 ++++++++++++++++++ docs/src/main/asciidoc/index.adoc | 1 + docs/src/main/asciidoc/logging.adoc | 5 + extensions/logging-gelf/deployment/pom.xml | 45 ++ .../deployment/GelfLogHandlerProcessor.java | 58 +++ extensions/logging-gelf/pom.xml | 21 + extensions/logging-gelf/runtime/pom.xml | 52 +++ .../io/quarkus/logging/gelf/GelfConfig.java | 64 +++ .../logging/gelf/GelfLogHandlerRecorder.java | 31 ++ .../gelf/graal/JulLogEventSubstitution.java | 20 + .../KafkaGelfSenderProviderSubstitution.java | 17 + .../RedisGelfSenderProviderSubstitution.java | 15 + .../resources/META-INF/quarkus-extension.yaml | 11 + extensions/pom.xml | 3 +- integration-tests/logging-gelf/README.md | 101 +++++ integration-tests/logging-gelf/pom.xml | 145 +++++++ .../gelf/it/GelfLogHandlerResource.java | 39 ++ .../src/main/resources/application.properties | 4 + .../logging/gelf/it/GelfLogHandlerIT.java | 8 + .../logging/gelf/it/GelfLogHandlerTest.java | 35 ++ .../src/test/resources/docker-compose-efk.yml | 38 ++ .../src/test/resources/docker-compose-elk.yml | 40 ++ .../test/resources/docker-compose-graylog.yml | 34 ++ .../src/test/resources/fluentd/Dockerfile | 3 + integration-tests/pom.xml | 1 + 28 files changed, 1205 insertions(+), 1 deletion(-) create mode 100644 docs/src/main/asciidoc/central-logging.adoc create mode 100644 extensions/logging-gelf/deployment/pom.xml create mode 100644 extensions/logging-gelf/deployment/src/main/java/io/quarkus/logging/gelf/deployment/GelfLogHandlerProcessor.java create mode 100644 extensions/logging-gelf/pom.xml create mode 100644 extensions/logging-gelf/runtime/pom.xml create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/JulLogEventSubstitution.java create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/KafkaGelfSenderProviderSubstitution.java create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/RedisGelfSenderProviderSubstitution.java create mode 100644 extensions/logging-gelf/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 integration-tests/logging-gelf/README.md create mode 100644 integration-tests/logging-gelf/pom.xml create mode 100644 integration-tests/logging-gelf/src/main/java/io/quarkus/logging/gelf/it/GelfLogHandlerResource.java create mode 100644 integration-tests/logging-gelf/src/main/resources/application.properties create mode 100644 integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerIT.java create mode 100644 integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerTest.java create mode 100644 integration-tests/logging-gelf/src/test/resources/docker-compose-efk.yml create mode 100644 integration-tests/logging-gelf/src/test/resources/docker-compose-elk.yml create mode 100644 integration-tests/logging-gelf/src/test/resources/docker-compose-graylog.yml create mode 100644 integration-tests/logging-gelf/src/test/resources/fluentd/Dockerfile diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index fb78e7f655742..c7526647b8595 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -511,6 +511,11 @@ quarkus-logging-sentry-deployment ${project.version} + + io.quarkus + quarkus-logging-gelf-deployment + ${project.version} + io.quarkus diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index aeb504afbb2ab..6d5a10c327634 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -172,6 +172,7 @@ 4.7.2 1.0.1.Final 8.0.1 + 1.13.0 @@ -726,6 +727,11 @@ quarkus-logging-sentry ${project.version} + + io.quarkus + quarkus-logging-gelf + ${project.version} + @@ -828,6 +834,11 @@ + + biz.paluch.logging + logstash-gelf + ${logstash-gelf.version} + org.apache.commons commons-lang3 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 50ad08daee567..afc54a00a8a52 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -37,6 +37,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String KOTLIN = "kotlin"; public static final String KUBERNETES = "kubernetes"; public static final String KUBERNETES_CLIENT = "kubernetes-client"; + public static final String LOGGING_GELF = "logging-gelf"; public static final String MAILER = "mailer"; public static final String MONGODB_CLIENT = "mongodb-client"; public static final String MONGODB_PANACHE = "mongodb-panache"; diff --git a/docs/src/main/asciidoc/central-logging.adoc b/docs/src/main/asciidoc/central-logging.adoc new file mode 100644 index 0000000000000..2323e89a03017 --- /dev/null +++ b/docs/src/main/asciidoc/central-logging.adoc @@ -0,0 +1,398 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Central log management (Graylog, Logstash, Fluentd) + +include::./attributes.adoc[] +:es-version: 6.8.2 + +This guide explains how you can send your logs to a central logging system like Graylog, Logstash (inside the Elastic Stack or ELK - Elasticsearch, Logstash, Kibana) or +Fluentd (inside EFK - Elasticsearch, Fluentd, Kibana). + +There are a lot of different ways to centralize your logs (if you are using Kubernetes, the simplest way is to log to the console and ask you cluster administrator to integrate a central log manager inside your cluster). +In this guide, we will expose how to send them to an external tool using the `quarkus-logging-gelf` extension that can use TCP or UDP to send logs in the Graylog Extended Log Format (GELF). + +The `quarkus-logging-gelf` extension will add a GELF log handler to the underlying logging backend that Quarkus uses (jboss-logmanager). +By default, it is disabled, if you enable it but still use another handler (by default the console handler is enabled), your logs will be sent to both handlers. + +== Example application + +The following examples will all be based on the same example application that you can create with the following steps: + +- Create an application with the `quarkus-logging-gelf` extension. You can use the following Maven command to create it: + +[source,shell] +---- +mvn io.quarkus:quarkus-maven-plugin:1.0.0.Final:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=gelf-logging \ + -DclassName="org.acme.quickstart.GelfLoggingResource" \ + -Dpath="/gelf-logging" \ + -Dextensions="logging-gelf" +---- + +- For demonstration purpose, we create an endpoint that does nothing but log a sentence. You don't need to do this inside your application. + +[source,java] +---- +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.jboss.logging.Logger; + +@Path("/gelf-logging") +@ApplicationScoped +public class GelfLoggingResource { + private static final Logger LOG = Logger.getLogger(GelfLogHandlerResource.class); + + @GET + public void log() { + LOG.info("Some useful log message"); + } + +} +---- + +- Configure the GELF log handler to send logs to an external UDP endpoint on the port 12201: + +[source,properties] +---- +quarkus.log.handler.gelf.enabled=true +quarkus.log.handler.gelf.host=localhost +quarkus.log.handler.gelf.port=12201 +---- + +== Send logs to Graylog + +To send logs to Graylog, you first need to launch the components that compose the Graylog stack: + +- MongoDB +- Elasticsearch +- Graylog + +You can do this via the following docker-compose file that you can launch via `docker-compose run -d`: + +[source,yaml,subs="attributes"] +---- +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:{es-version} + ports: + - "9200:9200" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - graylog + + mongo: + image: mongo:4.0 + networks: + - graylog + + graylog: + image: graylog/graylog:3.1 + ports: + - "9000:9000" + - "12201:12201/udp" + - "1514:1514" + environment: + GRAYLOG_HTTP_EXTERNAL_URI: "http://127.0.0.1:9000/" + networks: + - graylog + depends_on: + - elasticsearch + - mongo + +networks: + graylog: + driver: bridge +---- + +Then, you need to create a UDP input in Graylog. +You can do it from the Graylog web console (System -> Input -> Select GELF UDP) available at http://localhost:9000 or via the API. + +This curl example will create a new Input of type GELF UDP, it uses the default login from Graylog (admin/admin). + +[source,shell] +---- +curl -H "Content-Type: application/json" -H "Authorization: Basic YWRtaW46YWRtaW4=" -H "X-Requested-By: curl" -X POST -v -d \ +'{"title":"udp input","configuration":{"recv_buffer_size":262144,"bind_address":"0.0.0.0","port":12201,"decompress_size_limit":8388608},"type":"org.graylog2.inputs.gelf.udp.GELFUDPInput","global":true}' \ +http://localhost:9000/api/system/inputs +---- + +Launch your application, you should see your logs arriving inside Graylog. + +== Send logs to Logstash / the Elastic Stack (ELK) + +Logstash comes by default with an Input plugin that can understand the GELF format, we will first create a pipeline that enables this plugin. + +Create the following file in `$HOME/pipelines/gelf.conf`: + +[source] +---- +input { + gelf { + port => 12201 + } +} +output { + stdout {} + elasticsearch { + hosts => ["http://elasticsearch:9200"] + } +} +---- + +Finally, launch the components that compose the Elastic Stack: + +- Elasticsearch +- Logstash +- Kibana + +You can do this via the following docker-compose file that you can launch via `docker-compose run -d`: + +[source,yaml,subs="attributes"] +---- +# Launch Elasticsearch +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:{es-version} + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - elk + + logstash: + image: docker.elastic.co/logstash/logstash-oss:{es-version} + volumes: + - source: $HOME/pipelines + target: /usr/share/logstash/pipeline + type: bind + ports: + - "12201:12201/udp" + - "5000:5000" + - "9600:9600" + networks: + - elk + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana-oss:{es-version} + ports: + - "5601:5601" + networks: + - elk + depends_on: + - elasticsearch + +networks: + elk: + driver: bridge + +---- + +Launch your application, you should see your logs arriving inside the Elastic Stack; you can use Kibana available at http://localhost:5601/ to access them. + +== Send logs to Fluentd (EFK) + +First, you need to create a Fluentd image with the needed plugins: elasticsearch and input-gelf. +You can use the following Dockerfile that should be created inside a `fluentd` directory. + +[source] +---- +FROM fluent/fluentd:v1.3-debian +RUN ["gem", "install", "fluent-plugin-elasticsearch", "--version", "3.7.0"] +RUN ["gem", "install", "fluent-plugin-input-gelf", "--version", "0.3.1"] +---- + +You can build the image or let docker-compose build it for you. + +Then you need to create a fluentd configuration file inside `$HOME/fluentd/fluent.conf` + +[source] +---- + + type gelf + tag example.gelf + bind 0.0.0.0 + port 12201 + + + + @type elasticsearch + host elasticsearch + port 9200 + logstash_format true + +---- + +Finally, launch the components that compose the EFK Stack: + +- Elasticsearch +- Fluentd +- Kibana + +You can do this via the following docker-compose file that you can launch via `docker-compose run -d`: + +[source,yaml,subs="attributes"] +---- +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:{es-version} + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - efk + + fluentd: + build: fluentd + ports: + - "12201:12201/udp" + volumes: + - source: $HOME/fluentd + target: /fluentd/etc + type: bind + networks: + - efk + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana-oss:{es-version} + ports: + - "5601:5601" + networks: + - efk + depends_on: + - elasticsearch + +networks: + efk: + driver: bridge +---- + +Launch your application, you should see your logs arriving inside EFK: you can use Kibana available at http://localhost:5601/ to access them. + +== Fluentd alternative: use Syslog + +You can also send your logs to Fluentd using a Syslog input. +As opposed to the GELF input, the Syslog input will not render multiline logs in one event, that's why we advise to use the GELF input that we implement in Quarkus. + +First, you need to create a Fluentd image with the elasticsearch plugin. +You can use the following Dockerfile that should be created inside a `fluentd` directory. + +[source] +---- +FROM fluent/fluentd:v1.3-debian +RUN ["gem", "install", "fluent-plugin-elasticsearch", "--version", "3.7.0"] +---- + +Then, you need to create a fluentd configuration file inside `$HOME/fluentd/fluent.conf` + +[source] +---- + + @type syslog + port 5140 + bind 0.0.0.0 + message_format rfc5424 + tag system + + + + @type elasticsearch + host elasticsearch + port 9200 + logstash_format true + +---- + +Then, launch the components that compose the EFK Stack: + +- Elasticsearch +- Fluentd +- Kibana + +You can do this via the following docker-compose file that you can launch via `docker-compose run -d`: + +[source,yaml,subs="attributes"] +---- +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:{es-version} + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - efk + + fluentd: + build: fluentd + ports: + - "5140:5140/udp" + volumes: + - source: $HOME/fluentd + target: /fluentd/etc + type: bind + networks: + - efk + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana-oss:{es-version} + ports: + - "5601:5601" + networks: + - efk + depends_on: + - elasticsearch + +networks: + efk: + driver: bridge +---- + +Finally, configure your application to send logs to EFK using Syslog: + +[source,properties] +---- +quarkus.log.syslog.enable=true +quarkus.log.syslog.endpoint=localhost:5140 +quarkus.log.syslog.protocol=udp +quarkus.log.syslog.app-name=quarkus +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. + + +[[configuration-reference]] +== Configuration Reference + +Configuration is done through the usual `application.properties` file. + +include::{generated-dir}/config/quarkus-logging-gelf.adoc[opts=optional, leveloffset=+1] + +This extension uses the `logstash-gelf` library that allow more configuration options via system properties, +you can access its documentation here: https://logging.paluch.biz/ diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index b8e96aa79ebaa..56b6c557bff6c 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -17,6 +17,7 @@ include::quarkus-intro.adoc[tag=intro] * link:tooling.html[Use tooling with Quarkus] * link:config.html[Configuring Your Application] * link:logging.html[Configuring Logging] +* link:central-logging.html[Central log management] * link:lifecycle.html[Application Initialization and Termination] * link:rest-json.html[Writing JSON REST Services] * link:scheduler.html[Schedule Periodic Tasks] diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 9c85f9a152a74..92c8167d0690b 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -106,6 +106,8 @@ The presence of this extension will, by default, replace the output format confi This means that the format string and the color settings (if any) will be ignored. The other console configuration items (including those controlling asynchronous logging and the log level) will continue to be applied. + + ===== Configuration The JSON logging extension can be configured in various ways. The following properties are supported: @@ -166,3 +168,6 @@ Applications and components may use any of the following APIs for logging, and t * https://www.slf4j.org/[SLF4J] * https://commons.apache.org/proper/commons-logging/[Apache Commons Logging] +== Central log management + +If you want to send your logs to a central management tool like Graylog, Logstash or Fluentd; you can follow the link:central-logging.html[Central log management guide]. \ No newline at end of file diff --git a/extensions/logging-gelf/deployment/pom.xml b/extensions/logging-gelf/deployment/pom.xml new file mode 100644 index 0000000000000..193d448ab45db --- /dev/null +++ b/extensions/logging-gelf/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-gelf-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-gelf-deployment + Quarkus - Logging - GELF - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-logging-gelf + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/logging-gelf/deployment/src/main/java/io/quarkus/logging/gelf/deployment/GelfLogHandlerProcessor.java b/extensions/logging-gelf/deployment/src/main/java/io/quarkus/logging/gelf/deployment/GelfLogHandlerProcessor.java new file mode 100644 index 0000000000000..7b3aae12fa0a7 --- /dev/null +++ b/extensions/logging-gelf/deployment/src/main/java/io/quarkus/logging/gelf/deployment/GelfLogHandlerProcessor.java @@ -0,0 +1,58 @@ +package io.quarkus.logging.gelf.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.LogHandlerBuildItem; +import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.logging.gelf.GelfConfig; +import io.quarkus.logging.gelf.GelfLogHandlerRecorder; + +class GelfLogHandlerProcessor { + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.LOGGING_GELF); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + LogHandlerBuildItem build(GelfLogHandlerRecorder recorder, GelfConfig config) { + return new LogHandlerBuildItem(recorder.initializeHandler(config)); + } + + @BuildStep + RuntimeInitializedClassBuildItem nativeBuild() { + return new RuntimeInitializedClassBuildItem( + "biz.paluch.logging.gelf.jboss7.JBoss7GelfLogHandler"); + } + + @BuildStep() + SystemPropertyBuildItem sysProp() { + //FIXME we change the order ot the Hostname resolution for native image + // see https://logging.paluch.biz/hostname-lookup.html + // if not, we have the following error + /* + * java.lang.NullPointerException + * at java.net.InetAddress.getHostFromNameService(InetAddress.java:615) + * at java.net.InetAddress.getCanonicalHostName(InetAddress.java:589) + * at biz.paluch.logging.RuntimeContainer.isQualified(RuntimeContainer.java:20) + * at biz.paluch.logging.RuntimeContainer.getInetAddressWithHostname(RuntimeContainer.java:137) + * at biz.paluch.logging.RuntimeContainer.lookupHostname(RuntimeContainer.java:90) + * at biz.paluch.logging.RuntimeContainer.initialize(RuntimeContainer.java:67) + * at biz.paluch.logging.gelf.jul.GelfLogHandler.(GelfLogHandler.java:54) + * at biz.paluch.logging.gelf.jboss7.JBoss7GelfLogHandler.(JBoss7GelfLogHandler.java:75) + * at io.quarkus.logging.gelf.GelfLogHandlerRecorder.initializeHandler(GelfLogHandlerRecorder.java:17) + * at io.quarkus.deployment.steps.GelfLogHandlerProcessor$build27.deploy_0(GelfLogHandlerProcessor$build27.zig:76) + * at io.quarkus.deployment.steps.GelfLogHandlerProcessor$build27.deploy(GelfLogHandlerProcessor$build27.zig:36) + * at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:68) + * at io.quarkus.runtime.Application.start(Application.java:87) + * at io.quarkus.runtime.Application.run(Application.java:210) + * at io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:41) + */ + return new SystemPropertyBuildItem("logstash-gelf.resolutionOrder", "localhost,network"); + } + +} diff --git a/extensions/logging-gelf/pom.xml b/extensions/logging-gelf/pom.xml new file mode 100644 index 0000000000000..b25cb1a3ba762 --- /dev/null +++ b/extensions/logging-gelf/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + io.quarkus + quarkus-extensions-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-gelf-parent + Quarkus - Logging - GELF + + pom + + deployment + runtime + + diff --git a/extensions/logging-gelf/runtime/pom.xml b/extensions/logging-gelf/runtime/pom.xml new file mode 100644 index 0000000000000..d6a5663e7c803 --- /dev/null +++ b/extensions/logging-gelf/runtime/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-gelf-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-gelf + Quarkus - Logging - GELF - Runtime + + + + biz.paluch.logging + logstash-gelf + + + org.graalvm.nativeimage + svm + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java new file mode 100644 index 0000000000000..c5690a6c32c7b --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java @@ -0,0 +1,64 @@ +package io.quarkus.logging.gelf; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "log.handler.gelf") +public class GelfConfig { + /** + * Determine whether to enable the GELF logging handler + */ + @ConfigItem + boolean enabled; + + /** + * Hostname/IP-Address of the Logstash/Graylog Host + * By default it uses UDP, prepend tcp: to the hostname to switch to TCP, example: "tcp:localhost" + */ + @ConfigItem(defaultValue = "localhost") + public String host; + + /** + * The port + */ + @ConfigItem(defaultValue = "12201") + public int port; + + /** + * GELF version: 1.0 or 1.1 + */ + @ConfigItem(defaultValue = "1.1") + public String version; + + /** + * Whether to post Stack-Trace to StackTrace field. + * + * @see #stackTraceThrowableReference to customize the way the Stack-Trace is handled. + */ + @ConfigItem(defaultValue = "true") + public boolean extractStackTrace; + + /** + * Only used when `extractStackTrace` is `true`. + * A value of 0 will extract the whole stack trace. + * Any positive value will walk the cause chain: 1 corresponds with exception.getCause(), + * 2 with exception.getCause().getCause(), ... + * Negative throwable reference walk the exception chain from the root cause side: -1 will extract the root cause, + * -2 the exception wrapping the root cause, ... + */ + @ConfigItem(defaultValue = "0") + public int stackTraceThrowableReference; + + /** + * Whether to perform Stack-Trace filtering + */ + @ConfigItem + public boolean filterStackTrace; + + /** + * Java date pattern, see {@link java.text.SimpleDateFormat} + */ + @ConfigItem(defaultValue = "yyyy-MM-dd HH:mm:ss,SSS") + public String timestampPattern; +} diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java new file mode 100644 index 0000000000000..f309f52dc7865 --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java @@ -0,0 +1,31 @@ +package io.quarkus.logging.gelf; + +import java.util.Optional; +import java.util.logging.Handler; + +import biz.paluch.logging.gelf.jboss7.JBoss7GelfLogHandler; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class GelfLogHandlerRecorder { + public RuntimeValue> initializeHandler(final GelfConfig config) { + if (!config.enabled) { + return new RuntimeValue<>(Optional.empty()); + } + + final JBoss7GelfLogHandler handler = new JBoss7GelfLogHandler(); + handler.setVersion(config.version); + handler.setFacility("jboss-logmanager"); + String extractStackTrace = String.valueOf(config.extractStackTrace); + if (config.extractStackTrace && config.stackTraceThrowableReference != 0) { + extractStackTrace = String.valueOf(config.stackTraceThrowableReference); + } + handler.setExtractStackTrace(extractStackTrace); + handler.setFilterStackTrace(config.filterStackTrace); + handler.setTimestampPattern(config.timestampPattern); + handler.setHost(config.host); + handler.setPort(config.port); + return new RuntimeValue<>(Optional.of(handler)); + } +} diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/JulLogEventSubstitution.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/JulLogEventSubstitution.java new file mode 100644 index 0000000000000..3ecc4009a7265 --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/JulLogEventSubstitution.java @@ -0,0 +1,20 @@ +package io.quarkus.logging.gelf.graal; + +import java.util.logging.LogRecord; + +import org.jboss.logmanager.ExtLogRecord; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "biz.paluch.logging.gelf.jul.JulLogEvent") +public final class JulLogEventSubstitution { + + @Substitute + private String getThreadName(LogRecord record) { + // This is a temporary substitution to avoid using JMX to retrieve the name of the thread + // see https://github.com/mp911de/logstash-gelf/pull/214 and https://github.com/mp911de/logstash-gelf/pull/217 + + return ((ExtLogRecord) record).getThreadName(); + } +} diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/KafkaGelfSenderProviderSubstitution.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/KafkaGelfSenderProviderSubstitution.java new file mode 100644 index 0000000000000..ad0f94e6ee8e1 --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/KafkaGelfSenderProviderSubstitution.java @@ -0,0 +1,17 @@ +package io.quarkus.logging.gelf.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import biz.paluch.logging.gelf.intern.GelfSender; +import biz.paluch.logging.gelf.intern.GelfSenderConfiguration; + +@TargetClass(className = "biz.paluch.logging.gelf.intern.sender.KafkaGelfSenderProvider") +public final class KafkaGelfSenderProviderSubstitution { + + @Substitute + public GelfSender create(GelfSenderConfiguration configuration) { + return null; + } + +} diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/RedisGelfSenderProviderSubstitution.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/RedisGelfSenderProviderSubstitution.java new file mode 100644 index 0000000000000..be9cbd5d7cbd9 --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/graal/RedisGelfSenderProviderSubstitution.java @@ -0,0 +1,15 @@ +package io.quarkus.logging.gelf.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import biz.paluch.logging.gelf.intern.GelfSender; +import biz.paluch.logging.gelf.intern.GelfSenderConfiguration; + +@TargetClass(className = "biz.paluch.logging.gelf.intern.sender.RedisGelfSenderProvider") +public final class RedisGelfSenderProviderSubstitution { + @Substitute + public GelfSender create(GelfSenderConfiguration configuration) { + return null; + } +} diff --git a/extensions/logging-gelf/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/logging-gelf/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..88a8df6671f4e --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "Logging GELF" +metadata: + keywords: + - "logging" + - "gelf" + - "handler" + guide: "https://quarkus.io/guides/central-logging" + categories: + - "core" + status: "preview" diff --git a/extensions/pom.xml b/extensions/pom.xml index 70ca39af15faa..bf78ad80e2d89 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -137,7 +137,8 @@ logging-json logging-sentry - + logging-gelf + qute diff --git a/integration-tests/logging-gelf/README.md b/integration-tests/logging-gelf/README.md new file mode 100644 index 0000000000000..37a640d26c8b7 --- /dev/null +++ b/integration-tests/logging-gelf/README.md @@ -0,0 +1,101 @@ +# JAX-RS example using Graylog central log management + +## Running the tests + +By default, the tests of this module are disabled. + +To run them, you first need to start a Graylog server and it's needed dependencies. + +The following commands will launch MongoDB, Elasticsearch and Graylog using Docker Compose then create a UDP input. + +``` +# Launch Graylog and it's components (MongoDB and Elasticsearch) +docker-compose -f src/test/resources/docker-compose-graylog.yml up + +# Create a GELF UDP input +curl -H "Content-Type: application/json" -H "Authorization: Basic YWRtaW46YWRtaW4=" -H "X-Requested-By: curl" -X POST -v -d \ +'{"title":"udp input","configuration":{"recv_buffer_size":262144,"bind_address":"0.0.0.0","port":12201,"decompress_size_limit":8388608},"type":"org.graylog2.inputs.gelf.udp.GELFUDPInput","global":true}' \ +http://localhost:9000/api/system/inputs +``` + +When everything is launched and ready, you can run the tests in a standard JVM with the following command: + +``` +mvn clean test -Dtest-gelf +``` + +Additionally, you can generate a native image and run the tests for this native image by adding `-Dnative`: + +``` +mvn clean integration-test -Dtest-gelf -Dnative +``` + +## Testing with ELK (Elasticsearch, Logstash, Kibana) aka the Elastic Stack + +You can also run the test with an ELK cluster, in this case, the test will fail as it will try to access Graylog to validate +that the events are correctly sent. So you should assert yourself that it works. + +First, you need to create a Logstash pipeline file with a GELF input, we will put it inside ` $HOME/pipelines/gelf.conf` + +``` +input { + gelf { + port => 12201 + } +} +output { + stdout {} + elasticsearch { + hosts => ["http://elasticsearch:9200"] + } +} + +``` + +Then you can use the following commands to run an ELK cluster using the provided docker compose: + +``` +# Launch ELK (Elasticsearch, Logstash, Kibana) +docker-compose -f src/test/resources/docker-compose-elk.yml up +``` + +Finally, run the test via `mvn clean install -Dtest-gelf -Dmaven.test.failure.ignore` and manually verify that the log +events has been pushed to ELK. You can use Kibana on http://localhost:5601/ to access those logs. + + +## Testing with EFK (Elasticsearch, Fluentd, Kibana) + +You can also run the test with an EFK cluster, in this case, the test will fail as it will try to access Graylog to validate +that the events are correctly sent. So you should assert yourself that it works. + +First, you need to create a Fluentd image with the needed plugins (`fluent-plugin-elasticsearch` and `fluent-plugin-input-gelf`). +There is a Dockerfile inside `src/test/resources/fluentd` that can be used to create such image. +The `docker-compose-efk.yml` file used in this section uses this file to create the Fluentd container. + +Then, you need to create a configuration file that will defines an input of GELF UDP and an output to Elasticsearch + +``` + + type gelf + tag example.gelf + bind 0.0.0.0 + port 12201 + + + + @type elasticsearch + host elasticsearch + port 9200 + logstash_format true + +``` + +Then you can use the following commands to run an EFK cluster using the provided docker compose: + +``` +# Launch EFK (Elasticsearch, Fluentd, Kibana) +docker-compose -f src/test/resources/docker-compose-efk.yml up +``` + +Finally, run the test via `mvn clean install -Dtest-gelf -Dmaven.test.failure.ignore` and manually verify that the log +events has been pushed to EFK. You can use Kibana on http://localhost:5601/ to access those logs. \ No newline at end of file diff --git a/integration-tests/logging-gelf/pom.xml b/integration-tests/logging-gelf/pom.xml new file mode 100644 index 0000000000000..4ea67f8a40b1f --- /dev/null +++ b/integration-tests/logging-gelf/pom.xml @@ -0,0 +1,145 @@ + + + 4.0.0 + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-gelf-integration-test + Quarkus - Logging - GELF - Integration Test + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-logging-gelf + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-gelf + + + test-gelf + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + false + true + true + false + false + ${graalvmHome} + true + true + + + + + + + + + + diff --git a/integration-tests/logging-gelf/src/main/java/io/quarkus/logging/gelf/it/GelfLogHandlerResource.java b/integration-tests/logging-gelf/src/main/java/io/quarkus/logging/gelf/it/GelfLogHandlerResource.java new file mode 100644 index 0000000000000..4e7ac9127e207 --- /dev/null +++ b/integration-tests/logging-gelf/src/main/java/io/quarkus/logging/gelf/it/GelfLogHandlerResource.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.quarkus.logging.gelf.it; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.jboss.logging.Logger; + +/** + * This endpoint allow to test central logging solution by generating a log event when + * calling it's `log` operation. + */ +@Path("/gelf-log-handler") +@ApplicationScoped +public class GelfLogHandlerResource { + private static final Logger LOG = Logger.getLogger(GelfLogHandlerResource.class); + + @GET + public void log() { + LOG.info("Some useful log message"); + } + +} diff --git a/integration-tests/logging-gelf/src/main/resources/application.properties b/integration-tests/logging-gelf/src/main/resources/application.properties new file mode 100644 index 0000000000000..1a3bf92b4b2d5 --- /dev/null +++ b/integration-tests/logging-gelf/src/main/resources/application.properties @@ -0,0 +1,4 @@ +# Configure loggers +quarkus.log.handler.gelf.enabled=true +quarkus.log.handler.gelf.host=localhost +quarkus.log.handler.gelf.port=12201 \ No newline at end of file diff --git a/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerIT.java b/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerIT.java new file mode 100644 index 0000000000000..b234d189b7196 --- /dev/null +++ b/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerIT.java @@ -0,0 +1,8 @@ +package io.quarkus.logging.gelf.it; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class GelfLogHandlerIT extends GelfLogHandlerTest { + +} diff --git a/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerTest.java b/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerTest.java new file mode 100644 index 0000000000000..225ab4785357d --- /dev/null +++ b/integration-tests/logging-gelf/src/test/java/io/quarkus/logging/gelf/it/GelfLogHandlerTest.java @@ -0,0 +1,35 @@ +package io.quarkus.logging.gelf.it; + +import static org.hamcrest.Matchers.hasItem; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * This test is disabled by default as it needs a central log management system up and running to be able to be launched. + * Check the README.md, it contains info of how to launch one prior to this test. + * + * This test is designed to be launched with Graylog as the central management solution as the RestAssured assertion + * check that a log events is received using the Graylog search API. Launching the test with another solution will + * fail the test. + */ +@QuarkusTest +public class GelfLogHandlerTest { + + @Test + public void test() throws InterruptedException { + RestAssured.given().when().get("/gelf-log-handler").then().statusCode(204); + + //wait two seconds for the log events to be processed + Thread.sleep(2000); + + RestAssured.given() + .when() + .auth().basic("admin", "admin") + .get("http://127.0.0.1:9000/api/search/universal/relative?query=") + .then().statusCode(200) + .body("messages.message.message", hasItem("Some useful log message")); + } +} diff --git a/integration-tests/logging-gelf/src/test/resources/docker-compose-efk.yml b/integration-tests/logging-gelf/src/test/resources/docker-compose-efk.yml new file mode 100644 index 0000000000000..fc7ef94e63ae8 --- /dev/null +++ b/integration-tests/logging-gelf/src/test/resources/docker-compose-efk.yml @@ -0,0 +1,38 @@ +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.2 + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - efk + + fluentd: + build: fluentd + ports: + - "12201:12201/udp" + volumes: + - source: $HOME/fluentd + target: /fluentd/etc + type: bind + networks: + - efk + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana-oss:6.8.2 + ports: + - "5601:5601" + networks: + - efk + depends_on: + - elasticsearch + +networks: + efk: + driver: bridge \ No newline at end of file diff --git a/integration-tests/logging-gelf/src/test/resources/docker-compose-elk.yml b/integration-tests/logging-gelf/src/test/resources/docker-compose-elk.yml new file mode 100644 index 0000000000000..7d2257c457565 --- /dev/null +++ b/integration-tests/logging-gelf/src/test/resources/docker-compose-elk.yml @@ -0,0 +1,40 @@ +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.2 + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - elk + + logstash: + image: docker.elastic.co/logstash/logstash-oss:6.8.2 + volumes: + - source: $HOME/pipelines + target: /usr/share/logstash/pipeline + type: bind + ports: + - "12201:12201/udp" + - "5000:5000" + - "9600:9600" + networks: + - elk + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana-oss:6.8.2 + ports: + - "5601:5601" + networks: + - elk + depends_on: + - elasticsearch + +networks: + elk: + driver: bridge diff --git a/integration-tests/logging-gelf/src/test/resources/docker-compose-graylog.yml b/integration-tests/logging-gelf/src/test/resources/docker-compose-graylog.yml new file mode 100644 index 0000000000000..44dd0b6dfac4c --- /dev/null +++ b/integration-tests/logging-gelf/src/test/resources/docker-compose-graylog.yml @@ -0,0 +1,34 @@ +version: '3.2' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.2 + ports: + - "9200:9200" + environment: + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + networks: + - graylog + + mongo: + image: mongo:4.0 + networks: + - graylog + + graylog: + image: graylog/graylog:3.1 + ports: + - "9000:9000" + - "12201:12201/udp" + - "1514:1514" + environment: + GRAYLOG_HTTP_EXTERNAL_URI: "http://127.0.0.1:9000/" + networks: + - graylog + depends_on: + - elasticsearch + - mongo + +networks: + graylog: + driver: bridge \ No newline at end of file diff --git a/integration-tests/logging-gelf/src/test/resources/fluentd/Dockerfile b/integration-tests/logging-gelf/src/test/resources/fluentd/Dockerfile new file mode 100644 index 0000000000000..38de2774f9ef4 --- /dev/null +++ b/integration-tests/logging-gelf/src/test/resources/fluentd/Dockerfile @@ -0,0 +1,3 @@ +FROM fluent/fluentd:v1.3-debian +RUN ["gem", "install", "fluent-plugin-elasticsearch", "--version", "3.7.0"] +RUN ["gem", "install", "fluent-plugin-input-gelf", "--version", "0.3.1"] diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 508f7d6d3fe9c..19ae199ca53fd 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -88,6 +88,7 @@ vertx-graphql jpa-without-entity quartz + logging-gelf From 8d853ff9b617a3a0d02208e2b39520f17d15aab3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Dec 2019 13:47:55 +0100 Subject: [PATCH 286/602] Some minor adjustments on GELF logging documentation --- ...-logging.adoc => centralized-log-management.adoc} | 12 ++++++------ docs/src/main/asciidoc/logging.adoc | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) rename docs/src/main/asciidoc/{central-logging.adoc => centralized-log-management.adoc} (95%) diff --git a/docs/src/main/asciidoc/central-logging.adoc b/docs/src/main/asciidoc/centralized-log-management.adoc similarity index 95% rename from docs/src/main/asciidoc/central-logging.adoc rename to docs/src/main/asciidoc/centralized-log-management.adoc index 2323e89a03017..9f2c3ff615c8d 100644 --- a/docs/src/main/asciidoc/central-logging.adoc +++ b/docs/src/main/asciidoc/centralized-log-management.adoc @@ -3,12 +3,12 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// -= Quarkus - Central log management (Graylog, Logstash, Fluentd) += Quarkus - Centralized log management (Graylog, Logstash, Fluentd) include::./attributes.adoc[] :es-version: 6.8.2 -This guide explains how you can send your logs to a central logging system like Graylog, Logstash (inside the Elastic Stack or ELK - Elasticsearch, Logstash, Kibana) or +This guide explains how you can send your logs to a centralized log management system like Graylog, Logstash (inside the Elastic Stack or ELK - Elasticsearch, Logstash, Kibana) or Fluentd (inside EFK - Elasticsearch, Fluentd, Kibana). There are a lot of different ways to centralize your logs (if you are using Kubernetes, the simplest way is to log to the console and ask you cluster administrator to integrate a central log manager inside your cluster). @@ -23,9 +23,9 @@ The following examples will all be based on the same example application that yo - Create an application with the `quarkus-logging-gelf` extension. You can use the following Maven command to create it: -[source,shell] +[source,shell,subs=attributes+] ---- -mvn io.quarkus:quarkus-maven-plugin:1.0.0.Final:create \ +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=gelf-logging \ -DclassName="org.acme.quickstart.GelfLoggingResource" \ @@ -33,7 +33,7 @@ mvn io.quarkus:quarkus-maven-plugin:1.0.0.Final:create \ -Dextensions="logging-gelf" ---- -- For demonstration purpose, we create an endpoint that does nothing but log a sentence. You don't need to do this inside your application. +- For demonstration purposes, we create an endpoint that does nothing but log a sentence. You don't need to do this inside your application. [source,java] ---- @@ -395,4 +395,4 @@ Configuration is done through the usual `application.properties` file. include::{generated-dir}/config/quarkus-logging-gelf.adoc[opts=optional, leveloffset=+1] This extension uses the `logstash-gelf` library that allow more configuration options via system properties, -you can access its documentation here: https://logging.paluch.biz/ +you can access its documentation here: https://logging.paluch.biz/ . diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 92c8167d0690b..6e9eaf319769f 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -106,8 +106,6 @@ The presence of this extension will, by default, replace the output format confi This means that the format string and the color settings (if any) will be ignored. The other console configuration items (including those controlling asynchronous logging and the log level) will continue to be applied. - - ===== Configuration The JSON logging extension can be configured in various ways. The following properties are supported: @@ -168,6 +166,6 @@ Applications and components may use any of the following APIs for logging, and t * https://www.slf4j.org/[SLF4J] * https://commons.apache.org/proper/commons-logging/[Apache Commons Logging] -== Central log management +== Centralized log management -If you want to send your logs to a central management tool like Graylog, Logstash or Fluentd; you can follow the link:central-logging.html[Central log management guide]. \ No newline at end of file +If you want to send your logs to a centralized tool like Graylog, Logstash or Fluentd, you can follow the link:centralized-log-management[Centralized log management guide]. From a6e282e8d8abc88b427ec8bc3544b23d9fdfebfd Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Dec 2019 14:37:02 +0200 Subject: [PATCH 287/602] Prevent possible NPE in KotlinCompilationProvider This could happen in Gradle because we don't yet have support for populating the relevant data --- .../quarkus/kotlin/deployment/KotlinCompilationProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java index da22bd142f158..b727268450aa6 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java @@ -37,10 +37,10 @@ public Set handledExtensions() { @Override public void compile(Set filesToCompile, Context context) { K2JVMCompilerArguments compilerArguments = new K2JVMCompilerArguments(); - if (!context.getCompilePluginArtifacts().isEmpty()) { + if (context.getCompilePluginArtifacts() != null && !context.getCompilePluginArtifacts().isEmpty()) { compilerArguments.setPluginClasspaths(context.getCompilePluginArtifacts().toArray(new String[0])); } - if (!context.getCompilerPluginOptions().isEmpty()) { + if (context.getCompilerPluginOptions() != null && !context.getCompilerPluginOptions().isEmpty()) { List sanitizedOptions = new ArrayList<>(context.getCompilerOptions().size()); for (String rawOption : context.getCompilerPluginOptions()) { Matcher matcher = OPTION_PATTERN.matcher(rawOption); From 09d09222673a62ac299246c20eaa16da2e3e962e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Dec 2019 17:39:49 +0100 Subject: [PATCH 288/602] Make recently added integration tests pom consistent with the rest --- integration-tests/logging-gelf/pom.xml | 4 ++-- integration-tests/vertx-graphql/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/logging-gelf/pom.xml b/integration-tests/logging-gelf/pom.xml index 4ea67f8a40b1f..c6dcaa9b31ba5 100644 --- a/integration-tests/logging-gelf/pom.xml +++ b/integration-tests/logging-gelf/pom.xml @@ -10,8 +10,8 @@ ../pom.xml - quarkus-logging-gelf-integration-test - Quarkus - Logging - GELF - Integration Test + quarkus-integration-test-logging-gelf + Quarkus - Integration Tests - Logging - GELF diff --git a/integration-tests/vertx-graphql/pom.xml b/integration-tests/vertx-graphql/pom.xml index 3e0e554a507f5..2c543f814e8f0 100644 --- a/integration-tests/vertx-graphql/pom.xml +++ b/integration-tests/vertx-graphql/pom.xml @@ -10,8 +10,8 @@ ../pom.xml - quarkus-vertx-graphql-integration-test - Quarkus - Quarkus - Vert.x GraphQL - Integration Test + quarkus-integration-test-vertx-graphql + Quarkus - Integration Tests - Vert.x GraphQL From fd90e91900aaf154a01c427896aabfa6d317fef0 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 10 Dec 2019 07:06:52 +1100 Subject: [PATCH 289/602] Temp workaround: use a single thread to generate native image Workaround for https://github.com/oracle/graal/issues/1927 Should be possible to remove this in 19.3.1 This is a different problem to the previous one that this fix worked around, however because we were using this setting we only ran into the thread safety problem after it was removed. --- .../quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index c4f43fab7b908..ca6a5219f0bc1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -203,6 +203,11 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa command.add("-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time command.add("-jar"); command.add(runnerJarName); + //dynamic proxy generation is not thread safe + //should be fixed in 19.3.1 + //but we need to add this option back to prevent intermittent failures + //https://github.com/oracle/graal/issues/1927 + command.add("-J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1"); if (nativeConfig.enableFallbackImages) { command.add("-H:FallbackThreshold=5"); } else { From 320d2b643350d4dcffaa26b9ff74eb75c444b35a Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Fri, 6 Dec 2019 11:43:42 -0600 Subject: [PATCH 290/602] Get correct map key for config maps of maps, with tests. Fixes #5982 --- .../BuildTimeConfigurationReader.java | 2 +- .../extest/deployment/TestProcessor.java | 11 ++++++++++ .../src/main/resources/application.properties | 5 +++++ .../io/quarkus/extest/ConfiguredBeanTest.java | 22 +++++++++++++++++++ .../extest/runtime/config/MapMapConfig.java | 17 ++++++++++++++ .../config/TestBuildAndRunTimeConfig.java | 2 ++ .../runtime/config/TestBuildTimeConfig.java | 4 ++++ .../runtime/config/TestRunTimeConfig.java | 2 ++ 8 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/MapMapConfig.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java index 4f10c01ad7b4a..44e6153910e6c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -459,9 +459,9 @@ private Map getMap(MapContainer matched, NameIterator ni) { ni.previous(); // now the cursor is before our map key and after the enclosing map key Map map = getMap((MapContainer) parent, ni); + String key = ni.getPreviousSegment(); ni.next(); // cursor restored - String key = ni.getPreviousSegment(); Map instance = getAsMap(map, key); if (instance == null) { instance = new HashMap<>(); diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java index e578f1e26dbb0..3b0d26a0df83f 100644 --- a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.function.BooleanSupplier; @@ -458,6 +459,16 @@ void registerFinalFieldReflectionObject(BuildProducer classes.produce(finalField); } + @BuildStep + void checkMapMap(TestBuildAndRunTimeConfig btrt, TestBuildTimeConfig bt, BuildProducer unused) { + if (!Objects.equals("1234", btrt.mapMap.get("outer-key").get("inner-key"))) { + throw new AssertionError("BTRT map map failed"); + } + if (!Objects.equals("1234", bt.mapMap.get("outer-key").get("inner-key"))) { + throw new AssertionError("BT map map failed"); + } + } + @BuildStep(onlyIf = Never.class) void neverRunThisOne() { throw new IllegalStateException("Not supposed to run!"); diff --git a/core/test-extension/deployment/src/main/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties index e102554c65ed9..4444b8a849388 100644 --- a/core/test-extension/deployment/src/main/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -113,3 +113,8 @@ quarkus.btrt.my-enums=optional,enum-one,enum-two ### anonymous root property quarkus.test-property=foo + +### map of map of strings +quarkus.rt.map-map.outer-key.inner-key=1234 +quarkus.btrt.map-map.outer-key.inner-key=1234 +quarkus.bt.map-map.outer-key.inner-key=1234 diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java index bcf9d1092a31e..c80903eb08742 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBeanTest.java @@ -268,4 +268,26 @@ public void testConversionUsingConvertWith() { List actualMapValues = new ArrayList<>(configuredBean.getRunTimeConfig().mapOfNumbers.values()); Assertions.assertEquals(mapValues, actualMapValues); } + + @Test + public void testBtrtMapOfMap() { + Map> mapMap = configuredBean.getBuildTimeConfig().mapMap; + Assertions.assertFalse(mapMap.containsKey("inner-key")); + Assertions.assertTrue(mapMap.containsKey("outer-key")); + Map map = mapMap.get("outer-key"); + Assertions.assertTrue(map.containsKey("inner-key")); + Assertions.assertFalse(map.containsKey("outer-key")); + Assertions.assertEquals("1234", map.get("inner-key")); + } + + @Test + public void testRtMapOfMap() { + Map> mapMap = configuredBean.getRunTimeConfig().mapMap; + Assertions.assertFalse(mapMap.containsKey("inner-key")); + Assertions.assertTrue(mapMap.containsKey("outer-key")); + Map map = mapMap.get("outer-key"); + Assertions.assertTrue(map.containsKey("inner-key")); + Assertions.assertFalse(map.containsKey("outer-key")); + Assertions.assertEquals("1234", map.get("inner-key")); + } } diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/MapMapConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/MapMapConfig.java new file mode 100644 index 0000000000000..336c6ff0f37ed --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/MapMapConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.extest.runtime.config; + +import java.util.Map; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * + */ +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED, name = "mm-root") +public class MapMapConfig { + //### map of map of strings + //quarkus.mm-root.map.inner-key.outer-key=1234 + + Map> map; +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java index b44a89b14cc73..e9317f91b21d7 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java @@ -38,6 +38,8 @@ public class TestBuildAndRunTimeConfig { @ConvertWith(WholeNumberConverter.class) public Map mapOfNumbers; + public Map> mapMap; + /** * Enum object */ diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java index 834193f4e0874..d8544999324e5 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java @@ -1,5 +1,7 @@ package io.quarkus.extest.runtime.config; +import java.util.Map; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -25,6 +27,8 @@ public class TestBuildTimeConfig { @ConfigItem public AllValuesConfig allValues; + public Map> mapMap; + public TestBuildTimeConfig() { } diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java index 66d9c335790ce..ab963f4ec3000 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java @@ -107,6 +107,8 @@ public class TestRunTimeConfig { @ConvertWith(WholeNumberConverter.class) public Map mapOfNumbers; + public Map> mapMap; + @Override public String toString() { return "TestRunTimeConfig{" + From 18b3802ea5b1bfee84093cb1c400d4b1b29ce8bf Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 7 Dec 2019 23:24:37 +0100 Subject: [PATCH 291/602] Fix Keycloack Authorization require body handler detection --- .../pep/deployment/KeycloakPolicyEnforcerBuildStep.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java index 468ddc56e4e7a..5353dc95221cd 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java @@ -44,8 +44,10 @@ private boolean isBodyClaimInformationPointDefined(Map> entry : claims.entrySet()) { Map value = entry.getValue(); - if (value.get(entry.getKey()).contains("request.body")) { - return true; + for (String nestedValue : value.values()) { + if (nestedValue.contains("request.body")) { + return true; + } } } From 1177d281ae1115cae752757439bda65a5848fbea Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 9 Dec 2019 08:03:59 -0600 Subject: [PATCH 292/602] Add missing properties --- .../test-extension/src/main/resources/application.properties | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/integration-tests/test-extension/src/main/resources/application.properties b/integration-tests/test-extension/src/main/resources/application.properties index b4436b7721f75..9ec349989a8dd 100644 --- a/integration-tests/test-extension/src/main/resources/application.properties +++ b/integration-tests/test-extension/src/main/resources/application.properties @@ -104,3 +104,8 @@ quarkus.btrt.my-enums=optional,enum-one,enum-two ### anonymous root property quarkus.test-property=foo + +### map of map of strings +quarkus.rt.map-map.outer-key.inner-key=1234 +quarkus.btrt.map-map.outer-key.inner-key=1234 +quarkus.bt.map-map.outer-key.inner-key=1234 From 7cf900edf117acba017ffa332282cf0b9b8eef21 Mon Sep 17 00:00:00 2001 From: Johannes Date: Sat, 7 Dec 2019 09:28:50 +0100 Subject: [PATCH 293/602] Add Flyway support for multiple datasources #3449 --- .../agroal/deployment/AgroalProcessor.java | 12 +- extensions/agroal/spi/pom.xml | 6 + .../DataSourceInitializedBuildItem.java | 57 +++++- .../DataSourceInitializedBuildItemTest.java | 109 ++++++++++++ .../flyway/FlywayDatasourceBeanGenerator.java | 135 ++++++++++++++ .../io/quarkus/flyway/FlywayProcessor.java | 45 ++++- ...nBaselineOnMigrateNamedDataSourceTest.java | 37 ++++ ...yExtensionConfigDefaultDataSourceTest.java | 37 ++++ ...figDefaultDataSourceWithoutFlywayTest.java | 41 +++++ .../test/FlywayExtensionConfigEmptyTest.java | 39 ++++ .../test/FlywayExtensionConfigFixture.java | 166 ++++++++++++++++++ ...nsionConfigMissingNamedDataSourceTest.java | 40 +++++ ...ayExtensionConfigMultiDataSourcesTest.java | 75 ++++++++ ...figMultiDataSourcesWithoutDefaultTest.java | 53 ++++++ ...nfigNamedDataSourceWithoutDefaultTest.java | 42 +++++ ...onfigNamedDataSourceWithoutFlywayTest.java | 43 +++++ .../test/FlywayExtensionFullConfigTest.java | 82 --------- ...sionMigrateAtStartNamedDataSourceTest.java | 39 ++++ ...ine-on-migrate-named-datasource.properties | 11 ++ .../test/resources/config-empty.properties | 0 ...fault-datasource-without-flyway.properties | 4 + ... config-for-default-datasource.properties} | 2 + ...ig-for-missing-named-datasource.properties | 19 ++ ...ple-datasources-without-default.properties | 39 ++++ ...config-for-multiple-datasources.properties | 57 ++++++ ...amed-datasource-without-default.properties | 19 ++ ...named-datasource-without-flyway.properties | 4 + ...t-start-config-named-datasource.properties | 6 + extensions/flyway/runtime/pom.xml | 7 + .../io/quarkus/flyway/FlywayDataSource.java | 60 +++++++ .../flyway/runtime/FlywayBuildConfig.java | 43 +++-- .../quarkus/flyway/runtime/FlywayCreator.java | 33 ++++ .../runtime/FlywayDataSourceBuildConfig.java | 33 ++++ .../FlywayDataSourceRuntimeConfig.java | 93 ++++++++++ .../flyway/runtime/FlywayProducer.java | 56 ++++-- .../flyway/runtime/FlywayRecorder.java | 37 +++- .../flyway/runtime/FlywayRuntimeConfig.java | 91 +++------- .../flyway/runtime/FlywayCreatorTest.java | 165 +++++++++++++++++ .../flyway/runtime/FlywayProducerTest.java | 46 +++++ 39 files changed, 1687 insertions(+), 196 deletions(-) create mode 100644 extensions/agroal/spi/src/test/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItemTest.java create mode 100644 extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigFixture.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java delete mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFullConfigTest.java create mode 100644 extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java create mode 100644 extensions/flyway/deployment/src/test/resources/baseline-on-migrate-named-datasource.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-empty.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-default-datasource-without-flyway.properties rename extensions/flyway/deployment/src/test/resources/{full-config.properties => config-for-default-datasource.properties} (96%) create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-missing-named-datasource.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources-without-default.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-default.properties create mode 100644 extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-flyway.properties create mode 100644 extensions/flyway/deployment/src/test/resources/migrate-at-start-config-named-datasource.properties create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java create mode 100644 extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java create mode 100644 extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 3e74f95b06f67..5451623d6b944 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -190,14 +190,22 @@ private static void validateBuildTimeConfig(final String datasourceName, final D void configureRuntimeProperties(AgroalRecorder recorder, BuildProducer dataSourceInitialized, AgroalRuntimeConfig agroalRuntimeConfig) { - if (!agroalBuildTimeConfig.defaultDataSource.driver.isPresent() && agroalBuildTimeConfig.namedDataSources.isEmpty()) { + Optional defaultDataSourceDriver = agroalBuildTimeConfig.defaultDataSource.driver; + if (!defaultDataSourceDriver.isPresent() && agroalBuildTimeConfig.namedDataSources.isEmpty()) { // No datasource has been configured so bail out return; } recorder.configureRuntimeProperties(agroalRuntimeConfig); - dataSourceInitialized.produce(new DataSourceInitializedBuildItem()); + Set dataSourceNames = agroalBuildTimeConfig.namedDataSources.keySet(); + DataSourceInitializedBuildItem buildItem; + if (defaultDataSourceDriver.isPresent()) { + buildItem = DataSourceInitializedBuildItem.ofDefaultDataSourceAnd(dataSourceNames); + } else { + buildItem = DataSourceInitializedBuildItem.ofDataSources(dataSourceNames); + } + dataSourceInitialized.produce(buildItem); } @BuildStep diff --git a/extensions/agroal/spi/pom.xml b/extensions/agroal/spi/pom.xml index dca7328ae8c8b..b457ade0ddc41 100644 --- a/extensions/agroal/spi/pom.xml +++ b/extensions/agroal/spi/pom.xml @@ -18,6 +18,12 @@ io.quarkus quarkus-core-deployment + + + io.quarkus + quarkus-junit5-internal + test + diff --git a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java index 0a5329d3f437c..92472e7d39a97 100644 --- a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java +++ b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java @@ -1,12 +1,67 @@ package io.quarkus.agroal.deployment; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + import io.quarkus.builder.item.SimpleBuildItem; /** * Marker build item indicating the datasource has been fully initialized. + *

+ * Contains all processed datasource names, including an empty string for the default datasource. */ public final class DataSourceInitializedBuildItem extends SimpleBuildItem { + private static final String DEFAULT_DATASOURCE_NAME = ""; + + private final String defaultDataSourceName; + private final Collection dataSourceNames = new ArrayList<>(); + + /** + * Null-safe way to get the datasource names of the given {@link DataSourceInitializedBuildItem}. + */ + public static final Collection dataSourceNamesOf(DataSourceInitializedBuildItem buildItem) { + return (buildItem != null) ? buildItem.getDataSourceNames() : Collections.emptyList(); + } + + /** + * Null-safe way to find out, if the given {@link DataSourceInitializedBuildItem} contains the default datasource. + */ + public static final boolean isDefaultDataSourcePresent(DataSourceInitializedBuildItem buildItem) { + return (buildItem != null) ? buildItem.isDefaultDataSourcePresent() : false; + } + + /** + * Creates a new instance of the default DataSource name and the given DataSource names. + */ + public static final DataSourceInitializedBuildItem ofDefaultDataSourceAnd(Collection dataSourceNames) { + return new DataSourceInitializedBuildItem(dataSourceNames, DEFAULT_DATASOURCE_NAME); + } + + /** + * Creates a new instance of the given DataSource names. + */ + public static final DataSourceInitializedBuildItem ofDataSources(Collection dataSourceNames) { + Collection allDataSourceNames = new ArrayList<>(dataSourceNames); + return new DataSourceInitializedBuildItem(allDataSourceNames, null); + } + + DataSourceInitializedBuildItem(Collection dataSourceNames, String defaultDataSourceName) { + this.dataSourceNames.addAll(dataSourceNames); + this.defaultDataSourceName = defaultDataSourceName; + } + + public Collection getDataSourceNames() { + return new ArrayList<>(dataSourceNames); + } + + public boolean isDefaultDataSourcePresent() { + return (defaultDataSourceName != null); + } - public DataSourceInitializedBuildItem() { + @Override + public String toString() { + return "DataSourceInitializedBuildItem [defaultDataSourceName=" + defaultDataSourceName + ", dataSourceNames=" + + dataSourceNames + "]"; } } diff --git a/extensions/agroal/spi/src/test/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItemTest.java b/extensions/agroal/spi/src/test/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItemTest.java new file mode 100644 index 0000000000000..9063ef7121c87 --- /dev/null +++ b/extensions/agroal/spi/src/test/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItemTest.java @@ -0,0 +1,109 @@ +package io.quarkus.agroal.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DataSourceInitializedBuildItemTest { + + private static final String DEFAULT_DATASOURCE = ""; + private static final String NO_DEFAULT_DATASOURCE = null; + + private DataSourceInitializedBuildItem buildItem; + + @Test + @DisplayName("datasource names are contained correctly") + void testDataSourceNamesContained() { + Collection expectedNames = new ArrayList<>(Arrays.asList("one", "another")); + buildItem = new DataSourceInitializedBuildItem(expectedNames, DEFAULT_DATASOURCE); + assertEquals(expectedNames, buildItem.getDataSourceNames()); + } + + @Test + @DisplayName("datasource names may not be changed from the outside using the given Collection") + void testDataSourceNamesNotChangeableByGivenCollection() { + Collection expectedNames = new ArrayList<>(Arrays.asList("", "one", "another")); + Collection givenCollection = new ArrayList<>(expectedNames); + buildItem = new DataSourceInitializedBuildItem(givenCollection, DEFAULT_DATASOURCE); + givenCollection.add("shouldBeIgnored"); + assertEquals(expectedNames, buildItem.getDataSourceNames()); + } + + @Test + @DisplayName("datasource names may not be changed from the outside using the returned Collection") + void testDataSourceNamesNotChangeableByReturnedCollection() { + Collection expectedNames = new ArrayList<>(Arrays.asList("one", "another")); + buildItem = new DataSourceInitializedBuildItem(expectedNames, DEFAULT_DATASOURCE); + assertNotSame(expectedNames, buildItem.getDataSourceNames()); + buildItem.getDataSourceNames().add("shouldBeIgnored"); + assertEquals(expectedNames, buildItem.getDataSourceNames()); + } + + @Test + @DisplayName("dataSourceNamesOf returns names correctly") + void testDataSourceNamesOfInstanceReturnsNames() { + Collection expectedNames = new ArrayList<>(Arrays.asList("one", "another")); + buildItem = new DataSourceInitializedBuildItem(expectedNames, DEFAULT_DATASOURCE); + assertEquals(expectedNames, DataSourceInitializedBuildItem.dataSourceNamesOf(buildItem)); + } + + @Test + @DisplayName("dataSourceNamesOf is null-safe and returns an empty set instead of null") + void testDataSourceNamesOfInstanceIsNullSafe() { + assertTrue(DataSourceInitializedBuildItem.dataSourceNamesOf(null).isEmpty()); + } + + @Test + @DisplayName("isDefaultDataSourcePresent matches if there is a default datasource") + void testIsDefaultDataSourcePresentMatches() { + Set expectedNames = new HashSet<>(Arrays.asList("", "one", "another")); + buildItem = DataSourceInitializedBuildItem.ofDefaultDataSourceAnd(expectedNames); + assertTrue(DataSourceInitializedBuildItem.isDefaultDataSourcePresent(buildItem)); + } + + @Test + @DisplayName("isDefaultDataSourcePresent is null-safe and returns false in case of null") + void testIsDefaultDataSourcePresentIsNullSafe() { + assertFalse(DataSourceInitializedBuildItem.isDefaultDataSourcePresent(null)); + } + + @Test + @DisplayName("default datasource is present") + void testOnlyDefaultDataSourcePresent() { + buildItem = new DataSourceInitializedBuildItem(Arrays.asList("any"), DEFAULT_DATASOURCE); + assertTrue(buildItem.isDefaultDataSourcePresent()); + } + + @Test + @DisplayName("default datasource is not present") + void testDefaultDataSourceIsNotPresent() { + buildItem = new DataSourceInitializedBuildItem(Arrays.asList("any"), NO_DEFAULT_DATASOURCE); + assertFalse(buildItem.isDefaultDataSourcePresent()); + } + + @Test + @DisplayName("createable with default datasource") + void testSubsequentlyAddedPresentOptionalDefaultDataSourceIsPresent() { + buildItem = DataSourceInitializedBuildItem.ofDefaultDataSourceAnd(Arrays.asList("notdefault")); + assertTrue(buildItem.isDefaultDataSourcePresent()); + } + + @Test + @DisplayName("createable without default datasource") + void testSubsequentlyAddedNonPresentOptionalDefaultDataSourceDoesNotChangeAnything() { + buildItem = DataSourceInitializedBuildItem.ofDataSources(Arrays.asList("notdefault")); + assertSame(buildItem, buildItem); + } + +} diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java new file mode 100644 index 0000000000000..36c31c33cad02 --- /dev/null +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java @@ -0,0 +1,135 @@ +package io.quarkus.flyway; + +import java.util.Collection; +import java.util.HashSet; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.inject.Named; +import javax.sql.DataSource; + +import org.flywaydb.core.Flyway; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.util.HashUtil; +import io.quarkus.flyway.runtime.FlywayProducer; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.FieldCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; + +/** + * Generates the CDI producer bean for {@link Flyway} at build time.
+ * Supports multiple {@link Named} {@link DataSource}s. + *

+ * It produces {@link Flyway} instances for every {@link Named} {@link DataSource}.
+ * All {@link Flyway} instances get named the same way as the {@link DataSource}s,
+ * appended by the postfix {@value #FLYWAY_BEAN_NAME_POSTFIX}. + */ +class FlywayDatasourceBeanGenerator { + + public static final String FLYWAY_BEAN_NAME_POSTFIX = "_flyway"; + + private static final String FLYWAY_PRODUCER_BEAN_NAME = "FlywayDataSourceProducer"; + private static final String FLYWAY_PRODUCER_PACKAGE_NAME = FlywayProducer.class.getPackage().getName(); + private static final String FLYWAY_PRODUCER_TYPE_NAME = FLYWAY_PRODUCER_PACKAGE_NAME + "." + FLYWAY_PRODUCER_BEAN_NAME; + + private static final int ACCESS_PACKAGE_PROTECTED = 0; + + private final Collection dataSourceNames = new HashSet<>(); + private final BuildProducer generatedBean; + + public FlywayDatasourceBeanGenerator(Collection dataSourceNames, + BuildProducer generatedBean) { + this.dataSourceNames.addAll(dataSourceNames); + this.generatedBean = generatedBean; + } + + /** + * Create a producer bean managing flyway. + *

+ * Build time and runtime configuration are both injected into this bean. + * + * @return String name of the generated producer bean class. + */ + public void createFlywayProducerBean() { + ClassCreator classCreator = ClassCreator.builder() + .classOutput(this::writeGeneratedBeanBuildItem) + .className(FLYWAY_PRODUCER_TYPE_NAME) + .build(); + classCreator.addAnnotation(ApplicationScoped.class); + + FieldCreator defaultProducerField = classCreator.getFieldCreator("defaultProducer", FlywayProducer.class); + defaultProducerField.setModifiers(ACCESS_PACKAGE_PROTECTED); + defaultProducerField.addAnnotation(Inject.class); + + for (String dataSourceName : dataSourceNames) { + String dataSourceFieldName = "dataSource" + hashed(dataSourceName); + FieldCreator dataSourceField = classCreator.getFieldCreator(dataSourceFieldName, DataSource.class); + dataSourceField.setModifiers(ACCESS_PACKAGE_PROTECTED); + dataSourceField.addAnnotation(Inject.class); + dataSourceField.addAnnotation(annotatedWithNamed(dataSourceName)); + + String producerMethodName = "createFlywayForDataSource" + hashed(dataSourceName); + MethodCreator flywayProducerMethod = classCreator.getMethodCreator(producerMethodName, Flyway.class); + flywayProducerMethod.addAnnotation(Produces.class); + flywayProducerMethod.addAnnotation(Dependent.class); + flywayProducerMethod.addAnnotation(annotatedWithFlywayDatasource(dataSourceName)); + flywayProducerMethod.addAnnotation(annotatedWithNamed(dataSourceName + FLYWAY_BEAN_NAME_POSTFIX)); + + flywayProducerMethod.returnValue( + flywayProducerMethod.invokeVirtualMethod( + createFlywayMethod(), + resultHandleFor(defaultProducerField, flywayProducerMethod), + resultHandleFor(dataSourceField, flywayProducerMethod), + flywayProducerMethod.load(dataSourceName))); + } + classCreator.close(); + } + + private void writeGeneratedBeanBuildItem(String name, byte[] data) { + generatedBean.produce(new GeneratedBeanBuildItem(name, data)); + } + + private static String hashed(String dataSourceName) { + return "_" + HashUtil.sha1(dataSourceName); + } + + private static MethodDescriptor createFlywayMethod() { + Class[] parameterTypes = { DataSource.class, String.class }; + return MethodDescriptor.ofMethod(FlywayProducer.class, "createFlyway", Flyway.class, parameterTypes); + } + + private static ResultHandle resultHandleFor(FieldCreator field, BytecodeCreator method) { + FieldDescriptor fieldDescriptor = field.getFieldDescriptor(); + return method.readInstanceField(fieldDescriptor, method.getThis()); + } + + private static AnnotationInstance annotatedWithNamed(String dataSourceName) { + return AnnotationInstance.create(DotNames.NAMED, null, + new AnnotationValue[] { AnnotationValue.createStringValue("value", dataSourceName) }); + } + + //Since is does not seem to be possible to generate the annotation "@Typed", + //because AnnotationValue.createArrayValue is not implemented yet (jandex, August 2019), + //the annotation "@FlywayDataSource" was introduced (in conformity with @DataSource). + private AnnotationInstance annotatedWithFlywayDatasource(String dataSourceName) { + return AnnotationInstance.create(DotName.createSimple(FlywayDataSource.class.getName()), null, + new AnnotationValue[] { AnnotationValue.createStringValue("value", dataSourceName) }); + } + + @Override + public String toString() { + return "FlywayDatasourceBeanGenerator [dataSourceNames=" + dataSourceNames + ", generatedBean=" + generatedBean + "]"; + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index 0654daac8bf46..1bcea8226fb9b 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -13,9 +13,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -28,6 +30,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -38,7 +41,9 @@ import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.flyway.runtime.FlywayBuildConfig; +import io.quarkus.flyway.runtime.FlywayDataSourceBuildConfig; import io.quarkus.flyway.runtime.FlywayProducer; import io.quarkus.flyway.runtime.FlywayRecorder; import io.quarkus.flyway.runtime.FlywayRuntimeConfig; @@ -69,14 +74,20 @@ void build(BuildProducer additionalBeanProducer, BuildProducer containerListenerProducer, BuildProducer generatedResourceProducer, FlywayRecorder recorder, - DataSourceInitializedBuildItem dataSourceInitializedBuildItem) throws IOException, URISyntaxException { + DataSourceInitializedBuildItem dataSourceInitializedBuildItem, + BuildProducer generatedBeanBuildItem, + RecorderContext recorderContext) throws IOException, URISyntaxException { featureProducer.produce(new FeatureBuildItem(FeatureBuildItem.FLYWAY)); AdditionalBeanBuildItem unremovableProducer = AdditionalBeanBuildItem.unremovableOf(FlywayProducer.class); additionalBeanProducer.produce(unremovableProducer); - registerNativeImageResources(resourceProducer, generatedResourceProducer, flywayBuildConfig); + Collection dataSourceNames = DataSourceInitializedBuildItem.dataSourceNamesOf(dataSourceInitializedBuildItem); + new FlywayDatasourceBeanGenerator(dataSourceNames, generatedBeanBuildItem).createFlywayProducerBean(); + + registerNativeImageResources(resourceProducer, generatedResourceProducer, + discoverApplicationMigrations(getMigrationLocations(dataSourceInitializedBuildItem))); containerListenerProducer.produce( new BeanContainerListenerBuildItem(recorder.setFlywayBuildConfig(flywayBuildConfig))); @@ -102,10 +113,9 @@ ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder, private void registerNativeImageResources(BuildProducer resource, BuildProducer generatedResourceProducer, - FlywayBuildConfig flywayBuildConfig) + List applicationMigrations) throws IOException, URISyntaxException { final List nativeResources = new ArrayList<>(); - List applicationMigrations = discoverApplicationMigrations(flywayBuildConfig); nativeResources.addAll(applicationMigrations); // Store application migration in a generated resource that will be accessed later by the Quarkus-Flyway path scanner String resourcesList = applicationMigrations @@ -119,14 +129,31 @@ private void registerNativeImageResources(BuildProducer discoverApplicationMigrations(FlywayBuildConfig flywayBuildConfig) + /** + * Collects the configured migration locations for the default and all named DataSources. + *

+ * A {@link LinkedHashSet} is used to avoid duplications. + * + * @param dataSourceInitializedBuildItem {@link DataSourceInitializedBuildItem} + * @return {@link Collection} of {@link String}s + */ + private Collection getMigrationLocations(DataSourceInitializedBuildItem dataSourceInitializedBuildItem) { + List defaultLocations = FlywayDataSourceBuildConfig.defaultConfig().locations.orElse(Collections.emptyList()); + Collection dataSourceNames = DataSourceInitializedBuildItem.dataSourceNamesOf(dataSourceInitializedBuildItem); + Collection migrationLocations = dataSourceNames.stream() + .map(flywayBuildConfig::getConfigForDataSourceName) + .flatMap(config -> config.locations.orElse(defaultLocations).stream()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (DataSourceInitializedBuildItem.isDefaultDataSourcePresent(dataSourceInitializedBuildItem)) { + migrationLocations.addAll(flywayBuildConfig.defaultDataSource.locations.orElse(defaultLocations)); + } + return migrationLocations; + } + + private List discoverApplicationMigrations(Collection locations) throws IOException, URISyntaxException { List resources = new ArrayList<>(); try { - List locations = new ArrayList<>(flywayBuildConfig.locations.orElse(Collections.emptyList())); - if (locations.isEmpty()) { - locations.add("db/migration"); - } // Locations can be a comma separated list for (String location : locations) { // Strip any 'classpath:' protocol prefixes because they are assumed diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java new file mode 100644 index 0000000000000..ccc4ee65358be --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java @@ -0,0 +1,37 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.MigrationInfo; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionBaselineOnMigrateNamedDataSourceTest { + // Quarkus built object + @Inject + @Named("users_flyway") + Flyway flyway; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("baseline-on-migrate-named-datasource.properties", "application.properties")); + + @Test + @DisplayName("Create history table correctly") + public void testFlywayInitialBaselineInfo() { + MigrationInfo baselineInfo = flyway.info().applied()[0]; + + assertEquals("0.0.1", baselineInfo.getVersion().getVersion()); + assertEquals("Initial description for test", baselineInfo.getDescription()); + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java new file mode 100644 index 0000000000000..366f724288844 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java @@ -0,0 +1,37 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionConfigDefaultDataSourceTest { + + // Quarkus built object + @Inject + Flyway flyway; + + @Inject + FlywayExtensionConfigFixture fixture; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-default-datasource.properties", "application.properties")); + + @Test + @DisplayName("Reads flyway configuration for default datasource correctly") + public void testFlywayConfigInjection() { + fixture.assertAllConfigurationSettings(flyway.getConfiguration(), ""); + assertFalse(fixture.migrateAtStart("")); + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java new file mode 100644 index 0000000000000..0e8b243d79a45 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java @@ -0,0 +1,41 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Assures, that flyway can also be used without any configuration, + * provided, that at least a datasource is configured. + */ +public class FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest { + + // Quarkus built objects + @Inject + Flyway flyway; + + @Inject + FlywayExtensionConfigFixture fixture; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-default-datasource-without-flyway.properties", "application.properties")); + + @Test + @DisplayName("Reads predefined default flyway configuration for default datasource correctly") + public void testFlywayDefaultConfigInjection() { + fixture.assertDefaultConfigurationSettings(flyway.getConfiguration()); + assertFalse(fixture.migrateAtStart("")); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java new file mode 100644 index 0000000000000..a3183e2dab42a --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java @@ -0,0 +1,39 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Flyway needs a datasource to work. + * This tests assures, that an error occurs, + * as soon as the default flyway configuration points to an missing default datasource. + */ +public class FlywayExtensionConfigEmptyTest { + + // Quarkus built objects + @Inject + Instance flyway; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("config-empty.properties", "application.properties")); + + @Test + @DisplayName("Injecting (default) flyway should fail if there is no datasource configured") + public void testFlywayNotAvailableWithoutDataSource() { + assertThrows(UnsatisfiedResolutionException.class, flyway::get); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigFixture.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigFixture.java new file mode 100644 index 0000000000000..b40248186677c --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigFixture.java @@ -0,0 +1,166 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.Config; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.Location; +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.api.configuration.FluentConfiguration; +import org.junit.jupiter.api.Disabled; + +/** + * This fixture provides access to read the expected and the actual configuration of flyway. + * It also provides a method combining all assertions to be reused for multiple tests. + */ +@Disabled +@ApplicationScoped +public class FlywayExtensionConfigFixture { + + @Inject + Config config; + + public void assertAllConfigurationSettings(Configuration configuration, String dataSourceName) { + assertEquals(locations(configuration), locations(dataSourceName)); + assertEquals(sqlMigrationPrefix(configuration), sqlMigrationPrefix(dataSourceName)); + assertEquals(repeatableSqlMigrationPrefix(configuration), repeatableSqlMigrationPrefix(dataSourceName)); + assertEquals(tableName(configuration), tableName(dataSourceName)); + assertEquals(schemaNames(configuration), schemaNames(dataSourceName)); + + assertEquals(connectRetries(configuration), connectRetries(dataSourceName)); + + assertEquals(baselineOnMigrate(configuration), baselineOnMigrate(dataSourceName)); + assertEquals(baselineVersion(configuration), baselineVersion(dataSourceName)); + assertEquals(baselineDescription(configuration), baselineDescription(dataSourceName)); + } + + public void assertDefaultConfigurationSettings(Configuration configuration) { + FluentConfiguration defaultConfiguration = Flyway.configure(); + assertEquals(locations(configuration), locations(defaultConfiguration)); + assertEquals(sqlMigrationPrefix(configuration), sqlMigrationPrefix(defaultConfiguration)); + assertEquals(repeatableSqlMigrationPrefix(configuration), repeatableSqlMigrationPrefix(defaultConfiguration)); + assertEquals(tableName(configuration), tableName(defaultConfiguration)); + assertEquals(schemaNames(configuration), schemaNames(defaultConfiguration)); + + assertEquals(connectRetries(configuration), connectRetries(defaultConfiguration)); + + assertEquals(baselineOnMigrate(configuration), baselineOnMigrate(defaultConfiguration)); + assertEquals(baselineVersion(configuration), baselineVersion(defaultConfiguration)); + assertEquals(baselineDescription(configuration), baselineDescription(defaultConfiguration)); + } + + public int connectRetries(String datasourceName) { + return getIntValue("quarkus.flyway.%s.connect-retries", datasourceName); + } + + public int connectRetries(Configuration configuration) { + return configuration.getConnectRetries(); + } + + public String schemaNames(String datasourceName) { + return getStringValue("quarkus.flyway.%s.schemas", datasourceName); + } + + public String schemaNames(Configuration configuration) { + return Arrays.stream(configuration.getSchemas()).collect(Collectors.joining(",")); + } + + public String tableName(String datasourceName) { + return getStringValue("quarkus.flyway.%s.table", datasourceName); + } + + public String tableName(Configuration configuration) { + return configuration.getTable(); + } + + public String locations(String datasourceName) { + return getStringValue("quarkus.flyway.%s.locations", datasourceName); + } + + public String locations(Configuration configuration) { + return Arrays.stream(configuration.getLocations()).map(Location::getPath).collect(Collectors.joining(",")); + } + + public String sqlMigrationPrefix(String datasourceName) { + return getStringValue("quarkus.flyway.%s.sql-migration-prefix", datasourceName); + } + + public String sqlMigrationPrefix(Configuration configuration) { + return configuration.getSqlMigrationPrefix(); + } + + public String repeatableSqlMigrationPrefix(String datasourceName) { + return getStringValue("quarkus.flyway.%s.repeatable-sql-migration-prefix", datasourceName); + } + + public String repeatableSqlMigrationPrefix(Configuration configuration) { + return configuration.getRepeatableSqlMigrationPrefix(); + } + + public boolean baselineOnMigrate(String datasourceName) { + return getBooleanValue("quarkus.flyway.%s.baseline-on-migrate", datasourceName); + } + + public boolean baselineOnMigrate(Configuration configuration) { + return configuration.isBaselineOnMigrate(); + } + + public String baselineVersion(String datasourceName) { + return getStringValue("quarkus.flyway.%s.baseline-version", datasourceName); + } + + public String baselineVersion(Configuration configuration) { + return configuration.getBaselineVersion().getVersion(); + } + + public String baselineDescription(String datasourceName) { + return getStringValue("quarkus.flyway.%s.baseline-description", datasourceName); + } + + public String baselineDescription(Configuration configuration) { + return configuration.getBaselineDescription(); + } + + public boolean migrateAtStart(String datasourceName) { + return getBooleanValue("quarkus.flyway.migrate-at-start", datasourceName); + } + + private String getStringValue(String parameterName, String datasourceName) { + return getValue(parameterName, datasourceName, String.class); + } + + private int getIntValue(String parameterName, String datasourceName) { + return getValue(parameterName, datasourceName, Integer.class); + } + + private boolean getBooleanValue(String parameterName, String datasourceName) { + return getValue(parameterName, datasourceName, Boolean.class); + } + + private T getValue(String parameterName, String datasourceName, Class type) { + return getValue(parameterName, datasourceName, type, this::log); + } + + private T getValue(String parameterName, String datasourceName, Class type, Consumer logger) { + String propertyName = fillin(parameterName, datasourceName); + T propertyValue = config.getValue(propertyName, type); + logger.accept("Config property " + propertyName + " = " + propertyValue); + return propertyValue; + } + + private void log(String content) { + //activate for debugging + // System.out.println(content); + } + + private String fillin(String propertyName, String datasourceName) { + return String.format(propertyName, datasourceName).replace("..", "."); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java new file mode 100644 index 0000000000000..ed62cc929ef30 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java @@ -0,0 +1,40 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Flyway needs a datasource to work. + * This tests assures, that an error occurs, as soon as a named flyway configuration points to an missing datasource. + */ +public class FlywayExtensionConfigMissingNamedDataSourceTest { + + // Quarkus built objects + @Inject + @Named("users_flyway") + Instance flyway; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("config-for-missing-named-datasource.properties", "application.properties")); + + @Test + @DisplayName("Injecting flyway should fail if the named datasource is missing") + public void testFlywayNotAvailableWithoutDataSource() { + assertThrows(UnsatisfiedResolutionException.class, flyway::get); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java new file mode 100644 index 0000000000000..c74b30bf40b3b --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java @@ -0,0 +1,75 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.flyway.FlywayDataSource; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test a full configuration with default and two named datasources plus their flyway settings. + */ +public class FlywayExtensionConfigMultiDataSourcesTest { + + @Inject + FlywayExtensionConfigFixture fixture; + + // Quarkus built objects + @Inject + Flyway flyway; + + @Inject + @FlywayDataSource("users") + Flyway flywayUsers; + + @Inject + @FlywayDataSource("inventory") + Flyway flywayInventory; + + @Inject + @Named("inventory_flyway") + Flyway flywayNamedInventory; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-multiple-datasources.properties", "application.properties")); + + @Test + @DisplayName("Reads default flyway configuration for default datasource correctly") + public void testFlywayDefaultConfigInjection() { + fixture.assertAllConfigurationSettings(flyway.getConfiguration(), ""); + assertFalse(fixture.migrateAtStart("")); + } + + @Test + @DisplayName("Reads flyway configuration for datasource named 'users' correctly") + public void testFlywayConfigNamedUsersInjection() { + fixture.assertAllConfigurationSettings(flywayUsers.getConfiguration(), "users"); + assertFalse(fixture.migrateAtStart("")); + } + + @Test + @DisplayName("Reads flyway configuration for datasource named 'inventory' correctly") + public void testFlywayConfigNamedInventoryInjection() { + fixture.assertAllConfigurationSettings(flywayInventory.getConfiguration(), "inventory"); + assertFalse(fixture.migrateAtStart("")); + } + + @Test + @DisplayName("Reads flyway configuration directly named 'inventory_flyway' correctly") + public void testFlywayConfigDirectlyNamedInventoryInjection() { + fixture.assertAllConfigurationSettings(flywayNamedInventory.getConfiguration(), "inventory"); + assertFalse(fixture.migrateAtStart("")); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java new file mode 100644 index 0000000000000..4851cea7a1b47 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java @@ -0,0 +1,53 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test a full configuration with default and two named datasources plus their flyway settings. + */ +public class FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest { + + @Inject + FlywayExtensionConfigFixture fixture; + + // Quarkus built objects + @Inject + @Named("users_flyway") + Flyway flywayUsers; + + @Inject + @Named("inventory_flyway") + Flyway flywayInventory; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-multiple-datasources-without-default.properties", "application.properties")); + + @Test + @DisplayName("Reads flyway configuration for datasource named 'users' without default datasource correctly") + public void testFlywayConfigNamedUsersInjection() { + fixture.assertAllConfigurationSettings(flywayUsers.getConfiguration(), "users"); + assertFalse(fixture.migrateAtStart("")); + } + + @Test + @DisplayName("Reads flyway configuration for datasource named 'inventory' without default datasource correctly") + public void testFlywayConfigNamedInventoryInjection() { + fixture.assertAllConfigurationSettings(flywayInventory.getConfiguration(), "inventory"); + assertFalse(fixture.migrateAtStart("")); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java new file mode 100644 index 0000000000000..acf92ef23822f --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java @@ -0,0 +1,42 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test a full configuration with default and two named datasources plus their flyway settings. + */ +public class FlywayExtensionConfigNamedDataSourceWithoutDefaultTest { + + @Inject + FlywayExtensionConfigFixture fixture; + + // Quarkus built objects + @Inject + @Named("users_flyway") + Flyway flywayUsers; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-named-datasource-without-default.properties", "application.properties")); + + @Test + @DisplayName("Reads flyway configuration for datasource named 'users' without default datasource correctly") + public void testFlywayConfigNamedUsersInjection() { + fixture.assertAllConfigurationSettings(flywayUsers.getConfiguration(), "users"); + assertFalse(fixture.migrateAtStart("")); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java new file mode 100644 index 0000000000000..db9e6303744c4 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java @@ -0,0 +1,43 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Assures, that flyway can also be used without any configuration, + * provided, that at least a named datasource is configured. + */ +public class FlywayExtensionConfigNamedDataSourceWithoutFlywayTest { + + // Quarkus built objects + @Inject + @Named("users_flyway") + Flyway flyway; + + @Inject + FlywayExtensionConfigFixture fixture; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(FlywayExtensionConfigFixture.class) + .addAsResource("config-for-named-datasource-without-flyway.properties", "application.properties")); + + @Test + @DisplayName("Reads predefined default flyway configuration for named datasource correctly") + public void testFlywayDefaultConfigInjection() { + fixture.assertDefaultConfigurationSettings(flyway.getConfiguration()); + assertFalse(fixture.migrateAtStart("users")); + } +} \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFullConfigTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFullConfigTest.java deleted file mode 100644 index 3742f21c5ec2a..0000000000000 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFullConfigTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.quarkus.flyway.test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import javax.inject.Inject; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.Location; -import org.flywaydb.core.api.configuration.Configuration; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -public class FlywayExtensionFullConfigTest { - // Validation properties - @ConfigProperty(name = "quarkus.flyway.connect-retries") - int connectRetries; - @ConfigProperty(name = "quarkus.flyway.schemas") - List schemaNames; - @ConfigProperty(name = "quarkus.flyway.table") - String tableName; - @ConfigProperty(name = "quarkus.flyway.locations") - List locations; - @ConfigProperty(name = "quarkus.flyway.sql-migration-prefix") - String sqlMigrationPrefix; - @ConfigProperty(name = "quarkus.flyway.repeatable-sql-migration-prefix") - String repeatableSqlMigrationPrefix; - @ConfigProperty(name = "quarkus.flyway.baseline-on-migrate") - boolean baselineOnMigrate; - @ConfigProperty(name = "quarkus.flyway.baseline-version") - String baselineVersion; - @ConfigProperty(name = "quarkus.flyway.baseline-description") - String baselineDescription; - - // Quarkus built object - @Inject - Flyway flyway; - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addAsResource("full-config.properties", "application.properties")); - - @Test - @DisplayName("Reads flyway configuration correctly") - public void testFlywayConfigInjection() { - Configuration configuration = flyway.getConfiguration(); - - int locationsCount = locations.size(); - String joinedLocations = String.join(",", locations); - assertEquals(locationsCount, configuration.getLocations().length); - String configuredLocations = Arrays.stream(configuration.getLocations()).map(Location::getPath) - .collect(Collectors.joining(",")); - assertEquals(joinedLocations, configuredLocations); - - assertEquals(sqlMigrationPrefix, configuration.getSqlMigrationPrefix()); - assertEquals(repeatableSqlMigrationPrefix, configuration.getRepeatableSqlMigrationPrefix()); - - assertEquals(tableName, configuration.getTable()); - - int schemasCount = schemaNames.size(); - String joinedSchemas = String.join(",", schemaNames); - assertEquals(schemasCount, configuration.getSchemas().length); - String configuredNames = String.join(",", configuration.getSchemas()); - assertEquals(joinedSchemas, configuredNames); - - assertEquals(connectRetries, configuration.getConnectRetries()); - - assertEquals(baselineOnMigrate, configuration.isBaselineOnMigrate()); - assertEquals(baselineVersion, configuration.getBaselineVersion().getVersion()); - assertEquals(baselineDescription, configuration.getBaselineDescription()); - } -} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java new file mode 100644 index 0000000000000..b3ee0dfce2644 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java @@ -0,0 +1,39 @@ +package io.quarkus.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.flywaydb.core.Flyway; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Same as {@link FlywayExtensionMigrateAtStartTest} for named datasources. + */ +public class FlywayExtensionMigrateAtStartNamedDataSourceTest { + // Quarkus built object + @Inject + @Named("users_flyway") + Flyway flywayUsers; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("db/migration/V1.0.0__Quarkus.sql") + .addAsResource("migrate-at-start-config-named-datasource.properties", "application.properties")); + + @Test + @DisplayName("Migrates at start for datasource named 'users' correctly") + public void testFlywayConfigInjection() { + String currentVersion = flywayUsers.info().current().getVersion().toString(); + // Expected to be 1.0.0 as migration runs at start + assertEquals("1.0.0", currentVersion); + } +} diff --git a/extensions/flyway/deployment/src/test/resources/baseline-on-migrate-named-datasource.properties b/extensions/flyway/deployment/src/test/resources/baseline-on-migrate-named-datasource.properties new file mode 100644 index 0000000000000..b53de3c2144cc --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/baseline-on-migrate-named-datasource.properties @@ -0,0 +1,11 @@ +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:test-quarkus-baseline-on-migrate;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'src/test/resources/h2-init-data.sql' +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.username=sa +quarkus.datasource.users.password=sa + +# Flyway config properties +quarkus.flyway.users.migrate-at-start=true +quarkus.flyway.users.table=test_flyway_history +quarkus.flyway.users.baseline-on-migrate=true +quarkus.flyway.users.baseline-version=0.0.1 +quarkus.flyway.users.baseline-description=Initial description for test diff --git a/extensions/flyway/deployment/src/test/resources/config-empty.properties b/extensions/flyway/deployment/src/test/resources/config-empty.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/flyway/deployment/src/test/resources/config-for-default-datasource-without-flyway.properties b/extensions/flyway/deployment/src/test/resources/config-for-default-datasource-without-flyway.properties new file mode 100644 index 0000000000000..43a81aba22ef3 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-default-datasource-without-flyway.properties @@ -0,0 +1,4 @@ +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.username=sa +quarkus.datasource.password=sa diff --git a/extensions/flyway/deployment/src/test/resources/full-config.properties b/extensions/flyway/deployment/src/test/resources/config-for-default-datasource.properties similarity index 96% rename from extensions/flyway/deployment/src/test/resources/full-config.properties rename to extensions/flyway/deployment/src/test/resources/config-for-default-datasource.properties index 82a222d07b9de..6abc5c4b9d5aa 100644 --- a/extensions/flyway/deployment/src/test/resources/full-config.properties +++ b/extensions/flyway/deployment/src/test/resources/config-for-default-datasource.properties @@ -1,7 +1,9 @@ +#default datasource quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 quarkus.datasource.driver=org.h2.Driver quarkus.datasource.username=sa quarkus.datasource.password=sa + # Flyway config properties quarkus.flyway.connect-retries=10 quarkus.flyway.schemas=TEST_SCHEMA diff --git a/extensions/flyway/deployment/src/test/resources/config-for-missing-named-datasource.properties b/extensions/flyway/deployment/src/test/resources/config-for-missing-named-datasource.properties new file mode 100644 index 0000000000000..e70bf9908bccb --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-missing-named-datasource.properties @@ -0,0 +1,19 @@ +# Datasource for "inventory" +quarkus.datasource.inventory.driver=org.h2.jdbcx.JdbcDataSource +quarkus.datasource.inventory.url=jdbc:h2:tcp://localhost/mem:inventory +quarkus.datasource.inventory.username=username2 +quarkus.datasource.inventory.min-size=2 +quarkus.datasource.inventory.max-size=12 +quarkus.datasource.inventory.xa=true + +# Flyway configuration for missing "users" datasource +quarkus.flyway.users.connect-retries=11 +quarkus.flyway.users.schemas=USERS_TEST_SCHEMA +quarkus.flyway.users.table=users_flyway_quarkus_history +quarkus.flyway.users.locations=db/users/location1,db/users/location2 +quarkus.flyway.users.sql-migration-prefix=U +quarkus.flyway.users.repeatable-sql-migration-prefix=S +quarkus.flyway.users.migrate-at-start=false +quarkus.flyway.users.baseline-on-migrate=true +quarkus.flyway.users.baseline-version=2.0.1 +quarkus.flyway.users.baseline-description=Users initial description \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources-without-default.properties b/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources-without-default.properties new file mode 100644 index 0000000000000..a3829245ab5ba --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources-without-default.properties @@ -0,0 +1,39 @@ +# Datasource for "users" +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:users +quarkus.datasource.users.username=username1 +quarkus.datasource.users.min-size=1 +quarkus.datasource.users.max-size=11 +quarkus.datasource.users.xa=false + +# Datasource for "inventory" +quarkus.datasource.inventory.driver=org.h2.jdbcx.JdbcDataSource +quarkus.datasource.inventory.url=jdbc:h2:tcp://localhost/mem:inventory +quarkus.datasource.inventory.username=username2 +quarkus.datasource.inventory.min-size=2 +quarkus.datasource.inventory.max-size=12 +quarkus.datasource.inventory.xa=true + +# Flyway configuration for "users" datasource +quarkus.flyway.users.connect-retries=11 +quarkus.flyway.users.schemas=USERS_TEST_SCHEMA +quarkus.flyway.users.table=users_flyway_quarkus_history +quarkus.flyway.users.locations=db/users/location1,db/users/location2 +quarkus.flyway.users.sql-migration-prefix=U +quarkus.flyway.users.repeatable-sql-migration-prefix=S +quarkus.flyway.users.migrate-at-start=false +quarkus.flyway.users.baseline-on-migrate=true +quarkus.flyway.users.baseline-version=2.0.1 +quarkus.flyway.users.baseline-description=Users initial description + +# Flyway configuration for "inventory" datasource +quarkus.flyway.inventory.connect-retries=12 +quarkus.flyway.inventory.schemas=INVENTORY_TEST_SCHEMA +quarkus.flyway.inventory.table=inventory_flyway_quarkus_history +quarkus.flyway.inventory.locations=db/inventory/location1,db/inventory/location2 +quarkus.flyway.inventory.sql-migration-prefix=I +quarkus.flyway.inventory.repeatable-sql-migration-prefix=N +quarkus.flyway.inventory.migrate-at-start=false +quarkus.flyway.inventory.baseline-on-migrate=true +quarkus.flyway.inventory.baseline-version=3.0.1 +quarkus.flyway.inventory.baseline-description=Inventory initial description \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources.properties b/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources.properties new file mode 100644 index 0000000000000..1fd2bef2416b0 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-multiple-datasources.properties @@ -0,0 +1,57 @@ +# Datasource default +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.username=sa +quarkus.datasource.password=sa + +# Datasource for "users" +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:users +quarkus.datasource.users.username=username1 +quarkus.datasource.users.min-size=1 +quarkus.datasource.users.max-size=11 +quarkus.datasource.users.xa=false + +# Datasource for "inventory" +quarkus.datasource.inventory.driver=org.h2.jdbcx.JdbcDataSource +quarkus.datasource.inventory.url=jdbc:h2:tcp://localhost/mem:inventory +quarkus.datasource.inventory.username=username2 +quarkus.datasource.inventory.min-size=2 +quarkus.datasource.inventory.max-size=12 +quarkus.datasource.inventory.xa=true + +# Flyway configuration for default datasource +quarkus.flyway.connect-retries=10 +quarkus.flyway.schemas=TEST_SCHEMA +quarkus.flyway.table=flyway_quarkus_history +quarkus.flyway.locations=db/location1,db/location2 +quarkus.flyway.sql-migration-prefix=X +quarkus.flyway.repeatable-sql-migration-prefix=K +quarkus.flyway.migrate-at-start=false +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=2.0.1 +quarkus.flyway.baseline-description=Initial description + +# Flyway configuration for "users" datasource +quarkus.flyway.users.connect-retries=11 +quarkus.flyway.users.schemas=USERS_TEST_SCHEMA +quarkus.flyway.users.table=users_flyway_quarkus_history +quarkus.flyway.users.locations=db/users/location1,db/users/location2 +quarkus.flyway.users.sql-migration-prefix=U +quarkus.flyway.users.repeatable-sql-migration-prefix=S +quarkus.flyway.users.migrate-at-start=false +quarkus.flyway.users.baseline-on-migrate=true +quarkus.flyway.users.baseline-version=2.0.1 +quarkus.flyway.users.baseline-description=Users initial description + +# Flyway configuration for "inventory" datasource +quarkus.flyway.inventory.connect-retries=12 +quarkus.flyway.inventory.schemas=INVENTORY_TEST_SCHEMA +quarkus.flyway.inventory.table=inventory_flyway_quarkus_history +quarkus.flyway.inventory.locations=db/inventory/location1,db/inventory/location2 +quarkus.flyway.inventory.sql-migration-prefix=I +quarkus.flyway.inventory.repeatable-sql-migration-prefix=N +quarkus.flyway.inventory.migrate-at-start=false +quarkus.flyway.inventory.baseline-on-migrate=true +quarkus.flyway.inventory.baseline-version=3.0.1 +quarkus.flyway.inventory.baseline-description=Inventory initial description \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-default.properties b/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-default.properties new file mode 100644 index 0000000000000..3fd4b4629882b --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-default.properties @@ -0,0 +1,19 @@ +# Datasource for "users" +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:users +quarkus.datasource.users.username=username1 +quarkus.datasource.users.min-size=1 +quarkus.datasource.users.max-size=11 +quarkus.datasource.users.xa=false + +# Flyway configuration for "users" datasource +quarkus.flyway.users.connect-retries=11 +quarkus.flyway.users.schemas=USERS_TEST_SCHEMA +quarkus.flyway.users.table=users_flyway_quarkus_history +quarkus.flyway.users.locations=db/users/location1,db/users/location2 +quarkus.flyway.users.sql-migration-prefix=U +quarkus.flyway.users.repeatable-sql-migration-prefix=S +quarkus.flyway.users.migrate-at-start=false +quarkus.flyway.users.baseline-on-migrate=true +quarkus.flyway.users.baseline-version=2.0.1 +quarkus.flyway.users.baseline-description=Users initial description \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-flyway.properties b/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-flyway.properties new file mode 100644 index 0000000000000..dc0ac10f57560 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/config-for-named-datasource-without-flyway.properties @@ -0,0 +1,4 @@ +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:test_quarkus;DB_CLOSE_DELAY=-1 +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.username=sa +quarkus.datasource.users.password=sa diff --git a/extensions/flyway/deployment/src/test/resources/migrate-at-start-config-named-datasource.properties b/extensions/flyway/deployment/src/test/resources/migrate-at-start-config-named-datasource.properties new file mode 100644 index 0000000000000..8eddd6a9413de --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/migrate-at-start-config-named-datasource.properties @@ -0,0 +1,6 @@ +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start-users;DB_CLOSE_DELAY=-1 +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.username=sa +quarkus.datasource.users.password=sa +# Flyway config properties for datasource named users +quarkus.flyway.users.migrate-at-start=true diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml index c3e8a93eef5ee..f8f9e939bd1c1 100644 --- a/extensions/flyway/runtime/pom.xml +++ b/extensions/flyway/runtime/pom.xml @@ -33,6 +33,13 @@ org.graalvm.nativeimage svm + + + + io.quarkus + quarkus-junit5-internal + test + diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java new file mode 100644 index 0000000000000..6008886ef7d9c --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java @@ -0,0 +1,60 @@ +package io.quarkus.flyway; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Named; +import javax.inject.Qualifier; + +/** + * Qualifier used to specify which datasource will be used and therefore which flyway instance will be injected. implements FlywayDataSource { + + public static final Literal INSTANCE = of(""); + + private static final long serialVersionUID = 1L; + + private final String value; + + public static Literal of(String value) { + return new Literal(value); + } + + @Override + public String value() { + return value; + } + + private Literal(String value) { + this.value = value; + } + + @Override + public String toString() { + return "FlywayDataSourceLiteral [value=" + value + "]"; + } + } +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java index a888dbabd7bff..282b4e56ecf60 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java @@ -1,7 +1,7 @@ package io.quarkus.flyway.runtime; -import java.util.List; -import java.util.Optional; +import java.util.Collections; +import java.util.Map; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -9,13 +9,36 @@ @ConfigRoot(name = "flyway", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public final class FlywayBuildConfig { + /* + * Creates a {@link FlywayMultiDatasourceBuildConfig} with default settings. + * + * @return {@link FlywayMultiDatasourceBuildConfig} + */ + public static final FlywayBuildConfig defaultConfig() { + return new FlywayBuildConfig(); + } + + /** + * Gets the {@link FlywayDataSourceBuildConfig} for the given datasource name.
+ * The name of the default datasource is an empty {@link String}. + * + * @param dataSourceName {@link String} + * @return {@link FlywayDataSourceBuildConfig} + * @throws NullPointerException if dataSourceName is null. + */ + public FlywayDataSourceBuildConfig getConfigForDataSourceName(String dataSourceName) { + return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildConfig.defaultConfig()); + } + + /** + * Flyway configuration for the default datasource. + */ + @ConfigItem(name = ConfigItem.PARENT) + public FlywayDataSourceBuildConfig defaultDataSource = FlywayDataSourceBuildConfig.defaultConfig(); + /** - * Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. - * Unprefixed locations or locations starting with classpath: point to a package on the classpath and may contain both SQL - * and Java-based migrations. - * Locations starting with filesystem: point to a directory on the filesystem, may only contain SQL migrations and are only - * scanned recursively down non-hidden directories. + * Flyway configurations for named datasources. */ - @ConfigItem - public Optional> locations; -} + @ConfigItem(name = ConfigItem.PARENT) + public Map namedDataSources = Collections.emptyMap(); +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java new file mode 100644 index 0000000000000..b23df4694fae6 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java @@ -0,0 +1,33 @@ +package io.quarkus.flyway.runtime; + +import javax.sql.DataSource; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.configuration.FluentConfiguration; + +class FlywayCreator { + private final FlywayDataSourceRuntimeConfig flywayRuntimeConfig; + private final FlywayDataSourceBuildConfig flywayBuildConfig; + + public FlywayCreator(FlywayDataSourceRuntimeConfig flywayRuntimeConfig, FlywayDataSourceBuildConfig flywayBuildConfig) { + this.flywayRuntimeConfig = flywayRuntimeConfig; + this.flywayBuildConfig = flywayBuildConfig; + } + + public Flyway createFlyway(DataSource dataSource) { + FluentConfiguration configure = Flyway.configure(); + configure.dataSource(dataSource); + flywayRuntimeConfig.connectRetries.ifPresent(configure::connectRetries); + flywayRuntimeConfig.schemas.ifPresent(list -> configure.schemas(list.toArray(new String[0]))); + flywayRuntimeConfig.table.ifPresent(configure::table); + flywayBuildConfig.locations.ifPresent(list -> configure.locations(list.toArray(new String[0]))); + flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); + flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); + + configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate); + flywayRuntimeConfig.baselineVersion.ifPresent(configure::baselineVersion); + flywayRuntimeConfig.baselineDescription.ifPresent(configure::baselineDescription); + + return configure.load(); + } +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java new file mode 100644 index 0000000000000..5ee8f18f7dcd8 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java @@ -0,0 +1,33 @@ +package io.quarkus.flyway.runtime; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public final class FlywayDataSourceBuildConfig { + + private static final String DEFAULT_LOCATION = "db/migration"; + + /** + * Creates a {@link FlywayDataSourceBuildConfig} with default settings. + * + * @return {@link FlywayDataSourceBuildConfig} + */ + public static final FlywayDataSourceBuildConfig defaultConfig() { + return new FlywayDataSourceBuildConfig(); + } + + /** + * Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. + * Unprefixed locations or locations starting with classpath: point to a package on the classpath and may contain both SQL + * and Java-based migrations. + * Locations starting with filesystem: point to a directory on the filesystem, may only contain SQL migrations and are only + * scanned recursively down non-hidden directories. + */ + @ConfigItem + public Optional> locations = Optional.of(Collections.singletonList(DEFAULT_LOCATION)); +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java new file mode 100644 index 0000000000000..7f29123d2ca54 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java @@ -0,0 +1,93 @@ +package io.quarkus.flyway.runtime; + +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public final class FlywayDataSourceRuntimeConfig { + /** + * Creates a {@link FlywayDataSourceRuntimeConfig} with default settings. + * + * @return {@link FlywayDataSourceRuntimeConfig} + */ + public static final FlywayDataSourceRuntimeConfig defaultConfig() { + return new FlywayDataSourceRuntimeConfig(); + } + + /** + * The maximum number of retries when attempting to connect to the database. After each failed attempt, Flyway will wait 1 + * second before attempting to connect again, up to the maximum number of times specified by connectRetries. + */ + @ConfigItem + public OptionalInt connectRetries = OptionalInt.empty(); + /** + * Comma-separated case-sensitive list of schemas managed by Flyway. + * The first schema in the list will be automatically set as the default one during the migration. + * It will also be the one containing the schema history table. + */ + @ConfigItem + public Optional> schemas = Optional.empty(); + /** + * The name of Flyway's schema history table. + * By default (single-schema mode) the schema history table is placed in the default schema for the connection provided by + * the datasource. + * When the flyway.schemas property is set (multi-schema mode), the schema history table is placed in the first schema of + * the list. + */ + @ConfigItem + public Optional table = Optional.empty(); + + /** + * The file name prefix for versioned SQL migrations. + * + * Versioned SQL migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix , which using + * the defaults translates to V1.1__My_description.sql + */ + @ConfigItem + public Optional sqlMigrationPrefix = Optional.empty(); + + /** + * The file name prefix for repeatable SQL migrations. + * + * Repeatable SQL migrations have the following file name structure: prefixSeparatorDESCRIPTIONsuffix , which using the + * defaults translates to R__My_description.sql + */ + @ConfigItem + public Optional repeatableSqlMigrationPrefix = Optional.empty(); + + /** + * true to execute Flyway clean command automatically when the application starts, false otherwise. + * + */ + @ConfigItem + public boolean cleanAtStart; + + /** + * true to execute Flyway automatically when the application starts, false otherwise. + * + */ + @ConfigItem + public boolean migrateAtStart; + + /** + * Enable the creation of the history table if it does not exist already. + */ + @ConfigItem + public boolean baselineOnMigrate; + + /** + * The initial baseline version. + */ + @ConfigItem + public Optional baselineVersion = Optional.empty(); + + /** + * The description to tag an existing schema with when executing baseline. + */ + @ConfigItem + public Optional baselineDescription = Optional.empty(); +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java index 5268403cdad12..dcb79a1c132b4 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java @@ -2,38 +2,30 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Produces; import javax.inject.Inject; +import javax.sql.DataSource; import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.configuration.FluentConfiguration; - -import io.agroal.api.AgroalDataSource; @ApplicationScoped public class FlywayProducer { + private static final String ERROR_NOT_READY = "The flyway settings are not ready to be consumed: the %s configuration has not been injected yet"; + @Inject - AgroalDataSource dataSource; + @Default + Instance defaultDataSource; + private FlywayRuntimeConfig flywayRuntimeConfig; private FlywayBuildConfig flywayBuildConfig; @Produces @Dependent + @Default public Flyway produceFlyway() { - FluentConfiguration configure = Flyway.configure(); - configure.dataSource(dataSource); - flywayRuntimeConfig.connectRetries.ifPresent(configure::connectRetries); - flywayRuntimeConfig.schemas.ifPresent(l -> configure.schemas(l.toArray(new String[0]))); - flywayRuntimeConfig.table.ifPresent(configure::table); - flywayBuildConfig.locations.ifPresent(l -> configure.locations(l.toArray(new String[0]))); - flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); - flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); - - configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate); - flywayRuntimeConfig.baselineVersion.ifPresent(configure::baselineVersion); - flywayRuntimeConfig.baselineDescription.ifPresent(configure::baselineDescription); - - return configure.load(); + return createDefaultFlyway(defaultDataSource.get()); } public void setFlywayRuntimeConfig(FlywayRuntimeConfig flywayRuntimeConfig) { @@ -43,4 +35,30 @@ public void setFlywayRuntimeConfig(FlywayRuntimeConfig flywayRuntimeConfig) { public void setFlywayBuildConfig(FlywayBuildConfig flywayBuildConfig) { this.flywayBuildConfig = flywayBuildConfig; } -} + + private Flyway createDefaultFlyway(DataSource dataSource) { + return new FlywayCreator(getFlywayRuntimeConfig().defaultDataSource, getFlywayBuildConfig().defaultDataSource) + .createFlyway(dataSource); + } + + public Flyway createFlyway(DataSource dataSource, String dataSourceName) { + return new FlywayCreator(getFlywayRuntimeConfig().getConfigForDataSourceName(dataSourceName), + getFlywayBuildConfig().getConfigForDataSourceName(dataSourceName)) + .createFlyway(dataSource); + } + + private FlywayRuntimeConfig getFlywayRuntimeConfig() { + return failIfNotReady(flywayRuntimeConfig, "runtime"); + } + + private FlywayBuildConfig getFlywayBuildConfig() { + return failIfNotReady(flywayBuildConfig, "build"); + } + + private static T failIfNotReady(T config, String name) { + if (config == null) { + throw new IllegalStateException(String.format(ERROR_NOT_READY, name)); + } + return config; + } +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 76e5dd17416d6..981ded70c90ea 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -1,9 +1,16 @@ package io.quarkus.flyway.runtime; +import java.lang.annotation.Annotation; +import java.util.Map.Entry; + +import javax.enterprise.inject.Default; +import javax.enterprise.util.AnnotationLiteral; + import org.flywaydb.core.Flyway; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.runtime.annotations.Recorder; @Recorder @@ -21,13 +28,29 @@ public void configureFlywayProperties(FlywayRuntimeConfig flywayRuntimeConfig, B } public void doStartActions(FlywayRuntimeConfig config, BeanContainer container) { - if (config.cleanAtStart) { - Flyway flyway = container.instance(Flyway.class); - flyway.clean(); + if (config.defaultDataSource.cleanAtStart) { + clean(container, Default.Literal.INSTANCE); } - if (config.migrateAtStart) { - Flyway flyway = container.instance(Flyway.class); - flyway.migrate(); + if (config.defaultDataSource.migrateAtStart) { + migrate(container, Default.Literal.INSTANCE); } + for (Entry configPerDataSource : config.namedDataSources.entrySet()) { + if (configPerDataSource.getValue().cleanAtStart) { + clean(container, FlywayDataSource.Literal.of(configPerDataSource.getKey())); + } + if (configPerDataSource.getValue().migrateAtStart) { + migrate(container, FlywayDataSource.Literal.of(configPerDataSource.getKey())); + } + } + } + + private void clean(BeanContainer container, AnnotationLiteral qualifier) { + Flyway flyway = container.instance(Flyway.class, qualifier); + flyway.clean(); + } + + private void migrate(BeanContainer container, AnnotationLiteral qualifier) { + Flyway flyway = container.instance(Flyway.class, qualifier); + flyway.migrate(); } -} +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index 86936fa2bc697..d800902d12d50 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -1,8 +1,7 @@ package io.quarkus.flyway.runtime; -import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; +import java.util.Collections; +import java.util.Map; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -10,76 +9,36 @@ @ConfigRoot(name = "flyway", phase = ConfigPhase.RUN_TIME) public final class FlywayRuntimeConfig { - /** - * The maximum number of retries when attempting to connect to the database. After each failed attempt, Flyway will wait 1 - * second before attempting to connect again, up to the maximum number of times specified by connectRetries. - */ - @ConfigItem - public OptionalInt connectRetries; - /** - * Comma-separated case-sensitive list of schemas managed by Flyway. - * The first schema in the list will be automatically set as the default one during the migration. - * It will also be the one containing the schema history table. - */ - @ConfigItem - public Optional> schemas; - /** - * The name of Flyway's schema history table. - * By default (single-schema mode) the schema history table is placed in the default schema for the connection provided by - * the datasource. - * When the flyway.schemas property is set (multi-schema mode), the schema history table is placed in the first schema of - * the list. - */ - @ConfigItem - public Optional table; - - /** - * The file name prefix for versioned SQL migrations. - * - * Versioned SQL migrations have the following file name structure: prefixVERSIONseparatorDESCRIPTIONsuffix , which using - * the defaults translates to V1.1__My_description.sql - */ - @ConfigItem - public Optional sqlMigrationPrefix; - - /** - * The file name prefix for repeatable SQL migrations. - * - * Repeatable SQL migrations have the following file name structure: prefixSeparatorDESCRIPTIONsuffix , which using the - * defaults translates to R__My_description.sql - */ - @ConfigItem - public Optional repeatableSqlMigrationPrefix; - - /** - * true to execute Flyway clean command automatically when the application starts, false otherwise. - * - */ - @ConfigItem - public boolean cleanAtStart; - - /** - * true to execute Flyway automatically when the application starts, false otherwise. - * + /* + * Creates a {@link FlywayMultiDatasourceRuntimeConfig} with default settings. + * + * @return {@link FlywayMultiDatasourceRuntimeConfig} */ - @ConfigItem - public boolean migrateAtStart; + public static final FlywayRuntimeConfig defaultConfig() { + return new FlywayRuntimeConfig(); + } /** - * Enable the creation of the history table if it does not exist already. + * Gets the {@link FlywayDataSourceRuntimeConfig} for the given datasource name.
+ * The name of the default datasource is an empty {@link String}. + * + * @param dataSourceName {@link String} + * @return {@link FlywayDataSourceRuntimeConfig} + * @throws NullPointerException if dataSourceName is null. */ - @ConfigItem - public boolean baselineOnMigrate; + public FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { + return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceRuntimeConfig.defaultConfig()); + } /** - * The initial baseline version. + * Flyway configuration for the default datasource. */ - @ConfigItem - public Optional baselineVersion; + @ConfigItem(name = ConfigItem.PARENT) + public FlywayDataSourceRuntimeConfig defaultDataSource = FlywayDataSourceRuntimeConfig.defaultConfig(); /** - * The description to tag an existing schema with when executing baseline. + * Flyway configurations for named datasources. */ - @ConfigItem - public Optional baselineDescription; -} + @ConfigItem(name = ConfigItem.PARENT) + public Map namedDataSources = Collections.emptyMap(); +} \ No newline at end of file diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java new file mode 100644 index 0000000000000..e46f5b9e34ef6 --- /dev/null +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java @@ -0,0 +1,165 @@ +package io.quarkus.flyway.runtime; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.Location; +import org.flywaydb.core.api.configuration.Configuration; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FlywayCreatorTest { + + private FlywayDataSourceRuntimeConfig runtimeConfig = FlywayDataSourceRuntimeConfig.defaultConfig(); + private FlywayDataSourceBuildConfig buildConfig = FlywayDataSourceBuildConfig.defaultConfig(); + private Configuration defaultConfig = Flyway.configure().load().getConfiguration(); + + /** + * class under test. + */ + private FlywayCreator creator; + + @Test + @DisplayName("locations default matches flyway default") + void testLocationsDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(pathList(defaultConfig.getLocations()), pathList(createdFlywayConfig().getLocations())); + } + + @Test + @DisplayName("locations carried over from configuration") + void testLocationsOverridden() { + buildConfig.locations = Optional.of(Arrays.asList("db/migrations", "db/something")); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(buildConfig.locations.get(), pathList(createdFlywayConfig().getLocations())); + } + + @Test + @DisplayName("not configured locations replaced by default") + void testNotPresentLocationsOverridden() { + buildConfig.locations = Optional.empty(); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(pathList(defaultConfig.getLocations()), pathList(createdFlywayConfig().getLocations())); + } + + @Test + @DisplayName("baseline description default matches flyway default") + void testBaselineDescriptionDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getBaselineDescription(), createdFlywayConfig().getBaselineDescription()); + } + + @Test + @DisplayName("baseline description carried over from configuration") + void testBaselineDescriptionOverridden() { + runtimeConfig.baselineDescription = Optional.of("baselineDescription"); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.baselineDescription.get(), createdFlywayConfig().getBaselineDescription()); + } + + @Test + @DisplayName("baseline version default matches flyway default") + void testBaselineVersionDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getBaselineVersion(), createdFlywayConfig().getBaselineVersion()); + } + + @Test + @DisplayName("baseline version carried over from configuration") + void testBaselineVersionOverridden() { + runtimeConfig.baselineVersion = Optional.of("0.1.2"); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.baselineVersion.get(), createdFlywayConfig().getBaselineVersion().getVersion()); + } + + @Test + @DisplayName("connection retries default matches flyway default") + void testConnectionRetriesDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getConnectRetries(), createdFlywayConfig().getConnectRetries()); + } + + @Test + @DisplayName("connection retries carried over from configuration") + void testConnectionRetriesOverridden() { + runtimeConfig.connectRetries = OptionalInt.of(12); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.connectRetries.getAsInt(), createdFlywayConfig().getConnectRetries()); + } + + @Test + @DisplayName("repeatable SQL migration prefix default matches flyway default") + void testRepeatableSqlMigrationPrefixDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getRepeatableSqlMigrationPrefix(), createdFlywayConfig().getRepeatableSqlMigrationPrefix()); + } + + @Test + @DisplayName("repeatable SQL migration prefix carried over from configuration") + void testRepeatableSqlMigrationPrefixOverridden() { + runtimeConfig.repeatableSqlMigrationPrefix = Optional.of("A"); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.repeatableSqlMigrationPrefix.get(), createdFlywayConfig().getRepeatableSqlMigrationPrefix()); + } + + @Test + @DisplayName("schemas default matches flyway default") + void testSchemasDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(asList(defaultConfig.getSchemas()), asList(createdFlywayConfig().getSchemas())); + } + + @Test + @DisplayName("schemas carried over from configuration") + void testSchemasOverridden() { + runtimeConfig.schemas = Optional.of(Arrays.asList("TEST_SCHEMA_1", "TEST_SCHEMA_2")); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.schemas.get(), asList(createdFlywayConfig().getSchemas())); + } + + @Test + @DisplayName("SQL migration prefix default matches flyway default") + void testSqlMigrationPrefixDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getSqlMigrationPrefix(), createdFlywayConfig().getSqlMigrationPrefix()); + } + + @Test + @DisplayName("SQL migration prefix carried over from configuration") + void testSqlMigrationPrefixOverridden() { + runtimeConfig.sqlMigrationPrefix = Optional.of("M"); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.sqlMigrationPrefix.get(), createdFlywayConfig().getSqlMigrationPrefix()); + } + + @Test + @DisplayName("table default matches flyway default") + void testTableDefault() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(defaultConfig.getTable(), createdFlywayConfig().getTable()); + } + + @Test + @DisplayName("table carried over from configuration") + void testTableOverridden() { + runtimeConfig.table = Optional.of("flyway_history_test_table"); + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.table.get(), createdFlywayConfig().getTable()); + } + + private static List pathList(Location[] locations) { + return Stream.of(locations).map(Location::getPath).collect(Collectors.toList()); + } + + private Configuration createdFlywayConfig() { + return creator.createFlyway(null).getConfiguration(); + } +} diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java new file mode 100644 index 0000000000000..42ef4c03b08dd --- /dev/null +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java @@ -0,0 +1,46 @@ +package io.quarkus.flyway.runtime; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FlywayProducerTest { + + private static final String DEFAULT_DATASOURCE = ""; + private FlywayBuildConfig buildDataSourceConfig = FlywayBuildConfig.defaultConfig(); + private FlywayRuntimeConfig runtimeDataSourceConfig = FlywayRuntimeConfig.defaultConfig(); + + /** + * class under test. + */ + private FlywayProducer flywayProducer = new FlywayProducer(); + + @BeforeEach + void beforeEach() { + flywayProducer.setFlywayBuildConfig(buildDataSourceConfig); + flywayProducer.setFlywayRuntimeConfig(runtimeDataSourceConfig); + } + + @Test + @DisplayName("flyway can be created successfully") + void testCreatesFlywaySuccessfully() { + assertNotNull(flywayProducer.createFlyway(null, DEFAULT_DATASOURCE)); + } + + @Test + @DisplayName("fail on missing build configuration") + void testMissingBuildConfig() { + flywayProducer.setFlywayBuildConfig(null); + assertThrows(IllegalStateException.class, () -> flywayProducer.createFlyway(null, DEFAULT_DATASOURCE)); + } + + @Test + @DisplayName("fail on missing runtime configuration") + void testMissingRuntimeConfig() { + flywayProducer.setFlywayRuntimeConfig(null); + assertThrows(IllegalStateException.class, () -> flywayProducer.createFlyway(null, DEFAULT_DATASOURCE)); + } +} \ No newline at end of file From fd4606c4cb1051e2c5112ce6224d28586df6fc4f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 16:33:28 +0100 Subject: [PATCH 294/602] Some minor Flyway configuration adjustments --- .../io/quarkus/flyway/FlywayProcessor.java | 13 ++++----- ...Config.java => FlywayBuildTimeConfig.java} | 28 +++++++++---------- .../quarkus/flyway/runtime/FlywayCreator.java | 9 +++--- ...a => FlywayDataSourceBuildTimeConfig.java} | 23 ++++++++------- .../FlywayDataSourceRuntimeConfig.java | 5 +++- .../flyway/runtime/FlywayProducer.java | 6 ++-- .../flyway/runtime/FlywayRecorder.java | 2 +- .../flyway/runtime/FlywayCreatorTest.java | 7 ++--- .../flyway/runtime/FlywayProducerTest.java | 2 +- 9 files changed, 48 insertions(+), 47 deletions(-) rename extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/{FlywayBuildConfig.java => FlywayBuildTimeConfig.java} (51%) rename extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/{FlywayDataSourceBuildConfig.java => FlywayDataSourceBuildTimeConfig.java} (56%) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index 1bcea8226fb9b..d8e6f219e6829 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -14,7 +14,6 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashSet; @@ -42,8 +41,7 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.recording.RecorderContext; -import io.quarkus.flyway.runtime.FlywayBuildConfig; -import io.quarkus.flyway.runtime.FlywayDataSourceBuildConfig; +import io.quarkus.flyway.runtime.FlywayBuildTimeConfig; import io.quarkus.flyway.runtime.FlywayProducer; import io.quarkus.flyway.runtime.FlywayRecorder; import io.quarkus.flyway.runtime.FlywayRuntimeConfig; @@ -59,7 +57,7 @@ class FlywayProcessor { /** * Flyway build config */ - FlywayBuildConfig flywayBuildConfig; + FlywayBuildTimeConfig flywayBuildConfig; @BuildStep CapabilityBuildItem capability() { @@ -133,19 +131,18 @@ private void registerNativeImageResources(BuildProducer * A {@link LinkedHashSet} is used to avoid duplications. - * + * * @param dataSourceInitializedBuildItem {@link DataSourceInitializedBuildItem} * @return {@link Collection} of {@link String}s */ private Collection getMigrationLocations(DataSourceInitializedBuildItem dataSourceInitializedBuildItem) { - List defaultLocations = FlywayDataSourceBuildConfig.defaultConfig().locations.orElse(Collections.emptyList()); Collection dataSourceNames = DataSourceInitializedBuildItem.dataSourceNamesOf(dataSourceInitializedBuildItem); Collection migrationLocations = dataSourceNames.stream() .map(flywayBuildConfig::getConfigForDataSourceName) - .flatMap(config -> config.locations.orElse(defaultLocations).stream()) + .flatMap(config -> config.locations.stream()) .collect(Collectors.toCollection(LinkedHashSet::new)); if (DataSourceInitializedBuildItem.isDefaultDataSourcePresent(dataSourceInitializedBuildItem)) { - migrationLocations.addAll(flywayBuildConfig.defaultDataSource.locations.orElse(defaultLocations)); + migrationLocations.addAll(flywayBuildConfig.defaultDataSource.locations); } return migrationLocations; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java similarity index 51% rename from extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java rename to extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java index 282b4e56ecf60..554e1e81b8936 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java @@ -8,37 +8,35 @@ import io.quarkus.runtime.annotations.ConfigRoot; @ConfigRoot(name = "flyway", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) -public final class FlywayBuildConfig { - /* - * Creates a {@link FlywayMultiDatasourceBuildConfig} with default settings. - * - * @return {@link FlywayMultiDatasourceBuildConfig} - */ - public static final FlywayBuildConfig defaultConfig() { - return new FlywayBuildConfig(); +public final class FlywayBuildTimeConfig { + + public static final FlywayBuildTimeConfig defaultConfig() { + FlywayBuildTimeConfig defaultConfig = new FlywayBuildTimeConfig(); + defaultConfig.defaultDataSource = FlywayDataSourceBuildTimeConfig.defaultConfig(); + return defaultConfig; } /** - * Gets the {@link FlywayDataSourceBuildConfig} for the given datasource name.
+ * Gets the {@link FlywayDataSourceBuildTimeConfig} for the given datasource name.
* The name of the default datasource is an empty {@link String}. - * + * * @param dataSourceName {@link String} - * @return {@link FlywayDataSourceBuildConfig} + * @return {@link FlywayDataSourceBuildTimeConfig} * @throws NullPointerException if dataSourceName is null. */ - public FlywayDataSourceBuildConfig getConfigForDataSourceName(String dataSourceName) { - return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildConfig.defaultConfig()); + public FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { + return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildTimeConfig.defaultConfig()); } /** * Flyway configuration for the default datasource. */ @ConfigItem(name = ConfigItem.PARENT) - public FlywayDataSourceBuildConfig defaultDataSource = FlywayDataSourceBuildConfig.defaultConfig(); + public FlywayDataSourceBuildTimeConfig defaultDataSource; /** * Flyway configurations for named datasources. */ @ConfigItem(name = ConfigItem.PARENT) - public Map namedDataSources = Collections.emptyMap(); + public Map namedDataSources = Collections.emptyMap(); } \ No newline at end of file diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java index b23df4694fae6..0f0ea851eca87 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java @@ -7,11 +7,12 @@ class FlywayCreator { private final FlywayDataSourceRuntimeConfig flywayRuntimeConfig; - private final FlywayDataSourceBuildConfig flywayBuildConfig; + private final FlywayDataSourceBuildTimeConfig flywayBuildTimeConfig; - public FlywayCreator(FlywayDataSourceRuntimeConfig flywayRuntimeConfig, FlywayDataSourceBuildConfig flywayBuildConfig) { + public FlywayCreator(FlywayDataSourceRuntimeConfig flywayRuntimeConfig, + FlywayDataSourceBuildTimeConfig flywayBuildTimeConfig) { this.flywayRuntimeConfig = flywayRuntimeConfig; - this.flywayBuildConfig = flywayBuildConfig; + this.flywayBuildTimeConfig = flywayBuildTimeConfig; } public Flyway createFlyway(DataSource dataSource) { @@ -20,7 +21,7 @@ public Flyway createFlyway(DataSource dataSource) { flywayRuntimeConfig.connectRetries.ifPresent(configure::connectRetries); flywayRuntimeConfig.schemas.ifPresent(list -> configure.schemas(list.toArray(new String[0]))); flywayRuntimeConfig.table.ifPresent(configure::table); - flywayBuildConfig.locations.ifPresent(list -> configure.locations(list.toArray(new String[0]))); + configure.locations(flywayBuildTimeConfig.locations.toArray(new String[0])); flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java similarity index 56% rename from extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java rename to extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java index 5ee8f18f7dcd8..ee3e41f382c38 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceBuildTimeConfig.java @@ -1,33 +1,36 @@ package io.quarkus.flyway.runtime; -import java.util.Collections; +import java.util.Arrays; import java.util.List; -import java.util.Optional; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @ConfigGroup -public final class FlywayDataSourceBuildConfig { +public final class FlywayDataSourceBuildTimeConfig { private static final String DEFAULT_LOCATION = "db/migration"; /** - * Creates a {@link FlywayDataSourceBuildConfig} with default settings. - * - * @return {@link FlywayDataSourceBuildConfig} + * Creates a {@link FlywayDataSourceBuildTimeConfig} with default settings. + * + * @return {@link FlywayDataSourceBuildTimeConfig} */ - public static final FlywayDataSourceBuildConfig defaultConfig() { - return new FlywayDataSourceBuildConfig(); + public static final FlywayDataSourceBuildTimeConfig defaultConfig() { + FlywayDataSourceBuildTimeConfig defaultConfig = new FlywayDataSourceBuildTimeConfig(); + defaultConfig.locations = Arrays.asList(DEFAULT_LOCATION); + return defaultConfig; } /** * Comma-separated list of locations to scan recursively for migrations. The location type is determined by its prefix. + *

* Unprefixed locations or locations starting with classpath: point to a package on the classpath and may contain both SQL * and Java-based migrations. + *

* Locations starting with filesystem: point to a directory on the filesystem, may only contain SQL migrations and are only * scanned recursively down non-hidden directories. */ - @ConfigItem - public Optional> locations = Optional.of(Collections.singletonList(DEFAULT_LOCATION)); + @ConfigItem(defaultValue = DEFAULT_LOCATION) + public List locations; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java index 7f29123d2ca54..dbf08839ae78f 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java @@ -9,9 +9,10 @@ @ConfigGroup public final class FlywayDataSourceRuntimeConfig { + /** * Creates a {@link FlywayDataSourceRuntimeConfig} with default settings. - * + * * @return {@link FlywayDataSourceRuntimeConfig} */ public static final FlywayDataSourceRuntimeConfig defaultConfig() { @@ -24,6 +25,7 @@ public static final FlywayDataSourceRuntimeConfig defaultConfig() { */ @ConfigItem public OptionalInt connectRetries = OptionalInt.empty(); + /** * Comma-separated case-sensitive list of schemas managed by Flyway. * The first schema in the list will be automatically set as the default one during the migration. @@ -31,6 +33,7 @@ public static final FlywayDataSourceRuntimeConfig defaultConfig() { */ @ConfigItem public Optional> schemas = Optional.empty(); + /** * The name of Flyway's schema history table. * By default (single-schema mode) the schema history table is placed in the default schema for the connection provided by diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java index dcb79a1c132b4..51b1dd3f9bd60 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java @@ -19,7 +19,7 @@ public class FlywayProducer { Instance defaultDataSource; private FlywayRuntimeConfig flywayRuntimeConfig; - private FlywayBuildConfig flywayBuildConfig; + private FlywayBuildTimeConfig flywayBuildConfig; @Produces @Dependent @@ -32,7 +32,7 @@ public void setFlywayRuntimeConfig(FlywayRuntimeConfig flywayRuntimeConfig) { this.flywayRuntimeConfig = flywayRuntimeConfig; } - public void setFlywayBuildConfig(FlywayBuildConfig flywayBuildConfig) { + public void setFlywayBuildConfig(FlywayBuildTimeConfig flywayBuildConfig) { this.flywayBuildConfig = flywayBuildConfig; } @@ -51,7 +51,7 @@ private FlywayRuntimeConfig getFlywayRuntimeConfig() { return failIfNotReady(flywayRuntimeConfig, "runtime"); } - private FlywayBuildConfig getFlywayBuildConfig() { + private FlywayBuildTimeConfig getFlywayBuildConfig() { return failIfNotReady(flywayBuildConfig, "build"); } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 981ded70c90ea..e19d1f6674394 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -16,7 +16,7 @@ @Recorder public class FlywayRecorder { - public BeanContainerListener setFlywayBuildConfig(FlywayBuildConfig flywayBuildConfig) { + public BeanContainerListener setFlywayBuildConfig(FlywayBuildTimeConfig flywayBuildConfig) { return beanContainer -> { FlywayProducer producer = beanContainer.instance(FlywayProducer.class); producer.setFlywayBuildConfig(flywayBuildConfig); diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java index e46f5b9e34ef6..174dc6f5781ce 100644 --- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java @@ -19,7 +19,7 @@ class FlywayCreatorTest { private FlywayDataSourceRuntimeConfig runtimeConfig = FlywayDataSourceRuntimeConfig.defaultConfig(); - private FlywayDataSourceBuildConfig buildConfig = FlywayDataSourceBuildConfig.defaultConfig(); + private FlywayDataSourceBuildTimeConfig buildConfig = FlywayDataSourceBuildTimeConfig.defaultConfig(); private Configuration defaultConfig = Flyway.configure().load().getConfiguration(); /** @@ -37,15 +37,14 @@ void testLocationsDefault() { @Test @DisplayName("locations carried over from configuration") void testLocationsOverridden() { - buildConfig.locations = Optional.of(Arrays.asList("db/migrations", "db/something")); + buildConfig.locations = Arrays.asList("db/migrations", "db/something"); creator = new FlywayCreator(runtimeConfig, buildConfig); - assertEquals(buildConfig.locations.get(), pathList(createdFlywayConfig().getLocations())); + assertEquals(buildConfig.locations, pathList(createdFlywayConfig().getLocations())); } @Test @DisplayName("not configured locations replaced by default") void testNotPresentLocationsOverridden() { - buildConfig.locations = Optional.empty(); creator = new FlywayCreator(runtimeConfig, buildConfig); assertEquals(pathList(defaultConfig.getLocations()), pathList(createdFlywayConfig().getLocations())); } diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java index 42ef4c03b08dd..c62912c39b23d 100644 --- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayProducerTest.java @@ -10,7 +10,7 @@ class FlywayProducerTest { private static final String DEFAULT_DATASOURCE = ""; - private FlywayBuildConfig buildDataSourceConfig = FlywayBuildConfig.defaultConfig(); + private FlywayBuildTimeConfig buildDataSourceConfig = FlywayBuildTimeConfig.defaultConfig(); private FlywayRuntimeConfig runtimeDataSourceConfig = FlywayRuntimeConfig.defaultConfig(); /** From f6fe1ad4c3b78d72a2aaa0b4f8070bd34e2e91e5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 8 Dec 2019 16:45:37 +0100 Subject: [PATCH 295/602] Use FlywayDataSource as the default way to disambiguate Flyway instances Also use a flyway_ prefix instead of a _flyway postfix in the @Named annotations. --- .../flyway/FlywayDatasourceBeanGenerator.java | 17 +++++++++-------- ...onBaselineOnMigrateNamedDataSourceTest.java | 6 +++--- ...ensionConfigMissingNamedDataSourceTest.java | 5 ++--- ...wayExtensionConfigMultiDataSourcesTest.java | 2 +- ...nfigMultiDataSourcesWithoutDefaultTest.java | 7 +++---- ...onfigNamedDataSourceWithoutDefaultTest.java | 5 ++--- ...ConfigNamedDataSourceWithoutFlywayTest.java | 5 ++--- ...nsionMigrateAtStartNamedDataSourceTest.java | 6 +++--- .../io/quarkus/flyway/FlywayDataSource.java | 18 +++++++++--------- .../quarkus/flyway/runtime/FlywayRecorder.java | 4 ++-- 10 files changed, 36 insertions(+), 39 deletions(-) diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java index 36c31c33cad02..241721adae31f 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayDatasourceBeanGenerator.java @@ -32,13 +32,14 @@ * Generates the CDI producer bean for {@link Flyway} at build time.
* Supports multiple {@link Named} {@link DataSource}s. *

- * It produces {@link Flyway} instances for every {@link Named} {@link DataSource}.
- * All {@link Flyway} instances get named the same way as the {@link DataSource}s,
- * appended by the postfix {@value #FLYWAY_BEAN_NAME_POSTFIX}. + * It produces {@link Flyway} instances for every {@link Named} {@link DataSource}. + *

+ * All {@link Flyway} instances get named the same way as the {@link DataSource}s, + * prepended by the prefix {@value #FLYWAY_BEAN_NAME_PREFIX}. */ class FlywayDatasourceBeanGenerator { - public static final String FLYWAY_BEAN_NAME_POSTFIX = "_flyway"; + public static final String FLYWAY_BEAN_NAME_PREFIX = "flyway_"; private static final String FLYWAY_PRODUCER_BEAN_NAME = "FlywayDataSourceProducer"; private static final String FLYWAY_PRODUCER_PACKAGE_NAME = FlywayProducer.class.getPackage().getName(); @@ -59,7 +60,7 @@ public FlywayDatasourceBeanGenerator(Collection dataSourceNames, * Create a producer bean managing flyway. *

* Build time and runtime configuration are both injected into this bean. - * + * * @return String name of the generated producer bean class. */ public void createFlywayProducerBean() { @@ -85,7 +86,7 @@ public void createFlywayProducerBean() { flywayProducerMethod.addAnnotation(Produces.class); flywayProducerMethod.addAnnotation(Dependent.class); flywayProducerMethod.addAnnotation(annotatedWithFlywayDatasource(dataSourceName)); - flywayProducerMethod.addAnnotation(annotatedWithNamed(dataSourceName + FLYWAY_BEAN_NAME_POSTFIX)); + flywayProducerMethod.addAnnotation(annotatedWithNamed(FLYWAY_BEAN_NAME_PREFIX + dataSourceName)); flywayProducerMethod.returnValue( flywayProducerMethod.invokeVirtualMethod( @@ -120,8 +121,8 @@ private static AnnotationInstance annotatedWithNamed(String dataSourceName) { new AnnotationValue[] { AnnotationValue.createStringValue("value", dataSourceName) }); } - //Since is does not seem to be possible to generate the annotation "@Typed", - //because AnnotationValue.createArrayValue is not implemented yet (jandex, August 2019), + //Since is does not seem to be possible to generate the annotation "@Typed", + //because AnnotationValue.createArrayValue is not implemented yet (jandex, August 2019), //the annotation "@FlywayDataSource" was introduced (in conformity with @DataSource). private AnnotationInstance annotatedWithFlywayDatasource(String dataSourceName) { return AnnotationInstance.create(DotName.createSimple(FlywayDataSource.class.getName()), null, diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java index ccc4ee65358be..3e00aa79d0e67 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateNamedDataSourceTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationInfo; @@ -13,12 +12,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionBaselineOnMigrateNamedDataSourceTest { - // Quarkus built object + @Inject - @Named("users_flyway") + @FlywayDataSource("users") Flyway flyway; @RegisterExtension diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java index ed62cc929ef30..d65f98b3eec7e 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java @@ -5,7 +5,6 @@ import javax.enterprise.inject.Instance; import javax.enterprise.inject.UnsatisfiedResolutionException; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -14,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; /** @@ -22,9 +22,8 @@ */ public class FlywayExtensionConfigMissingNamedDataSourceTest { - // Quarkus built objects @Inject - @Named("users_flyway") + @FlywayDataSource("users") Instance flyway; @RegisterExtension diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java index c74b30bf40b3b..a2802e00949c5 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java @@ -36,7 +36,7 @@ public class FlywayExtensionConfigMultiDataSourcesTest { Flyway flywayInventory; @Inject - @Named("inventory_flyway") + @Named("flyway_inventory") Flyway flywayNamedInventory; @RegisterExtension diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java index 4851cea7a1b47..66118d4712055 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; /** @@ -22,13 +22,12 @@ public class FlywayExtensionConfigMultiDataSourcesWithoutDefaultTest { @Inject FlywayExtensionConfigFixture fixture; - // Quarkus built objects @Inject - @Named("users_flyway") + @FlywayDataSource("users") Flyway flywayUsers; @Inject - @Named("inventory_flyway") + @FlywayDataSource("inventory") Flyway flywayInventory; @RegisterExtension diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java index acf92ef23822f..4effb9578dafe 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutDefaultTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; /** @@ -22,9 +22,8 @@ public class FlywayExtensionConfigNamedDataSourceWithoutDefaultTest { @Inject FlywayExtensionConfigFixture fixture; - // Quarkus built objects @Inject - @Named("users_flyway") + @FlywayDataSource("users") Flyway flywayUsers; @RegisterExtension diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java index db9e6303744c4..bcc66980f5c70 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigNamedDataSourceWithoutFlywayTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; /** @@ -20,9 +20,8 @@ */ public class FlywayExtensionConfigNamedDataSourceWithoutFlywayTest { - // Quarkus built objects @Inject - @Named("users_flyway") + @FlywayDataSource("users") Flyway flyway; @Inject diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java index b3ee0dfce2644..50039cd1c8637 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDataSourceTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import javax.inject.Inject; -import javax.inject.Named; import org.flywaydb.core.Flyway; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -12,15 +11,16 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.flyway.FlywayDataSource; import io.quarkus.test.QuarkusUnitTest; /** * Same as {@link FlywayExtensionMigrateAtStartTest} for named datasources. */ public class FlywayExtensionMigrateAtStartNamedDataSourceTest { - // Quarkus built object + @Inject - @Named("users_flyway") + @FlywayDataSource("users") Flyway flywayUsers; @RegisterExtension diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java index 6008886ef7d9c..ba4229d4e67bf 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/FlywayDataSource.java @@ -15,10 +15,10 @@ import javax.inject.Qualifier; /** - * Qualifier used to specify which datasource will be used and therefore which flyway instance will be injected. + * Flyway instances can also be qualified by name using @{@link Named}. + * The name is the datasource name prefixed by "flyway_". */ @Target({ METHOD, FIELD, PARAMETER, TYPE }) @Retention(RUNTIME) @@ -31,16 +31,16 @@ /** * Supports inline instantiation of the {@link FlywayDataSource} qualifier. */ - public static final class Literal extends AnnotationLiteral implements FlywayDataSource { + public static final class FlywayDataSourceLiteral extends AnnotationLiteral implements FlywayDataSource { - public static final Literal INSTANCE = of(""); + public static final FlywayDataSourceLiteral INSTANCE = of(""); private static final long serialVersionUID = 1L; private final String value; - public static Literal of(String value) { - return new Literal(value); + public static FlywayDataSourceLiteral of(String value) { + return new FlywayDataSourceLiteral(value); } @Override @@ -48,7 +48,7 @@ public String value() { return value; } - private Literal(String value) { + private FlywayDataSourceLiteral(String value) { this.value = value; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index e19d1f6674394..251a8b144e56f 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -36,10 +36,10 @@ public void doStartActions(FlywayRuntimeConfig config, BeanContainer container) } for (Entry configPerDataSource : config.namedDataSources.entrySet()) { if (configPerDataSource.getValue().cleanAtStart) { - clean(container, FlywayDataSource.Literal.of(configPerDataSource.getKey())); + clean(container, FlywayDataSource.FlywayDataSourceLiteral.of(configPerDataSource.getKey())); } if (configPerDataSource.getValue().migrateAtStart) { - migrate(container, FlywayDataSource.Literal.of(configPerDataSource.getKey())); + migrate(container, FlywayDataSource.FlywayDataSourceLiteral.of(configPerDataSource.getKey())); } } } From 8b59d8f447d658c78bead50cb6d18a790c13c1e4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Dec 2019 16:18:10 +0100 Subject: [PATCH 296/602] Some additional minor cleanup in the Flyway extension --- .../test/FlywayExtensionBaselineOnMigrateTest.java | 2 +- .../FlywayExtensionCleanAndMigrateAtStartTest.java | 2 +- .../test/FlywayExtensionCleanAtStartTest.java | 2 +- .../FlywayExtensionConfigDefaultDataSourceTest.java | 1 - ...ionConfigDefaultDataSourceWithoutFlywayTest.java | 3 +-- .../flyway/test/FlywayExtensionConfigEmptyTest.java | 1 - .../FlywayExtensionConfigMultiDataSourcesTest.java | 1 - extensions/flyway/runtime/pom.xml | 2 +- .../flyway/runtime/FlywayBuildTimeConfig.java | 7 +------ .../io/quarkus/flyway/runtime/FlywayProducer.java | 2 +- .../quarkus/flyway/runtime/FlywayRuntimeConfig.java | 13 ++----------- 11 files changed, 9 insertions(+), 27 deletions(-) diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateTest.java index 13984db28ee26..cd9e9b38dc31f 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionBaselineOnMigrateTest.java @@ -15,7 +15,7 @@ import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionBaselineOnMigrateTest { - // Quarkus built object + @Inject Flyway flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java index 2fa8feabcea2b..a63a2f1d3feb7 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAndMigrateAtStartTest.java @@ -21,7 +21,7 @@ import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionCleanAndMigrateAtStartTest { - // Quarkus built object + @Inject Flyway flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java index 501d5fe76cfe3..e7c33ee8d09c1 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionCleanAtStartTest.java @@ -23,7 +23,7 @@ import io.quarkus.test.QuarkusUnitTest; public class FlywayExtensionCleanAtStartTest { - // Quarkus built object + @Inject Flyway flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java index 366f724288844..b42729f107488 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceTest.java @@ -15,7 +15,6 @@ public class FlywayExtensionConfigDefaultDataSourceTest { - // Quarkus built object @Inject Flyway flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java index 0e8b243d79a45..4fee05bb4e9e3 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest.java @@ -14,12 +14,11 @@ import io.quarkus.test.QuarkusUnitTest; /** - * Assures, that flyway can also be used without any configuration, + * Assures, that Flyway can also be used without any configuration, * provided, that at least a datasource is configured. */ public class FlywayExtensionConfigDefaultDataSourceWithoutFlywayTest { - // Quarkus built objects @Inject Flyway flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java index a3183e2dab42a..a7195077bdf8a 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java @@ -22,7 +22,6 @@ */ public class FlywayExtensionConfigEmptyTest { - // Quarkus built objects @Inject Instance flyway; diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java index a2802e00949c5..4dd14e1da05f7 100644 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMultiDataSourcesTest.java @@ -23,7 +23,6 @@ public class FlywayExtensionConfigMultiDataSourcesTest { @Inject FlywayExtensionConfigFixture fixture; - // Quarkus built objects @Inject Flyway flyway; diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml index f8f9e939bd1c1..651367e3da86a 100644 --- a/extensions/flyway/runtime/pom.xml +++ b/extensions/flyway/runtime/pom.xml @@ -33,7 +33,7 @@ org.graalvm.nativeimage svm - + io.quarkus diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java index 554e1e81b8936..8e856d661872f 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildTimeConfig.java @@ -17,12 +17,7 @@ public static final FlywayBuildTimeConfig defaultConfig() { } /** - * Gets the {@link FlywayDataSourceBuildTimeConfig} for the given datasource name.
- * The name of the default datasource is an empty {@link String}. - * - * @param dataSourceName {@link String} - * @return {@link FlywayDataSourceBuildTimeConfig} - * @throws NullPointerException if dataSourceName is null. + * Gets the {@link FlywayDataSourceBuildTimeConfig} for the given datasource name. */ public FlywayDataSourceBuildTimeConfig getConfigForDataSourceName(String dataSourceName) { return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceBuildTimeConfig.defaultConfig()); diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java index 51b1dd3f9bd60..ac9f9c7dd6c21 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java @@ -12,7 +12,7 @@ @ApplicationScoped public class FlywayProducer { - private static final String ERROR_NOT_READY = "The flyway settings are not ready to be consumed: the %s configuration has not been injected yet"; + private static final String ERROR_NOT_READY = "The Flyway settings are not ready to be consumed: the %s configuration has not been injected yet"; @Inject @Default diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java index d800902d12d50..637761d1be00f 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -9,22 +9,13 @@ @ConfigRoot(name = "flyway", phase = ConfigPhase.RUN_TIME) public final class FlywayRuntimeConfig { - /* - * Creates a {@link FlywayMultiDatasourceRuntimeConfig} with default settings. - * - * @return {@link FlywayMultiDatasourceRuntimeConfig} - */ + public static final FlywayRuntimeConfig defaultConfig() { return new FlywayRuntimeConfig(); } /** - * Gets the {@link FlywayDataSourceRuntimeConfig} for the given datasource name.
- * The name of the default datasource is an empty {@link String}. - * - * @param dataSourceName {@link String} - * @return {@link FlywayDataSourceRuntimeConfig} - * @throws NullPointerException if dataSourceName is null. + * Gets the {@link FlywayDataSourceRuntimeConfig} for the given datasource name. */ public FlywayDataSourceRuntimeConfig getConfigForDataSourceName(String dataSourceName) { return namedDataSources.getOrDefault(dataSourceName, FlywayDataSourceRuntimeConfig.defaultConfig()); From 4aaa28995a9187bbe204a42c886ab8f44023d917 Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Mon, 9 Dec 2019 17:39:24 +0100 Subject: [PATCH 297/602] Clean up glossary --- docs/src/main/asciidoc/0-glossary.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/0-glossary.adoc b/docs/src/main/asciidoc/0-glossary.adoc index 4ac2f83adeca6..259e309503e0c 100644 --- a/docs/src/main/asciidoc/0-glossary.adoc +++ b/docs/src/main/asciidoc/0-glossary.adoc @@ -5,9 +5,9 @@ include::./attributes.adoc[] This is a collection of preferred term in the documentation and website. Please stay within these terms for consistency. -* Live reload:: for our `quarkus:dev` capability -* GraalVM:: preferred term for the VM creating native executable. No space. -* Substrate VM:: non-preferred. Only if you want to clarify which part of GraalVM we use. +* Live coding:: for our `quarkus:dev` capability +* GraalVM native image:: preferred term for the VM creating native executable. No space. +* Substrate VM:: non-preferred. Exclude. * Native Executable:: the executable that is compiled to native 1s and 0s * Docker image:: for the actual `Dockerfile` definition and when the tool chain is involved * Container:: when we discuss Quarkus running in... containers From b855cbd25a13d91594d22e5c63e86cae225f8cda Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Mon, 9 Dec 2019 17:40:25 +0100 Subject: [PATCH 298/602] issue #6042 Externalize preview status doc for each extension Also introduce experimental proposal --- .../src/main/asciidoc/amazon-lambda-http.adoc | 8 ++------ docs/src/main/asciidoc/amazon-lambda.adoc | 8 ++------ docs/src/main/asciidoc/amqp.adoc | 9 +++------ .../main/asciidoc/azure-functions-http.adoc | 8 ++------ docs/src/main/asciidoc/dynamodb.adoc | 8 ++------ .../hibernate-search-elasticsearch.adoc | 8 ++++---- .../main/asciidoc/infinispan-embedded.adoc | 8 ++------ docs/src/main/asciidoc/jms.adoc | 8 ++------ docs/src/main/asciidoc/kogito.adoc | 8 ++------ docs/src/main/asciidoc/kotlin.adoc | 8 ++------ docs/src/main/asciidoc/mongodb-panache.adoc | 8 ++------ docs/src/main/asciidoc/mongodb.adoc | 8 ++------ docs/src/main/asciidoc/neo4j.adoc | 10 +++++----- docs/src/main/asciidoc/quartz.adoc | 8 ++------ docs/src/main/asciidoc/qute-reference.adoc | 3 +++ docs/src/main/asciidoc/qute.adoc | 8 ++------ docs/src/main/asciidoc/security-oauth2.adoc | 8 ++------ .../asciidoc/security-openid-connect.adoc | 8 ++------ .../software-transactional-memory.adoc | 8 ++------ docs/src/main/asciidoc/spring-data-jpa.adoc | 8 ++------ docs/src/main/asciidoc/spring-di.adoc | 8 ++------ docs/src/main/asciidoc/spring-security.adoc | 3 +++ docs/src/main/asciidoc/spring-web.adoc | 8 ++------ docs/src/main/asciidoc/status-include.adoc | 20 +++++++++++++++++++ docs/src/main/asciidoc/vault.adoc | 8 ++------ 25 files changed, 76 insertions(+), 129 deletions(-) create mode 100644 docs/src/main/asciidoc/status-include.adoc diff --git a/docs/src/main/asciidoc/amazon-lambda-http.adoc b/docs/src/main/asciidoc/amazon-lambda-http.adoc index 1892d7de3d05d..32eebbcafd5d1 100644 --- a/docs/src/main/asciidoc/amazon-lambda-http.adoc +++ b/docs/src/main/asciidoc/amazon-lambda-http.adoc @@ -4,6 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Amazon Lambda with Resteasy, Undertow, or Vert.x Web  +:extension-status: preview include::./attributes.adoc[] @@ -14,12 +15,7 @@ using https://aws.amazon.com/api-gateway/[Amazon's API Gateway] and https://docs You can deploy your Lambda as a pure Java jar, or you can compile your project to a native image and deploy that for a smaller memory footprint and startup time. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/amazon-lambda.adoc b/docs/src/main/asciidoc/amazon-lambda.adoc index 781b97f54e6d5..07aeb40960de8 100644 --- a/docs/src/main/asciidoc/amazon-lambda.adoc +++ b/docs/src/main/asciidoc/amazon-lambda.adoc @@ -4,6 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Amazon Lambda +:extension-status: preview include::./attributes.adoc[] @@ -13,12 +14,7 @@ Your lambdas can use injection annotations from CDI or Spring and other Quarkus Quarkus lambdas can be deployed using the Amazon Java Runtime, or you can build a native executable and use Amazon's Custom Runtime if you want a smaller memory footprint and faster cold boot startup time. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/amqp.adoc b/docs/src/main/asciidoc/amqp.adoc index cf08d9f3df7da..63918544723db 100644 --- a/docs/src/main/asciidoc/amqp.adoc +++ b/docs/src/main/asciidoc/amqp.adoc @@ -4,16 +4,13 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Using AMQP with Reactive Messaging +:extension-status: preview + include::./attributes.adoc[] This guide demonstrates how your Quarkus application can utilize MicroProfile Reactive Messaging to interact with AMQP. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/azure-functions-http.adoc b/docs/src/main/asciidoc/azure-functions-http.adoc index 1afdde9c75c17..90a2e0a643a82 100644 --- a/docs/src/main/asciidoc/azure-functions-http.adoc +++ b/docs/src/main/asciidoc/azure-functions-http.adoc @@ -4,6 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Azure Functions (Serverless) with Resteasy, Undertow, or Vert.x Web +:extension-status: preview include::./attributes.adoc[] @@ -12,12 +13,7 @@ Undertow (servlet), or Vert.x Web and make these microservices deployable to the One azure function deployment can represent any number of JAX-RS, servlet, or Vert.x Web endpoints. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/dynamodb.adoc b/docs/src/main/asciidoc/dynamodb.adoc index d12fa47aa81ff..68ed836d00f1a 100644 --- a/docs/src/main/asciidoc/dynamodb.adoc +++ b/docs/src/main/asciidoc/dynamodb.adoc @@ -5,6 +5,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Amazon DynamoDB Client +:extension-status: preview include::./attributes.adoc[] @@ -17,12 +18,7 @@ NOTE: The DynamoDB extension is based on https://docs.aws.amazon.com/sdk-for-jav It's a major rewrite of the 1.x code base that offers two programming models (Blocking & Async). Keep in mind it's actively developed and does not support yet all the features available in SDK 1.x such as https://github.com/aws/aws-sdk-java-v2/issues/36[Document APIs] or https://github.com/aws/aws-sdk-java-v2/issues/35[Object Mappers] -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] The Quarkus extension supports two programming models: diff --git a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc index 688a7c7619379..a93e1ffdea5be 100644 --- a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc +++ b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc @@ -4,7 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Hibernate Search guide - +:extension-status: preview include::./attributes.adoc[] You have a Hibernate ORM-based application? You want to provide a full-featured full-text search to your users? You're at the right place. @@ -12,12 +12,12 @@ You have a Hibernate ORM-based application? You want to provide a full-featured With this guide, you'll learn how to synchronize your entities to an Elasticsearch cluster in a heart beat with Hibernate Search. We will also explore how you can can query your Elasticsearch cluster using the Hibernate Search API. +include::./status-include.adoc[] + [WARNING] ==== -This extension is considered `preview`. -It is based on a beta version of Hibernate Search. +This extension is based on a beta version of Hibernate Search. While APIs are quite stable and the code is of production quality and thoroughly tested, some features are still missing, performance might not be optimal and some APIs or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. ==== == Prerequisites diff --git a/docs/src/main/asciidoc/infinispan-embedded.adoc b/docs/src/main/asciidoc/infinispan-embedded.adoc index 6e88963936bd8..31392de1a41f3 100644 --- a/docs/src/main/asciidoc/infinispan-embedded.adoc +++ b/docs/src/main/asciidoc/infinispan-embedded.adoc @@ -4,6 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Infinispan Embedded +:extension-status: preview include::./attributes.adoc[] @@ -13,12 +14,7 @@ directly in your application. Check out the link:https://infinispan.org/documentation/[Infinispan documentation] to find out more about the Infinispan project. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Adding the Infinispan Embedded Extension After you set up your Quarkus project, run the following command from the base directory: diff --git a/docs/src/main/asciidoc/jms.adoc b/docs/src/main/asciidoc/jms.adoc index 8e72826491f32..69a677c98fa51 100644 --- a/docs/src/main/asciidoc/jms.adoc +++ b/docs/src/main/asciidoc/jms.adoc @@ -5,15 +5,11 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Using Artemis JMS extension include::./attributes.adoc[] +:extension-status: preview This guide demonstrates how your Quarkus application can use Artemis JMS messaging. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/kogito.adoc b/docs/src/main/asciidoc/kogito.adoc index eb349c37f27f6..ba5f1d67b26ae 100644 --- a/docs/src/main/asciidoc/kogito.adoc +++ b/docs/src/main/asciidoc/kogito.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Using Kogito to add business automation capabilities to an application include::./attributes.adoc[] +:extension-status: preview This guide demonstrates how your Quarkus application can use Kogito to add business automation to power it up with business processes and rules. @@ -15,12 +16,7 @@ Drools (for business rules) and jBPM (for business processes). Kogito aims at pr to business automation where the main message is to expose your business knowledge (processes, rules and decisions) in a domain specific way. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index e63a50e05f278..d27de1d7c2883 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -5,18 +5,14 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Using Kotlin +:extension-status: preview include::./attributes.adoc[] https://kotlinlang.org/[Kotlin] is a very popular programming language that targets the JVM (amongst other environments). Kotlin has experienced a surge in popularity the last few years making it the most popular JVM language, except for Java of course. Quarkus provides first class support for using Kotlin as will be explained in this guide. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index ee2e29e79d54c..94ad05f159664 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -7,6 +7,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] :config-file: application.properties +:extension-status: preview MongoDB is a well known NoSQL Database that is widely used, but using its raw API can be cumbersome as you need to express your entities and your queries as a MongoDB link:https://mongodb.github.io/mongo-java-driver/3.11/bson/documents/#document[`Document`]. @@ -14,12 +15,7 @@ MongoDB with Panache provides active record style entities (and repositories) li It is built on top of the link:mongodb[MongoDB Client] extension. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == First: an example diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 36920d6143c02..18964c959738f 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -5,17 +5,13 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Using the MongoDB Client include::./attributes.adoc[] +:extension-status: preview MongoDB is a well known NoSQL Database that is widely used. In this guide, we see how you can get your REST services to use the MongoDB database. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 90c0a8ea63775..acb271115287f 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -7,9 +7,9 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc :neo4j_version: 3.5.6 include::./attributes.adoc[] +:extension-status: preview -https://neo4j.com[Neo4j] is a graph database management system developed by Neo4j, Inc. -Neo4j is a native graph database focused not only on the data itself, but especially on the relations between data. +https://neo4j.com[Neo4j] is a graph database management system developed by Neo4j, Inc. Neo4j is a native graph database focused not only on the data itself, but especially on the relations between data. Neo4j stores data as a property graph, which consists of vertices or nodes as we call them, connected with edges or relationships. Both of them can have properties. @@ -34,12 +34,12 @@ The driver itself is released under the Apache 2.0 license, while Neo4j itself is available in a GPL3-licensed open-source "community edition", with online backup and high availability extensions licensed under a closed-source commercial license. +include::./status-include.adoc[] + [WARNING] ==== -This extension is considered `preview`. It is based on an alpha version of the Neo4j driver. -Some interactions with the driver, the API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +Hence the reason for it to be _preview_ ==== == Programming model diff --git a/docs/src/main/asciidoc/quartz.adoc b/docs/src/main/asciidoc/quartz.adoc index ccf96c73e9ab0..589d561dda8a0 100644 --- a/docs/src/main/asciidoc/quartz.adoc +++ b/docs/src/main/asciidoc/quartz.adoc @@ -6,16 +6,12 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Scheduling Periodic Tasks with Quartz include::./attributes.adoc[] +:extension-status: preview Modern applications often need to run specific tasks periodically. In this guide, you learn how to schedule periodic clustered tasks using the http://www.quartz-scheduler.org/[Quartz] extension. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] TIP: If you only need to run in-memory scheduler use the link:scheduler[Scheduler] extension. diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 23de48001463f..829638a71ca1c 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -11,6 +11,9 @@ include::./attributes.adoc[] :sectnums: :sectnumlevels: 4 :toc: +:extension-status: experimental + +include::./status-include.adoc[] == Hello World Example diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index 036ee26a5a9f4..f0a3170105443 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -4,6 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Qute Templating Engine +:extension-status: experimental include::./attributes.adoc[] @@ -14,12 +15,7 @@ In the development mode, all files located in `src/main/resources/templates` are Furthermore, we try to detect most of the template problems at build time. In this guide, you will learn how to easily render templates in your application. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Hello World with JAX-RS diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index b1757f3837fad..53f3b7ddc738b 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -7,6 +7,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] :extension-name: Elytron Security OAuth2 +:extension-status: preview This guide explains how your Quarkus application can utilize OAuth2 tokens to provide secured access to the JAX-RS endpoints. @@ -15,12 +16,7 @@ It can be used to implement an application authentication mechanism based on tok If your OAuth2 Authentication server provides JWT tokens, you should use link:security-jwt[MicroProfile JWT RBAC] instead, this extension aims to be used with opaque tokens and validate the token by calling an introspection endpoint. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Solution diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 3876d6d12e905..7cd42ecd3b7b0 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Using OpenID Connect Adapter to Protect JAX-RS Applications include::./attributes.adoc[] +:extension-status: preview This guide demonstrates how your Quarkus application can use an OpenID Connect Adapter to protect your JAX-RS applications using bearer token authorization, where these tokens are issued by OpenId Connect and OAuth 2.0 compliant Authorization Servers such as https://www.keycloak.org/about.html[Keycloak]. @@ -13,12 +14,7 @@ Bearer Token Authorization is the process of authorizing HTTP requests based on We are going to give you a guideline on how to use OpenId Connect and OAuth 2.0 in your JAX-RS applications using the Quarkus OpenID Connect Extension. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/software-transactional-memory.adoc b/docs/src/main/asciidoc/software-transactional-memory.adoc index d3c8d27c2c660..2ece8de4bcbf7 100644 --- a/docs/src/main/asciidoc/software-transactional-memory.adoc +++ b/docs/src/main/asciidoc/software-transactional-memory.adoc @@ -6,6 +6,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Using Software Transactional Memory in Quarkus include::./attributes.adoc[] +:extension-status: preview Quarkus supports the Software Transactional Memory (STM) implementation provided by the Narayana open source project. Narayana STM allows a program to group object accesses into @@ -24,12 +25,7 @@ There is also a fully worked example in the quickstarts which you may access by Git repository: `git clone {quickstarts-clone-url}`, or by downloading an {quickstarts-archive-url}[archive]. Look for the `software-transactional-memory-quickstart` example. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Setting it up diff --git a/docs/src/main/asciidoc/spring-data-jpa.adoc b/docs/src/main/asciidoc/spring-data-jpa.adoc index 098517915538a..13490a1b83dd6 100644 --- a/docs/src/main/asciidoc/spring-data-jpa.adoc +++ b/docs/src/main/asciidoc/spring-data-jpa.adoc @@ -6,16 +6,12 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Extension for Spring Data API include::./attributes.adoc[] +:extension-status: preview While users are encouraged to use Hibernate ORM with Panache for Relational Database access, Quarkus provides a compatibility layer for Spring Data JPA repositories in the form of the `spring-data-jpa` extension. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/spring-di.adoc b/docs/src/main/asciidoc/spring-di.adoc index fc7b469f7f7ba..0e081803c9e93 100644 --- a/docs/src/main/asciidoc/spring-di.adoc +++ b/docs/src/main/asciidoc/spring-di.adoc @@ -6,17 +6,13 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Quarkus Extension for Spring DI API include::./attributes.adoc[] +:extension-status: preview While users are encouraged to use CDI annotations for injection, Quarkus provides a compatibility layer for Spring dependency injection in the form of the `spring-di` extension. This guide explains how a Quarkus application can leverage the well known Dependency Injection annotations included in the Spring Framework. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/spring-security.adoc b/docs/src/main/asciidoc/spring-security.adoc index 46817567605c2..19c3aedba4060 100644 --- a/docs/src/main/asciidoc/spring-security.adoc +++ b/docs/src/main/asciidoc/spring-security.adoc @@ -6,11 +6,14 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Quarkus Extension for Spring Security API include::./attributes.adoc[] +:extension-status: preview While users are encouraged to use <>, Quarkus provides a compatibility layer for Spring Security in the form of the `spring-security` extension. This guide explains how a Quarkus application can leverage the well known Spring Security annotations to define authorizations on RESTful services using roles. +include::./status-include.adoc[] + == Prerequisites To complete this guide, you need: diff --git a/docs/src/main/asciidoc/spring-web.adoc b/docs/src/main/asciidoc/spring-web.adoc index ae7b7c3be26c0..311fb9c6f258e 100644 --- a/docs/src/main/asciidoc/spring-web.adoc +++ b/docs/src/main/asciidoc/spring-web.adoc @@ -6,17 +6,13 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc = Quarkus - Quarkus Extension for Spring Web API include::./attributes.adoc[] +:extension-status: preview While users are encouraged to use JAX-RS annotation for defining REST endpoints, Quarkus provides a compatibility layer for Spring Web in the form of the `spring-web` extension. This guide explains how a Quarkus application can leverage the well known Spring Web annotations to define RESTful services. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites diff --git a/docs/src/main/asciidoc/status-include.adoc b/docs/src/main/asciidoc/status-include.adoc new file mode 100644 index 0000000000000..9e934e24fd231 --- /dev/null +++ b/docs/src/main/asciidoc/status-include.adoc @@ -0,0 +1,20 @@ +[NOTE] +==== +This technology is considered {extension-status}. + +ifeval::["{extension-status}" == "experimental"] +In _experimental_ mode, early feedback is requested to mature the idea. +There is no guarantee of stability nor long term presense in the platform until the solution matures. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +endif::[] +ifeval::["{extension-status}" == "preview"] +In _preview_, backward compatibility and presence in the ecosystem is not guaranteed. +Specific improvements might require to change configuration or APIs and plans to become _stable_ are under way. +Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. +endif::[] +ifeval::["{extension-status}" == "stable"] +Being _stable_, backward compatibility and presence in the ecosystem are taken very seriously. +endif::[] + +For a full list of possible extension statuses, check our https://quarkus.io/faq/#extension-status[FAQ entry]. +==== diff --git a/docs/src/main/asciidoc/vault.adoc b/docs/src/main/asciidoc/vault.adoc index 0b59095b44254..5e5695c6642b0 100644 --- a/docs/src/main/asciidoc/vault.adoc +++ b/docs/src/main/asciidoc/vault.adoc @@ -10,6 +10,7 @@ include::./attributes.adoc[] :vault-version: 1.2.2 :root-token: s.5VUS8pte13RqekCB2fmMT3u2 :client-token: s.s93BVzJPzBiIGuYJHBTkG8Uw +:extension-status: preview https://www.vaultproject.io/[HashiCorp Vault] is a multi-purpose tool aiming at protecting sensitive data, such as credentials, certificates, access tokens, encryption keys, ... In the context of Quarkus, @@ -23,12 +24,7 @@ https://www.vaultproject.io/docs/secrets/kv/index.html[Vault kv secret engine] a Under the hood, the Quarkus Vault extension takes care of authentication when negotiating a client Vault token plus any transparent token or lease renewals according to ttl and max-ttl. -[NOTE] -==== -This extension is considered `preview`. -API or configuration properties might change as the extension matures. -Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. -==== +include::./status-include.adoc[] == Prerequisites From c0ecd195da4c23c05b757062ccd246cc472cba3a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:11:52 +0000 Subject: [PATCH 299/602] Bump assertj-core from 3.12.2 to 3.14.0 Bumps [assertj-core](https://github.com/joel-costigliola/assertj-core) from 3.12.2 to 3.14.0. - [Release notes](https://github.com/joel-costigliola/assertj-core/releases) - [Commits](https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.12.2...assertj-core-3.14.0) Signed-off-by: dependabot-preview[bot] --- independent-projects/arc/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index e0f2a4db6b629..ad5668fd32c77 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -39,7 +39,7 @@ 2.1.2.Final 5.5.2 3.5.4 - 3.12.2 + 3.14.0 3.3.2.Final 1.3.5 1.0.0.Final diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index ba29154062395..2c95b9a53a9b6 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -37,7 +37,7 @@ 4.1.1 - 3.13.2 + 3.14.0 0.9.5 2.9.10 2.0.2 From 8f74e1117686d7121cfb0c541a9b73d035c6e7c3 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 10 Dec 2019 02:27:27 +0200 Subject: [PATCH 300/602] Property handle pagination in Spring Data JPA custom queries Fixes: #5823 --- .../orm/panache/runtime/JpaOperations.java | 6 +-- .../orm/panache/runtime/PanacheQueryImpl.java | 6 ++- .../generate/CustomQueryMethodsAdder.java | 52 +++++++++++++++---- .../generate/SpringDataRepositoryCreator.java | 2 +- .../runtime/AdditionalJpaOperations.java | 40 ++++++++++++++ .../runtime/CustomCountPanacheQuery.java | 20 +++++++ .../it/spring/data/jpa/MovieRepository.java | 8 ++- .../it/spring/data/jpa/MovieResource.java | 12 ++++- .../it/spring/data/jpa/MovieResourceTest.java | 15 ++++++ 9 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java create mode 100644 extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java index e12f5621fc283..d206827936a34 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/JpaOperations.java @@ -101,11 +101,11 @@ public static Query bindParameters(Query query, Map params) { return query; } - private static int paramCount(Object[] params) { + static int paramCount(Object[] params) { return params != null ? params.length : 0; } - private static int paramCount(Map params) { + static int paramCount(Map params) { return params != null ? params.size() : 0; } @@ -114,7 +114,7 @@ private static String getEntityName(Class entityClass) { return entityClass.getName(); } - private static String createFindQuery(Class entityClass, String query, int paramCount) { + static String createFindQuery(Class entityClass, String query, int paramCount) { if (query == null) return "FROM " + getEntityName(entityClass); diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java index 89d4982d08bfb..5c5be466669d4 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/PanacheQueryImpl.java @@ -116,7 +116,7 @@ public long count() { int orderByIndex = lcQuery.lastIndexOf(" order by "); if (orderByIndex != -1) query = query.substring(0, orderByIndex); - Query countQuery = em.createQuery("SELECT COUNT(*) " + query); + Query countQuery = em.createQuery(countQuery()); if (paramsArrayOrMap instanceof Map) JpaOperations.bindParameters(countQuery, (Map) paramsArrayOrMap); else @@ -126,6 +126,10 @@ public long count() { return count; } + protected String countQuery() { + return "SELECT COUNT(*) " + query; + } + @Override @SuppressWarnings("unchecked") public List list() { diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java index a473835db8216..1fab4af0b5b08 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java @@ -12,6 +12,7 @@ import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; @@ -21,14 +22,23 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.hibernate.orm.panache.PanacheQuery; +import io.quarkus.hibernate.orm.panache.runtime.AdditionalJpaOperations; import io.quarkus.hibernate.orm.panache.runtime.JpaOperations; import io.quarkus.panache.common.Parameters; import io.quarkus.spring.data.deployment.DotNames; +import io.quarkus.spring.data.deployment.MethodNameParser; import io.quarkus.spring.data.runtime.TypesConverter; public class CustomQueryMethodsAdder extends AbstractMethodsAdder { private static final String QUERY_VALUE_FIELD = "value"; + private static final String QUERY_COUNT_FIELD = "countQuery"; + + private final IndexView index; + + public CustomQueryMethodsAdder(IndexView index) { + this.index = index; + } public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescriptor, ClassInfo repositoryClassInfo, ClassInfo entityClassInfo) { @@ -40,7 +50,8 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr String methodName = method.name(); String repositoryName = repositoryClassInfo.name().toString(); - String queryString = ensureOnlyValue(queryInstance, methodName, repositoryName); + verifyQueryAnnotation(queryInstance, methodName, repositoryName); + String queryString = queryInstance.value(QUERY_VALUE_FIELD).asString().trim(); if (queryString.contains("#{")) { throw new IllegalArgumentException("spEL expressions are not currently supported. " + "Offending method is " + methodName + " of Repository " + repositoryName); @@ -190,25 +201,46 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr "a delete or update query"); } } else { + // by default just hope that adding select count(*) will do + String countQueryString = "SELECT COUNT(*) " + queryString; + if (queryInstance.value(QUERY_COUNT_FIELD) != null) { // if a countQuery is specified, use it + countQueryString = queryInstance.value(QUERY_COUNT_FIELD).asString().trim(); + } else { + // otherwise try and derive the select query from the method name and use that to construct the count query + MethodNameParser methodNameParser = new MethodNameParser(repositoryClassInfo, index); + try { + MethodNameParser.Result parseResult = methodNameParser.parse(method); + if (MethodNameParser.QueryType.SELECT == parseResult.getQueryType()) { + countQueryString = "SELECT COUNT (*) " + parseResult.getQuery(); + } + } catch (Exception ignored) { + // we just ignore the exception if the method does not match one of the supported styles + } + } + ResultHandle panacheQuery; if (useNamedParams) { ResultHandle parameters = generateParametersObject(namedParameterToIndex, methodCreator); // call JpaOperations.find() panacheQuery = methodCreator.invokeStaticMethod( - MethodDescriptor.ofMethod(JpaOperations.class, "find", PanacheQuery.class, - Class.class, String.class, io.quarkus.panache.common.Sort.class, Parameters.class), + MethodDescriptor.ofMethod(AdditionalJpaOperations.class, "find", PanacheQuery.class, + Class.class, String.class, String.class, io.quarkus.panache.common.Sort.class, + Parameters.class), methodCreator.readInstanceField(entityClassFieldDescriptor, methodCreator.getThis()), - methodCreator.load(queryString), generateSort(sortParameterIndex, methodCreator), parameters); + methodCreator.load(queryString), methodCreator.load(countQueryString), + generateSort(sortParameterIndex, methodCreator), parameters); } else { ResultHandle paramsArray = generateParamsArray(queryParameterIndexes, methodCreator); // call JpaOperations.find() panacheQuery = methodCreator.invokeStaticMethod( - MethodDescriptor.ofMethod(JpaOperations.class, "find", PanacheQuery.class, - Class.class, String.class, io.quarkus.panache.common.Sort.class, Object[].class), + MethodDescriptor.ofMethod(AdditionalJpaOperations.class, "find", PanacheQuery.class, + Class.class, String.class, String.class, io.quarkus.panache.common.Sort.class, + Object[].class), methodCreator.readInstanceField(entityClassFieldDescriptor, methodCreator.getThis()), - methodCreator.load(queryString), generateSort(sortParameterIndex, methodCreator), paramsArray); + methodCreator.load(queryString), methodCreator.load(countQueryString), + generateSort(sortParameterIndex, methodCreator), paramsArray); } generateFindQueryResultHandling(methodCreator, panacheQuery, pageableParameterIndex, repositoryClassInfo, @@ -219,10 +251,10 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr } // we currently only support the 'value' attribute of @Query - private String ensureOnlyValue(AnnotationInstance queryInstance, String methodName, String repositoryName) { + private void verifyQueryAnnotation(AnnotationInstance queryInstance, String methodName, String repositoryName) { List values = queryInstance.values(); for (AnnotationValue value : values) { - if (!QUERY_VALUE_FIELD.equals(value.name())) { + if (!QUERY_VALUE_FIELD.equals(value.name()) && !QUERY_COUNT_FIELD.equals(value.name())) { throw new IllegalArgumentException("Attribute " + value.name() + " of @Query is currently not supported. " + "Offending method is " + methodName + " of Repository " + repositoryName); } @@ -231,8 +263,6 @@ private String ensureOnlyValue(AnnotationInstance queryInstance, String methodNa throw new IllegalArgumentException("'value' attribute must be specified on @Query annotation of method. " + "Offending method is " + methodName + " of Repository " + repositoryName); } - - return queryInstance.value(QUERY_VALUE_FIELD).asString().trim(); } private ResultHandle generateParamsArray(List queryParameterIndexes, MethodCreator methodCreator) { diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java index 4ee64b8ea36c9..d72f6dcec497c 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/SpringDataRepositoryCreator.java @@ -45,7 +45,7 @@ public SpringDataRepositoryCreator(ClassOutput classOutput, IndexView index, this.fragmentMethodsAdder = new FragmentMethodsAdder(fragmentImplClassResolvedCallback, index); this.stockMethodsAdder = new StockMethodsAdder(index); this.derivedMethodsAdder = new DerivedMethodsAdder(index); - this.customQueryMethodsAdder = new CustomQueryMethodsAdder(); + this.customQueryMethodsAdder = new CustomQueryMethodsAdder(index); } public void implementCrudRepository(ClassInfo repositoryToImplement) { diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java new file mode 100644 index 0000000000000..4ab7fe9800a84 --- /dev/null +++ b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java @@ -0,0 +1,40 @@ +package io.quarkus.hibernate.orm.panache.runtime; + +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import io.quarkus.hibernate.orm.panache.PanacheQuery; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; + +public class AdditionalJpaOperations { + + @SuppressWarnings("rawtypes") + public static PanacheQuery find(Class entityClass, String query, String countQuery, Sort sort, + Map params) { + String findQuery = JpaOperations.createFindQuery(entityClass, query, JpaOperations.paramCount(params)); + EntityManager em = JpaOperations.getEntityManager(); + Query jpaQuery = em.createQuery(sort != null ? findQuery + JpaOperations.toOrderBy(sort) : findQuery); + JpaOperations.bindParameters(jpaQuery, params); + return new CustomCountPanacheQuery(em, jpaQuery, findQuery, countQuery, params); + // return new PanacheQueryImpl(em, jpaQuery, findQuery, params); + } + + @SuppressWarnings("rawtypes") + public static PanacheQuery find(Class entityClass, String query, String countQuery, Sort sort, + Parameters parameters) { + return find(entityClass, query, countQuery, sort, parameters.map()); + } + + @SuppressWarnings("rawtypes") + public static PanacheQuery find(Class entityClass, String query, String countQuery, Sort sort, Object... params) { + String findQuery = JpaOperations.createFindQuery(entityClass, query, JpaOperations.paramCount(params)); + EntityManager em = JpaOperations.getEntityManager(); + Query jpaQuery = em.createQuery(sort != null ? findQuery + JpaOperations.toOrderBy(sort) : findQuery); + JpaOperations.bindParameters(jpaQuery, params); + return new CustomCountPanacheQuery(em, jpaQuery, findQuery, countQuery, params); + // return new PanacheQueryImpl(em, jpaQuery, findQuery, params); + } +} diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java new file mode 100644 index 0000000000000..0694fef6e4075 --- /dev/null +++ b/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java @@ -0,0 +1,20 @@ +package io.quarkus.hibernate.orm.panache.runtime; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +public class CustomCountPanacheQuery extends PanacheQueryImpl { + + private final String customCountQuery; + + public CustomCountPanacheQuery(EntityManager em, Query jpaQuery, String query, String customCountQuery, + Object paramsArrayOrMap) { + super(em, jpaQuery, query, paramsArrayOrMap); + this.customCountQuery = customCountQuery; + } + + @Override + protected String countQuery() { + return customCountQuery; + } +} diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieRepository.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieRepository.java index aa05e1d0f163f..def6e58fb03f5 100644 --- a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieRepository.java +++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieRepository.java @@ -3,6 +3,7 @@ import java.util.Iterator; import java.util.List; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @@ -46,6 +47,9 @@ public interface MovieRepository extends CrudRepository { @Query("update Movie set rating = null where title =?1") void setRatingToNullForTitle(String title); - @Query("from Movie order by length(title)") - Slice orderByTitleLength(Pageable pageable); + @Query("from Movie m where m.duration > ?1") + Slice findByDurationGreaterThan(Integer duration, Pageable pageable); + + @Query(value = "select m from Movie m", countQuery = "select count(m) from Movie m") + Page customFind(Pageable pageable); } diff --git a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieResource.java b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieResource.java index 5dd4a6948b693..c3482926504ab 100644 --- a/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieResource.java +++ b/integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/MovieResource.java @@ -9,6 +9,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @@ -46,10 +47,19 @@ public Movie findByTitle(@PathParam("title") String title) { @GET @Path("/title/titleLengthOrder/page/{size}/{num}") public String orderByTitleLengthSlice(@PathParam("size") int pageSize, @PathParam("num") int pageNum) { - Slice slice = movieRepository.orderByTitleLength(PageRequest.of(pageNum, pageSize)); + Slice slice = movieRepository.findByDurationGreaterThan(1, + PageRequest.of(pageNum, pageSize, Sort.Direction.ASC, "title")); return slice.hasNext() + " / " + slice.getNumberOfElements(); } + @GET + @Path("/customFind/page/{size}/{num}") + public String customFind(@PathParam("size") int pageSize, @PathParam("num") int pageNum) { + Page page = movieRepository.customFind( + PageRequest.of(pageNum, pageSize, Sort.Direction.ASC, "title")); + return page.hasNext() + " / " + page.getNumberOfElements(); + } + @GET @Path("/rating/{rating}") @Produces("application/json") diff --git a/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/MovieResourceTest.java b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/MovieResourceTest.java index d0f80a7703e5d..5d38d47ba9592 100644 --- a/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/MovieResourceTest.java +++ b/integration-tests/spring-data-jpa/src/test/java/io/quarkus/it/spring/data/jpa/MovieResourceTest.java @@ -168,4 +168,19 @@ void testOrderByTitleLength() { .statusCode(200) .body(containsString("false /")); } + + @Test + void testCustomFind() { + when().get("/movie/customFind/page/1/0").then() + .statusCode(200) + .body(is("true / 1")); + + when().get("/movie/customFind/page/1/1").then() + .statusCode(200) + .body(is("true / 1")); + + when().get("/movie/customFind/page/10/0").then() + .statusCode(200) + .body(containsString("false /")); + } } From b00233b1178a00c440ddbcee029df4c453a1a94f Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Tue, 10 Dec 2019 09:29:56 +0100 Subject: [PATCH 301/602] Typo in guide --- docs/src/main/asciidoc/status-include.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/status-include.adoc b/docs/src/main/asciidoc/status-include.adoc index 9e934e24fd231..a594e251362e8 100644 --- a/docs/src/main/asciidoc/status-include.adoc +++ b/docs/src/main/asciidoc/status-include.adoc @@ -4,7 +4,7 @@ This technology is considered {extension-status}. ifeval::["{extension-status}" == "experimental"] In _experimental_ mode, early feedback is requested to mature the idea. -There is no guarantee of stability nor long term presense in the platform until the solution matures. +There is no guarantee of stability nor long term presence in the platform until the solution matures. Feedback is welcome on our https://groups.google.com/d/forum/quarkus-dev[mailing list] or as issues in our https://github.com/quarkusio/quarkus/issues[GitHub issue tracker]. endif::[] ifeval::["{extension-status}" == "preview"] From 501a5accbc50a9dbc54912afc349df2e0935274d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:10:10 +0000 Subject: [PATCH 302/602] Bump cron-utils from 9.0.1 to 9.0.2 Bumps [cron-utils](https://github.com/jmrozanec/cron-utils) from 9.0.1 to 9.0.2. - [Release notes](https://github.com/jmrozanec/cron-utils/releases) - [Commits](https://github.com/jmrozanec/cron-utils/compare/9.0.1...9.0.2) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 6d5a10c327634..b20f345f73c97 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -104,7 +104,7 @@ 4.5.10 4.4.12 4.1.4 - 9.0.1 + 9.0.2 2.3.2 1.4.197 42.2.8 From 7e4652e03b134f7b437f9b04d46a22ddffe7ff77 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:11:01 +0000 Subject: [PATCH 303/602] Bump test-containers.version from 1.12.3 to 1.12.4 Bumps `test-containers.version` from 1.12.3 to 1.12.4. Updates `testcontainers` from 1.12.3 to 1.12.4 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.12.3...1.12.4) Updates `junit-jupiter` from 1.12.3 to 1.12.4 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.12.3...1.12.4) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index b20f345f73c97..6457342385a7b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -123,7 +123,7 @@ 2.6.2 4.1.42.Final 1.0.3 - 1.12.3 + 1.12.4 3.3.2.Final 0.0.10 2.2.1 From 5418953494fd33b9de02694de80f52229fe03340 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:11:27 +0000 Subject: [PATCH 304/602] Bump postgresql from 42.2.8 to 42.2.9 Bumps [postgresql](https://github.com/pgjdbc/pgjdbc) from 42.2.8 to 42.2.9. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.2.8...REL42.2.9) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 6457342385a7b..bf37223984a96 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -107,7 +107,7 @@ 9.0.2 2.3.2 1.4.197 - 42.2.8 + 42.2.9 2.5.2 8.0.18 7.2.1.jre8 From ed98e81646c84f3f95422483c83f50c9a4e1056a Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 10 Dec 2019 11:13:09 +0100 Subject: [PATCH 305/602] Add Qute experimental status and fix metadata --- extensions/qute/runtime/pom.xml | 1 + .../main/resources/META-INF/quarkus-extension.yaml | 4 ++-- extensions/resteasy-qute/runtime/pom.xml | 2 +- .../main/resources/META-INF/quarkus-extension.yaml | 11 +++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 extensions/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/extensions/qute/runtime/pom.xml b/extensions/qute/runtime/pom.xml index 93142fc631bbb..26e14b62c45a9 100644 --- a/extensions/qute/runtime/pom.xml +++ b/extensions/qute/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-qute Quarkus - Qute - Runtime + Qute is a templating engine designed specifically to meet the Quarkus needs diff --git a/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 436b27d3aaa2c..dc31829fad11f 100644 --- a/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -1,5 +1,5 @@ --- -name: "Qute - templating" +name: "Qute Templating" metadata: keywords: - "templating" @@ -7,4 +7,4 @@ metadata: guide: "https://quarkus.io/guides/qute" categories: - "miscellaneous" - status: "preview" + status: "experimental" diff --git a/extensions/resteasy-qute/runtime/pom.xml b/extensions/resteasy-qute/runtime/pom.xml index 62440e6930aa9..1cd11ae1927f8 100644 --- a/extensions/resteasy-qute/runtime/pom.xml +++ b/extensions/resteasy-qute/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-resteasy-qute Quarkus - RESTEasy - Qute - Runtime - + Qute Templating integration for RESTEasy io.quarkus diff --git a/extensions/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..b18397ef209ab --- /dev/null +++ b/extensions/resteasy-qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "RESTEasy Qute" +metadata: + keywords: + - "templating" + - "templates" + - "resteasy" + guide: "https://quarkus.io/guides/qute" + categories: + - "web" + status: "experimental" From e007c01b53bd8cd7363fea4ec6966bb0d717dfc9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Dec 2019 09:20:05 +0100 Subject: [PATCH 306/602] Add Nexus Maven plugin version to Qute --- independent-projects/qute/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index c0cd525ca46d5..847bac07b7440 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -39,6 +39,7 @@ 1.0.2 2.2.10 1.0.6 + 1.6.8 @@ -233,7 +234,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} + ${version.nexus-staging-maven-plugin} true https://oss.sonatype.org/ From b33a93ef0b7eddd8f11d3c7beb56a67a4fc92965 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Mon, 9 Dec 2019 16:07:59 +0100 Subject: [PATCH 307/602] StandardCharsets.UTF_8 usage --- .../quarkus/deployment/pkg/steps/NativeImageBuildStep.java | 3 ++- .../main/java/io/quarkus/qute/deployment/QuteProcessor.java | 4 +++- .../src/test/java/io/quarkus/jwt/test/TokenUtils.java | 2 +- .../io/quarkus/vault/runtime/client/CertificateHelper.java | 2 +- .../json/test/TestJsonPlatformDescriptorLoader.java | 3 ++- .../java/io/quarkus/it/infinispan/embedded/TestServlet.java | 6 +++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index ca6a5219f0bc1..ddd4be32da107 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -143,7 +143,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa Process versionProcess = new ProcessBuilder(versionCommand.toArray(new String[0])) .redirectErrorStream(true) .start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(versionProcess.getInputStream()))) { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(versionProcess.getInputStream(), StandardCharsets.UTF_8))) { graalVMVersion = reader.lines().filter((l) -> l.startsWith("GraalVM Version")).findFirst(); } } catch (Exception e) { diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 385578463650a..bda1ffc9c445d 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -164,7 +165,8 @@ public CompletionStage resolve(SectionResolutionContext context) { for (TemplatePathBuildItem path : templatePaths) { try { - Template template = dummyEngine.parse(new String(Files.readAllBytes(path.getFullPath()))); + Template template = dummyEngine + .parse(new String(Files.readAllBytes(path.getFullPath()), StandardCharsets.UTF_8)); analysis.add(new TemplateAnalysis(template.getGeneratedId(), template.getExpressions(), path)); } catch (IOException e) { LOGGER.warn("Unable to analyze the template from path: " + path.getFullPath(), e); diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java index 52a96c7be2bb2..b21dea605bc2b 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenUtils.java @@ -117,7 +117,7 @@ public static String generateTokenString(PrivateKey pk, String kid, String jsonR byte[] content = new byte[length]; System.arraycopy(tmp, 0, content, 0, length); - JwtClaims claims = JwtClaims.parse(new String(content)); + JwtClaims claims = JwtClaims.parse(new String(content, StandardCharsets.UTF_8)); // Change the issuer to INVALID_ISSUER for failure testing if requested if (invalidClaims.contains(InvalidClaims.ISSUER)) { diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/CertificateHelper.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/CertificateHelper.java index 26ec057008126..9d0a8a902343f 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/CertificateHelper.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/client/CertificateHelper.java @@ -31,7 +31,7 @@ public static TrustManager[] createTrustManagers(String cacert) throws GeneralSe KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); - String certBundle = new String(Files.readAllBytes(Paths.get(cacert))); + String certBundle = new String(Files.readAllBytes(Paths.get(cacert)), StandardCharsets.UTF_8); int start = 0; int count = 0; Matcher matcher = certBundlePattern.matcher(certBundle); diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java index 647fe459a6dcb..dbd3624f856d3 100644 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; @@ -23,7 +24,7 @@ public class TestJsonPlatformDescriptorLoader implements QuarkusJsonPlatformDesc @Override public QuarkusPlatformDescriptor load(QuarkusJsonPlatformDescriptorLoaderContext context) { final JsonObject json = context.parseJson(s -> { - try (InputStreamReader reader = new InputStreamReader(s)) { + try (InputStreamReader reader = new InputStreamReader(s, StandardCharsets.UTF_8)) { return Json.parse(reader).asObject(); } catch (IOException e) { throw new IllegalStateException("Failed to parse JSON descriptor", e); diff --git a/integration-tests/infinispan-embedded/src/main/java/io/quarkus/it/infinispan/embedded/TestServlet.java b/integration-tests/infinispan-embedded/src/main/java/io/quarkus/it/infinispan/embedded/TestServlet.java index 6434bb8760015..e182cd32703fb 100644 --- a/integration-tests/infinispan-embedded/src/main/java/io/quarkus/it/infinispan/embedded/TestServlet.java +++ b/integration-tests/infinispan-embedded/src/main/java/io/quarkus/it/infinispan/embedded/TestServlet.java @@ -46,7 +46,7 @@ public String get(@PathParam("cacheName") String cacheName, @PathParam("id") Str log.info("Retrieving " + id + " from " + cacheName); Cache cache = emc.getCache(cacheName); byte[] result = cache.get(id.getBytes(StandardCharsets.UTF_8)); - return result == null ? "null" : new String(result); + return result == null ? "null" : new String(result, StandardCharsets.UTF_8); } @Transactional @@ -61,7 +61,7 @@ public String put(@PathParam("cacheName") String cacheName, @PathParam("id") Str if (Boolean.parseBoolean(shouldFail)) { throw new RuntimeException("Forced Exception!"); } - return result == null ? "null" : new String(result); + return result == null ? "null" : new String(result, StandardCharsets.UTF_8); } @Path("REMOVE/{cacheName}/{id}") @@ -71,7 +71,7 @@ public String remove(@PathParam("cacheName") String cacheName, @PathParam("id") log.info("Removing " + id + " from " + cacheName); Cache cache = emc.getCache(cacheName); byte[] result = cache.remove(id.getBytes(StandardCharsets.UTF_8)); - return result == null ? "null" : new String(result); + return result == null ? "null" : new String(result, StandardCharsets.UTF_8); } @Path("CLUSTER") From 248bda7e1f5f65508cbdff5a6a3660e90e216b75 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 10 Dec 2019 09:06:19 -0300 Subject: [PATCH 308/602] Remove Kotlin from dependabot config --- .dependabot/config.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 72fdacbf205a3..3e88b136060b1 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -54,15 +54,6 @@ update_configs: # Debezium - match: dependency_name: "io.debezium:debezium-core" - # Kotlin - - match: - dependency_name: "org.jetbrains.kotlin:kotlin-compiler" - - match: - dependency_name: "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - - match: - dependency_name: "org.jetbrains.kotlin:kotlin-maven-plugin" - - match: - dependency_name: "org.jetbrains.kotlin:kotlin-maven-allopen" # Scala - match: dependency_name: "org.scala-lang:scala-reflect" From 1f5b9224ac63eb751b9c11e8a9d4fb717870c650 Mon Sep 17 00:00:00 2001 From: Justin Lee Date: Tue, 10 Dec 2019 13:43:36 -0500 Subject: [PATCH 309/602] revert change to run() add a shutdown hook to properly call stop() to release database resources properly, e.g. fixes #5921 --- .../amazon/lambda/runtime/QuarkusStreamHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java index 1edbb680e66e2..5e5cb505de1d4 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/QuarkusStreamHandler.java @@ -30,7 +30,13 @@ public class QuarkusStreamHandler implements RequestStreamHandler { Class appClass = Class.forName("io.quarkus.runner.ApplicationImpl"); String[] args = {}; Application app = (Application) appClass.newInstance(); - app.run(args); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + app.stop(); + } + }); + app.start(args); errorWriter.println("Quarkus bootstrapped successfully."); started = true; } catch (Exception ex) { From c19c20a80de358eef048df99a477a4c1f59d14ed Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Fri, 6 Dec 2019 16:00:42 +0100 Subject: [PATCH 310/602] Log a warning if Arc finds observer for @Initialized(ApplicationScope.class). --- .../ObserverValidationProcessor.java | 59 +++++++++++++++++++ .../io/quarkus/arc/processor/Annotations.java | 19 ++++++ .../io/quarkus/arc/processor/DotNames.java | 2 + 3 files changed, 80 insertions(+) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverValidationProcessor.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverValidationProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverValidationProcessor.java new file mode 100644 index 0000000000000..1f9efcaf8b74b --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverValidationProcessor.java @@ -0,0 +1,59 @@ +package io.quarkus.arc.deployment; + +import java.util.Collection; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.logging.Logger; + +import io.quarkus.arc.processor.Annotations; +import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.ObserverInfo; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; + +/** + * Validates observer methods from application classes. + * If an observer listening for {@code @Initialized(ApplicationScoped.class)} is found, it logs a warning. + */ +public class ObserverValidationProcessor { + + private static final Logger LOGGER = Logger.getLogger(ObserverValidationProcessor.class.getName()); + + @BuildStep + public void validateApplicationObserver(ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer validators) { + // an index of all root archive classes (usually src/main/classes) + IndexView applicationClassesIndex = applicationArchivesBuildItem.getRootArchive().getIndex(); + + validators.produce(new BeanDeploymentValidatorBuildItem(new BeanDeploymentValidator() { + + @Override + public void validate(ValidationContext context) { + Collection allObservers = context.get(Key.OBSERVERS); + // do the validation for each observer that can be found within application classes + for (ObserverInfo observer : allObservers) { + DotName declaringBeanDotName = observer.getDeclaringBean().getBeanClass(); + AnnotationInstance instance = Annotations.getParameterAnnotation(observer.getObserverMethod(), + DotNames.INITIALIZED); + if (applicationClassesIndex.getClassByName(declaringBeanDotName) != null && instance != null && + instance.value().asClass().name().equals(BuiltinScope.APPLICATION.getName())) { + // found an observer for @Initialized(ApplicationScoped.class) + // log a warning and recommend to use StartupEvent instead + final String observerWarning = "The method %s#%s is an observer for " + + "@Initialized(ApplicationScoped.class). Observer notification for this event may " + + "vary between JVM and native modes! We strongly recommend to observe StartupEvent " + + "instead as that one is consistently delivered in both modes once the container is " + + "running."; + LOGGER.warnf(observerWarning, observer.getDeclaringBean().getImplClazz(), + observer.getObserverMethod().name()); + } + } + } + })); + } +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java index 0c2edd5a54aef..0eaf0783c44e5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java @@ -62,4 +62,23 @@ static Set getParameterAnnotations(BeanDeployment beanDeploy return annotations; } + /** + * Iterates over all annotations on a method and its parameters, filters out all non-parameter annotations + * and returns a first encountered {@link AnnotationInstance} with Annotation specified as {@link DotName}. + * Returns {@code null} if no such annotation exists. + * + * @param method MethodInfo to be searched for annotations + * @param annotation Annotation we are looking for, represented as DotName + * @return First encountered {@link AnnotationInstance} fitting the requirements, {@code null} if none is found + */ + public static AnnotationInstance getParameterAnnotation(MethodInfo method, DotName annotation) { + for (AnnotationInstance annotationInstance : method.annotations()) { + if (annotationInstance.target().kind().equals(Kind.METHOD_PARAMETER) && + annotationInstance.name().equals(annotation)) { + return annotationInstance; + } + } + return null; + } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index cfba393b211cf..b0112a6e08935 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -7,6 +7,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Priority; +import javax.enterprise.context.Initialized; import javax.enterprise.context.control.ActivateRequestContext; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; @@ -81,6 +82,7 @@ public final class DotNames { public static final DotName NAMED = create(Named.class); public static final DotName ACTIVATE_REQUEST_CONTEXT = create(ActivateRequestContext.class); public static final DotName TRANSACTION_PHASE = create(TransactionPhase.class); + public static final DotName INITIALIZED = create(Initialized.class); public static final DotName BOOLEAN = create(Boolean.class); public static final DotName BYTE = create(Byte.class); From fbf4aae3141aec80ca1d6e60f3dc26c648ba3e8f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Dec 2019 16:41:56 +0100 Subject: [PATCH 311/602] Add proper artifact names for Qute --- independent-projects/qute/core/pom.xml | 1 + independent-projects/qute/generator/pom.xml | 1 + independent-projects/qute/pom.xml | 2 ++ independent-projects/qute/rxjava/pom.xml | 1 + 4 files changed, 5 insertions(+) diff --git a/independent-projects/qute/core/pom.xml b/independent-projects/qute/core/pom.xml index 509f046bd64c6..b9a8d0e4c3cfb 100644 --- a/independent-projects/qute/core/pom.xml +++ b/independent-projects/qute/core/pom.xml @@ -12,6 +12,7 @@ qute-core + Qute - Core diff --git a/independent-projects/qute/generator/pom.xml b/independent-projects/qute/generator/pom.xml index facd101f24ff9..c3412f284d5ee 100644 --- a/independent-projects/qute/generator/pom.xml +++ b/independent-projects/qute/generator/pom.xml @@ -12,6 +12,7 @@ qute-generator + Qute - Generator diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 847bac07b7440..6628b9bd46566 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -13,6 +13,8 @@ qute-parent pom 999-SNAPSHOT + + Qute - Parent diff --git a/independent-projects/qute/rxjava/pom.xml b/independent-projects/qute/rxjava/pom.xml index 749ed516c5646..57ea97e80850b 100644 --- a/independent-projects/qute/rxjava/pom.xml +++ b/independent-projects/qute/rxjava/pom.xml @@ -12,6 +12,7 @@ qute-rxjava + Qute - RxJava From 4da82b9cf5ac4e2234a12da2f5246145c47c3296 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 10 Dec 2019 09:38:01 +0100 Subject: [PATCH 312/602] Vertx @ConsumeEvent methods have request context activated - resolves #6059 --- .../io/quarkus/arc/runtime/BeanInvoker.java | 29 ++++++++++++ .../vertx/deployment/EventBusConsumer.java | 3 +- .../vertx/deployment/VertxProcessor.java | 24 +++++----- .../MessageConsumerFailureTest.java | 18 ++++---- .../deployment/MessageConsumerMethodTest.java | 44 +++++++++++++++++-- .../vertx/runtime/EventConsumerInvoker.java | 5 +-- .../quarkus/vertx/runtime/VertxRecorder.java | 30 ++++++++----- 7 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanInvoker.java diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanInvoker.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanInvoker.java new file mode 100644 index 0000000000000..d0a0741e3e436 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanInvoker.java @@ -0,0 +1,29 @@ +package io.quarkus.arc.runtime; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ManagedContext; + +/** + * Invokes a business method of a bean. The request context is activated if necessary. + * + * @param + */ +public interface BeanInvoker { + + default void invoke(T param) { + ManagedContext requestContext = Arc.container().requestContext(); + if (requestContext.isActive()) { + invokeBean(param); + } else { + try { + requestContext.activate(); + invokeBean(param); + } finally { + requestContext.terminate(); + } + } + } + + void invokeBean(T param); + +} diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java index 4ed8cb6964a66..bb485cb6d9494 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java @@ -85,7 +85,8 @@ static String generateInvoker(BeanInfo bean, MethodInfo method, ClassCreator invokerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) .interfaces(EventConsumerInvoker.class).build(); - MethodCreator invoke = invokerCreator.getMethodCreator("invoke", void.class, Message.class); + // The method descriptor is: void invokeBean(Object message) + MethodCreator invoke = invokerCreator.getMethodCreator("invokeBean", void.class, Object.class); ResultHandle containerHandle = invoke.invokeStaticMethod(ARC_CONTAINER); AnnotationValue blocking = consumeEvent.value("blocking"); diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index b76021f524029..6ca78ef306870 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -25,6 +25,7 @@ import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuildExtension; +import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.annotations.BuildProducer; @@ -32,6 +33,7 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AnnotationProxyBuildItem; +import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; @@ -52,9 +54,10 @@ class VertxProcessor { @Inject BuildProducer reflectiveClass; - @BuildStep(providesCapabilities = Capabilities.RESTEASY_JSON_EXTENSION) - FeatureBuildItem feature() { - return new FeatureBuildItem(FeatureBuildItem.VERTX); + @BuildStep + void featureAndCapability(BuildProducer feature, BuildProducer capability) { + feature.produce(new FeatureBuildItem(FeatureBuildItem.VERTX)); + capability.produce(new CapabilityBuildItem(Capabilities.RESTEASY_JSON_EXTENSION)); } @BuildStep @@ -138,14 +141,13 @@ public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) { @Override public void transform(TransformationContext context) { - if (context.getAnnotations().isEmpty()) { - // Class with no annotations but with a method annotated with @ConsumeMessage - if (context.getTarget().asClass().annotations().containsKey(CONSUME_EVENT)) { - LOGGER.debugf( - "Found event consumer business methods on a class %s with no scope annotation - adding @Singleton", - context.getTarget()); - context.transform().add(Singleton.class).done(); - } + if (!BuiltinScope.isIn(context.getAnnotations()) + && context.getTarget().asClass().annotations().containsKey(CONSUME_EVENT)) { + // Class with no built-in scope annotation but with a method annotated with @ConsumeMessage + LOGGER.debugf( + "Found event consumer business methods on a class %s with no scope annotation - adding @Singleton", + context.getTarget()); + context.transform().add(Singleton.class).done(); } } }); diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java index 285396120135d..3578d76e50475 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerFailureTest.java @@ -35,20 +35,22 @@ public class MessageConsumerFailureTest { @Test public void testFailure() throws InterruptedException { - verifyFailure("foo", "Foo is dead"); - verifyFailure("foo-message", null); - verifyFailure("foo-completion-stage", "Something is null"); + verifyFailure("foo", "java.lang.IllegalStateException: Foo is dead"); + verifyFailure("foo-message", "java.lang.NullPointerException"); + verifyFailure("foo-completion-stage", "java.lang.NullPointerException: Something is null"); } void verifyFailure(String address, String expectedMessage) throws InterruptedException { BlockingQueue synchronizer = new LinkedBlockingQueue<>(); - eventBus.send(address, "hello", ar -> { - if (ar.cause() != null) { - try { + eventBus.request(address, "hello", ar -> { + try { + if (ar.cause() != null) { synchronizer.put(ar.cause()); - } catch (InterruptedException e) { - throw new IllegalStateException(e); + } else { + synchronizer.put(false); } + } catch (InterruptedException e) { + throw new IllegalStateException(e); } }); Object ret = synchronizer.poll(2, TimeUnit.SECONDS); diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerMethodTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerMethodTest.java index e3f9948ca7b8b..8ae91a95e910a 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerMethodTest.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/deployment/MessageConsumerMethodTest.java @@ -13,6 +13,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -31,7 +32,7 @@ public class MessageConsumerMethodTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class)); + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class, Transformer.class)); @Inject SimpleBean simpleBean; @@ -40,7 +41,7 @@ public class MessageConsumerMethodTest { public void testSend() throws InterruptedException { EventBus eventBus = Arc.container().instance(EventBus.class).get(); BlockingQueue synchronizer = new LinkedBlockingQueue<>(); - eventBus.send("foo", "hello", ar -> { + eventBus.request("foo", "hello", ar -> { if (ar.succeeded()) { try { synchronizer.put(ar.result().body()); @@ -58,7 +59,7 @@ public void testSend() throws InterruptedException { public void testSendAsync() throws InterruptedException { EventBus eventBus = Arc.container().instance(EventBus.class).get(); BlockingQueue synchronizer = new LinkedBlockingQueue<>(); - eventBus.send("foo-async", "hello", ar -> { + eventBus.request("foo-async", "hello", ar -> { if (ar.succeeded()) { try { synchronizer.put(ar.result().body()); @@ -76,7 +77,7 @@ public void testSendAsync() throws InterruptedException { public void testSendDefaultAddress() throws InterruptedException { EventBus eventBus = Arc.container().instance(EventBus.class).get(); BlockingQueue synchronizer = new LinkedBlockingQueue<>(); - eventBus.send("io.quarkus.vertx.deployment.MessageConsumerMethodTest$SimpleBean", "Hello", ar -> { + eventBus.request("io.quarkus.vertx.deployment.MessageConsumerMethodTest$SimpleBean", "Hello", ar -> { if (ar.succeeded()) { try { synchronizer.put(ar.result().body()); @@ -90,6 +91,24 @@ public void testSendDefaultAddress() throws InterruptedException { assertEquals("hello", synchronizer.poll(2, TimeUnit.SECONDS)); } + @Test + public void testRequestContext() throws InterruptedException { + EventBus eventBus = Arc.container().instance(EventBus.class).get(); + BlockingQueue synchronizer = new LinkedBlockingQueue<>(); + eventBus.request("request", "Martin", ar -> { + if (ar.succeeded()) { + try { + synchronizer.put(ar.result().body()); + } catch (InterruptedException e) { + fail(e); + } + } else { + fail(ar.cause()); + } + }); + assertEquals("MArtin", synchronizer.poll(2, TimeUnit.SECONDS)); + } + @Test public void testPublish() throws InterruptedException { SimpleBean.MESSAGES.clear(); @@ -139,6 +158,9 @@ static class SimpleBean { static final List MESSAGES = new CopyOnWriteArrayList<>(); + @Inject + Transformer transformer; + @ConsumeEvent // io.quarkus.vertx.deployment.MessageConsumerMethodTest$SimpleBean String sendDefaultAddress(String message) { return message.toLowerCase(); @@ -183,6 +205,20 @@ void consume(io.vertx.reactivex.core.eventbus.Message message) { MESSAGES.add(message.body().toUpperCase()); latch.countDown(); } + + @ConsumeEvent("request") + String requestContextActive(String message) { + return transformer.transform(message); + } + } + + @RequestScoped + static class Transformer { + + String transform(String message) { + return message.replace('a', 'A'); + } + } } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/EventConsumerInvoker.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/EventConsumerInvoker.java index 0ae8b262f0016..234012d266f7b 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/EventConsumerInvoker.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/EventConsumerInvoker.java @@ -1,13 +1,12 @@ package io.quarkus.vertx.runtime; +import io.quarkus.arc.runtime.BeanInvoker; import io.quarkus.vertx.ConsumeEvent; import io.vertx.core.eventbus.Message; /** * Invokes a business method annotated with {@link ConsumeEvent}. */ -public interface EventConsumerInvoker { - - void invoke(Message message); +public interface EventConsumerInvoker extends BeanInvoker> { } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java index f2e12b9671056..eb45afe7b97cd 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java @@ -16,9 +16,12 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.vertx.ConsumeEvent; +import io.vertx.core.AsyncResult; import io.vertx.core.Context; +import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.eventbus.EventBus; +import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageCodec; import io.vertx.core.eventbus.MessageConsumer; @@ -32,8 +35,8 @@ public class VertxRecorder { public void configureVertx(Supplier vertx, Map messageConsumerConfigurations, LaunchMode launchMode, ShutdownContext shutdown, Map, Class> codecByClass) { - this.vertx = vertx.get(); - this.messageConsumers = new ArrayList<>(); + VertxRecorder.vertx = vertx.get(); + VertxRecorder.messageConsumers = new ArrayList<>(); registerMessageConsumers(messageConsumerConfigurations); registerCodecs(codecByClass); @@ -85,16 +88,23 @@ void registerMessageConsumers(Map messageConsumerConfigura } else { consumer = eventBus.consumer(address); } - consumer.handler(m -> { - try { - invoker.invoke(m); - } catch (Throwable e) { - m.fail(ConsumeEvent.FAILURE_CODE, e.getMessage()); + consumer.handler(new Handler>() { + @Override + public void handle(Message m) { + try { + invoker.invoke(m); + } catch (Throwable e) { + m.fail(ConsumeEvent.FAILURE_CODE, e.toString()); + } } }); - consumer.completionHandler(ar -> { - if (ar.succeeded()) { - latch.countDown(); + consumer.completionHandler(new Handler>() { + + @Override + public void handle(AsyncResult ar) { + if (ar.succeeded()) { + latch.countDown(); + } } }); messageConsumers.add(consumer); From 1b61273e94e5d81cacd765765551e1678f11d634 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 10 Dec 2019 09:41:17 +0100 Subject: [PATCH 313/602] Scheduler - make use of io.quarkus.arc.runtime.BeanInvoker --- .../deployment/SchedulerProcessor.java | 7 ++--- .../runtime/AbstractScheduledInvoker.java | 26 ------------------- .../scheduler/runtime/ScheduledInvoker.java | 7 ++--- 3 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/AbstractScheduledInvoker.java 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 4e4b90791b130..b0bbb7efc4cc2 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 @@ -61,7 +61,7 @@ import io.quarkus.gizmo.ResultHandle; import io.quarkus.scheduler.Scheduled; import io.quarkus.scheduler.ScheduledExecution; -import io.quarkus.scheduler.runtime.AbstractScheduledInvoker; +import io.quarkus.scheduler.runtime.ScheduledInvoker; import io.quarkus.scheduler.runtime.ScheduledMethodMetadata; import io.quarkus.scheduler.runtime.SchedulerConfig; import io.quarkus.scheduler.runtime.SchedulerRecorder; @@ -253,10 +253,11 @@ private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput cla + HashUtil.sha1(sigBuilder.toString()); ClassCreator invokerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) - .superClass(AbstractScheduledInvoker.class) + .interfaces(ScheduledInvoker.class) .build(); - MethodCreator invoke = invokerCreator.getMethodCreator("invokeBean", void.class, ScheduledExecution.class); + // The descriptor is: void invokeBean(Object execution) + MethodCreator invoke = invokerCreator.getMethodCreator("invokeBean", void.class, Object.class); // InjectableBean handle = Arc.container().instance(bean); // handle.get().ping(); diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/AbstractScheduledInvoker.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/AbstractScheduledInvoker.java deleted file mode 100644 index 88b9fca3f8dcc..0000000000000 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/AbstractScheduledInvoker.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.scheduler.runtime; - -import io.quarkus.arc.Arc; -import io.quarkus.arc.ManagedContext; -import io.quarkus.scheduler.ScheduledExecution; - -public abstract class AbstractScheduledInvoker implements ScheduledInvoker { - - @Override - public void invoke(ScheduledExecution execution) { - ManagedContext requestContext = Arc.container().requestContext(); - if (requestContext.isActive()) { - invokeBean(execution); - } else { - try { - requestContext.activate(); - invokeBean(execution); - } finally { - requestContext.terminate(); - } - } - } - - public abstract void invokeBean(ScheduledExecution action); - -} diff --git a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/ScheduledInvoker.java b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/ScheduledInvoker.java index c0519edbfa7bc..f248059d4e539 100644 --- a/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/ScheduledInvoker.java +++ b/extensions/scheduler/runtime/src/main/java/io/quarkus/scheduler/runtime/ScheduledInvoker.java @@ -1,14 +1,11 @@ package io.quarkus.scheduler.runtime; +import io.quarkus.arc.runtime.BeanInvoker; import io.quarkus.scheduler.ScheduledExecution; /** * Invokes a scheduled business method of a bean. - * - * @author Martin Kouba */ -public interface ScheduledInvoker { - - void invoke(ScheduledExecution execution); +public interface ScheduledInvoker extends BeanInvoker { } From e2cb3718d7dd7a95b17b5a5e965e94975355122f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 10 Dec 2019 15:13:05 +0100 Subject: [PATCH 314/602] Reactive routes - active CDI request context during Handler.handle() --- .../web/deployment/VertxWebProcessor.java | 6 ++- .../io/quarkus/vertx/web/SimpleRouteTest.java | 48 +++++++++++++------ .../vertx/web/runtime/RouteHandler.java | 20 ++++++++ .../vertx/web/runtime/VertxWebRecorder.java | 2 +- .../java/io/quarkus/vertx/ConsumeEvent.java | 12 +++-- 5 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index ed69c72f5731e..dc831effa5d67 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -55,6 +55,7 @@ import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; import io.quarkus.vertx.web.RoutingExchange; +import io.quarkus.vertx.web.runtime.RouteHandler; import io.quarkus.vertx.web.runtime.RoutingExchangeImpl; import io.quarkus.vertx.web.runtime.VertxWebRecorder; import io.vertx.core.Handler; @@ -332,9 +333,10 @@ private String generateHandler(BeanInfo bean, MethodInfo method, ClassOutput cla + HashUtil.sha1(sigBuilder.toString()); ClassCreator invokerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) - .interfaces(Handler.class).build(); + .interfaces(RouteHandler.class).build(); - MethodCreator invoke = invokerCreator.getMethodCreator("handle", void.class, Object.class); + // The descriptor is: void invokeBean(Object context) + MethodCreator invoke = invokerCreator.getMethodCreator("invokeBean", void.class, Object.class); // ArcContainer container = Arc.container(); // InjectableBean handle = container().instance(bean); diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java index bf0a2a7273522..d36a2233de49a 100644 --- a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java @@ -1,5 +1,7 @@ package io.quarkus.vertx.web; +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; import static io.vertx.core.http.HttpMethod.DELETE; import static io.vertx.core.http.HttpMethod.GET; import static org.hamcrest.Matchers.is; @@ -7,6 +9,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import javax.enterprise.context.RequestScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; @@ -18,7 +21,6 @@ import io.quarkus.test.QuarkusUnitTest; import io.quarkus.vertx.ConsumeEvent; -import io.restassured.RestAssured; import io.vertx.core.eventbus.EventBus; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; @@ -29,27 +31,31 @@ public class SimpleRouteTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class, - SimpleEventBusBean.class, SimpleRoutesBean.class)); + SimpleEventBusBean.class, SimpleRoutesBean.class, Transformer.class)); @Test public void testSimpleRoute() { - RestAssured.when().get("/hello").then().statusCode(200).body(is("Hello world!")); - RestAssured.when().get("/no-slash").then().statusCode(200).body(is("Hello world!")); - RestAssured.when().get("/rx-hello").then().statusCode(200).body(is("Hello world!")); - RestAssured.when().get("/bzuk").then().statusCode(200).body(is("Hello world!")); - RestAssured.when().get("/hello-event-bus?name=ping").then().statusCode(200).body(is("Hello PING!")); - RestAssured.when().get("/foo?name=foo").then().statusCode(200).body(is("Hello foo!")); - RestAssured.when().get("/bar").then().statusCode(200).body(is("Hello bar!")); - RestAssured.when().get("/delete").then().statusCode(405); - RestAssured.when().delete("/delete").then().statusCode(200).body(is("deleted")); - RestAssured.when().get("/routes").then().statusCode(200) + when().get("/hello").then().statusCode(200).body(is("Hello world!")); + when().get("/no-slash").then().statusCode(200).body(is("Hello world!")); + when().get("/rx-hello").then().statusCode(200).body(is("Hello world!")); + when().get("/bzuk").then().statusCode(200).body(is("Hello world!")); + when().get("/hello-event-bus?name=ping").then().statusCode(200).body(is("Hello PING!")); + when().get("/foo?name=foo").then().statusCode(200).body(is("Hello foo!")); + when().get("/bar").then().statusCode(200).body(is("Hello bar!")); + when().get("/delete").then().statusCode(405); + when().delete("/delete").then().statusCode(200).body(is("deleted")); + when().get("/routes").then().statusCode(200) .body(Matchers.containsString("/hello-event-bus")); - RestAssured.given().contentType("text/plain").body("world") + given().contentType("text/plain").body("world") .post("/body").then().body(is("Hello world!")); + when().get("/request").then().statusCode(200).body(is("HellO!")); } static class SimpleBean { + @Inject + Transformer transformer; + @Route(path = "/hello") @Route(path = "/foo") @Route(path = "no-slash") @@ -79,6 +85,11 @@ void post(RoutingContext context) { context.response().setStatusCode(200).end("Hello " + context.getBodyAsString() + "!"); } + @Route + void request(RoutingContext context) { + context.response().setStatusCode(200).end(transformer.transform("Hello!")); + } + } static class SimpleRoutesBean { @@ -106,7 +117,7 @@ static class SimpleEventBusBean { @Route(path = "/hello-event-bus", methods = GET) void helloEventBus(RoutingExchange exchange) { - eventBus.send("hello", exchange.getParam("name").orElse("missing"), ar -> { + eventBus.request("hello", exchange.getParam("name").orElse("missing"), ar -> { if (ar.succeeded()) { exchange.ok(ar.result().body().toString()); } else { @@ -125,4 +136,13 @@ String generate(String name) { } + @RequestScoped + static class Transformer { + + String transform(String message) { + return message.replace('o', 'O'); + } + + } + } diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java new file mode 100644 index 0000000000000..f87177660062b --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java @@ -0,0 +1,20 @@ +package io.quarkus.vertx.web.runtime; + +import io.quarkus.arc.runtime.BeanInvoker; +import io.quarkus.vertx.web.Route; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Handles invocation of a reactive route. + * + * @see Route + */ +public interface RouteHandler extends Handler, BeanInvoker { + + @Override + default void handle(RoutingContext context) { + BeanInvoker.super.invoke(context); + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index e7b3051717b6b..c6d69288c10ab 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -26,7 +26,7 @@ public Handler createHandler(String handlerClassName) { return handlerClazz.getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) { - throw new IllegalStateException("Unable to create invoker: " + handlerClassName, e); + throw new IllegalStateException("Unable to create route handler: " + handlerClassName, e); } } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/ConsumeEvent.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/ConsumeEvent.java index 9acb80ed76334..fc672055534a0 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/ConsumeEvent.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/ConsumeEvent.java @@ -12,11 +12,9 @@ * Marks a business method to be automatically registered as a Vertx message consumer. *

* The method must accept exactly one parameter. If it accepts {@link io.vertx.core.eventbus.Message} then the return type must - * be void. For any other - * type the {@link io.vertx.core.eventbus.Message#body()} - * is passed as the parameter value and the method may return an object that is passed to - * {@link io.vertx.core.eventbus.Message#reply(Object)}, either - * directly or via + * be void. For any other type the {@link io.vertx.core.eventbus.Message#body()} is passed as the parameter value and the method + * may return an object that is passed to + * {@link io.vertx.core.eventbus.Message#reply(Object)}, either directly or via * {@link java.util.concurrent.CompletionStage#thenAccept(java.util.function.Consumer)} in case of the method returns a * completion stage. * @@ -41,6 +39,9 @@ * } * * + *

+ * The CDI request context is active during notification of the registered message consumer. + * * @see io.vertx.core.eventbus.EventBus */ @Target({ METHOD }) @@ -79,6 +80,7 @@ * @return {@code null} if it should use a default MessageCodec * @see io.quarkus.vertx.LocalEventBusCodec */ + @SuppressWarnings("rawtypes") Class codec() default LocalEventBusCodec.class; } From ca7a42d5f92cf11ec9d8c0dbbb4b3a5eae5f5ddc Mon Sep 17 00:00:00 2001 From: David Walluck Date: Tue, 10 Dec 2019 22:50:59 -0500 Subject: [PATCH 315/602] The quarkus.index-dependency..classifier property should be optional --- .../quarkus/deployment/index/ApplicationArchiveBuildStep.java | 2 +- .../io/quarkus/deployment/index/IndexDependencyConfig.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java index e4c79e20ea409..5d38c35198fa2 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java @@ -72,7 +72,7 @@ static final class IndexDependencyConfiguration { void addConfiguredIndexedDependencies(BuildProducer indexDependencyBuildItemBuildProducer) { for (IndexDependencyConfig indexDependencyConfig : config.indexDependency.values()) { indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem(indexDependencyConfig.groupId, - indexDependencyConfig.artifactId, indexDependencyConfig.classifier)); + indexDependencyConfig.artifactId, indexDependencyConfig.classifier.orElse(null))); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java index bb8c39099d7a3..418dad5d7ba4b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexDependencyConfig.java @@ -1,5 +1,7 @@ package io.quarkus.deployment.index; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -22,6 +24,6 @@ public class IndexDependencyConfig { * The maven classifier of the artifact to index */ @ConfigItem - String classifier; + Optional classifier; } From 895f00405feb60f11733964a5799253312975163 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2019 20:35:21 +0000 Subject: [PATCH 316/602] Bump flyway-core from 6.1.0 to 6.1.1 Bumps [flyway-core](https://github.com/flyway/flyway) from 6.1.0 to 6.1.1. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-6.1.0...flyway-6.1.1) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index bf37223984a96..c1e81b26579f3 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -146,7 +146,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.1.0 + 6.1.1 1.0.5 4.0.0-beta03 3.10.2 From 7b6d0792d084c192861e776725ef521979a2a718 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 11 Dec 2019 18:38:51 +0530 Subject: [PATCH 317/602] issue-6070 Ignore empty lines in jaxb.index file --- .../main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java index 722aeb05b54bb..d8f62b43e8b79 100644 --- a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java +++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java @@ -218,8 +218,9 @@ private void handleJaxbFile(Path p) { addResource(path); for (String line : Files.readAllLines(p)) { - if (!line.startsWith("#")) { - String clazz = pkg + line.trim(); + line = line.trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + String clazz = pkg + line; Class cl = Class.forName(clazz); while (cl != Object.class) { From 180497a14435938e39f0fdb28ece948756be3087 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 9 Dec 2019 12:02:04 -0300 Subject: [PATCH 318/602] Show command line in 'gradle quarkusDev' in one line --- .../io/quarkus/gradle/tasks/QuarkusDev.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 17b789afebc30..0455c676251ca 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -1,5 +1,7 @@ package io.quarkus.gradle.tasks; +import static java.util.stream.Collectors.joining; + import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -80,8 +82,9 @@ public QuarkusDev() { @InputDirectory @Optional public File getBuildDir() { - if (buildDir == null) + if (buildDir == null) { buildDir = getProject().getBuildDir(); + } return buildDir; } @@ -92,10 +95,11 @@ public void setBuildDir(File buildDir) { @Optional @InputDirectory public File getSourceDir() { - if (sourceDir == null) + if (sourceDir == null) { return extension().sourceDir(); - else + } else { return new File(sourceDir); + } } @Option(description = "Set source directory", option = "source-dir") @@ -106,10 +110,11 @@ public void setSourceDir(String sourceDir) { @Optional @InputDirectory public File getWorkingDir() { - if (workingDir == null) + if (workingDir == null) { return extension().workingDir(); - else + } else { return new File(workingDir); + } } @Option(description = "Set working directory", option = "working-dir") @@ -259,8 +264,9 @@ public void startDev() { StringBuilder resources = new StringBuilder(); String res = null; for (File file : extension.resourcesDir()) { - if (resources.length() > 0) + if (resources.length() > 0) { resources.append(File.pathSeparator); + } resources.append(file.getAbsolutePath()); res = file.getAbsolutePath(); } @@ -341,15 +347,11 @@ public void startDev() { args.add("-jar"); args.add(tempFile.getAbsolutePath()); - ProcessBuilder pb = new ProcessBuilder(args.toArray(new String[0])); - pb.redirectErrorStream(true); - pb.redirectInput(ProcessBuilder.Redirect.INHERIT); - pb.directory(getWorkingDir()); - System.out.println("Starting process: "); - pb.command().forEach(System.out::println); - System.out.println("Args: "); - args.forEach(System.out::println); - + ProcessBuilder pb = new ProcessBuilder(args) + .redirectErrorStream(true) + .redirectInput(ProcessBuilder.Redirect.INHERIT) + .directory(getWorkingDir()); + System.out.printf("Launching JVM with command line: %s%n", pb.command().stream().collect(joining(" "))); Process p = pb.start(); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override From a6a00215e1300f21bc59143441b85a291bf54b69 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 10 Dec 2019 09:27:18 -0300 Subject: [PATCH 319/602] Shutdown ExecutorService after using it --- .../src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 0455c676251ca..2c58b65a06aab 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -362,7 +362,7 @@ public void run() { try { ExecutorService es = Executors.newSingleThreadExecutor(); es.submit(() -> copyOutputToConsole(p.getInputStream())); - + es.shutdown(); p.waitFor(); } catch (Exception e) { p.destroy(); From 0dd2e4494c2d2de6ef5063aa69e645f917eb2b47 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 9 Dec 2019 09:47:56 -0600 Subject: [PATCH 320/602] Drop unused build item classes These classes are not exposed to build step APIs and should be dropped --- .../io/quarkus/builder/BuildChainBuilder.java | 38 +--- .../java/io/quarkus/builder/BuildContext.java | 150 +------------ .../builder/BuildExecutionBuilder.java | 46 +--- .../java/io/quarkus/builder/BuildResult.java | 6 +- .../io/quarkus/builder/BuildStepBuilder.java | 207 +----------------- .../main/java/io/quarkus/builder/ItemId.java | 28 +-- .../quarkus/builder/item/NamedBuildItem.java | 12 - .../builder/item/NamedMultiBuildItem.java | 12 - .../builder/item/SymbolicBuildItem.java | 36 --- 9 files changed, 28 insertions(+), 507 deletions(-) delete mode 100644 core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java delete mode 100644 core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java delete mode 100644 core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java index e9d41f5001f2f..d99f28909b296 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java @@ -22,8 +22,6 @@ import org.wildfly.common.Assert; import io.quarkus.builder.item.BuildItem; -import io.quarkus.builder.item.NamedBuildItem; -import io.quarkus.builder.item.SymbolicBuildItem; /** * A build chain builder. @@ -105,23 +103,7 @@ public BuildStepBuilder addBuildStep() { */ public BuildChainBuilder addInitial(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - initialIds.add(new ItemId(type, null)); - return this; - } - - public BuildChainBuilder addInitial(Enum symbolic) { - Assert.checkNotNullParam("symbolic", symbolic); - initialIds.add(new ItemId(SymbolicBuildItem.class, symbolic)); - return this; - } - - public BuildChainBuilder addInitial(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - initialIds.add(new ItemId(type, name)); + initialIds.add(new ItemId(type)); return this; } @@ -143,23 +125,7 @@ public BuildChainBuilder loadProviders(ClassLoader classLoader) throws ChainBuil */ public BuildChainBuilder addFinal(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot consume a named build item without a name"); - } - finalIds.add(new ItemId(type, null)); - return this; - } - - public BuildChainBuilder addFinal(Enum symbolic) { - Assert.checkNotNullParam("symbolic", symbolic); - finalIds.add(new ItemId(SymbolicBuildItem.class, symbolic)); - return this; - } - - public BuildChainBuilder addFinal(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - finalIds.add(new ItemId(type, name)); + finalIds.add(new ItemId(type)); return this; } diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildContext.java b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java index d86ff32f763d4..f787bac7f64d2 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildContext.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java @@ -15,8 +15,6 @@ import io.quarkus.builder.diag.Diagnostic; import io.quarkus.builder.item.BuildItem; import io.quarkus.builder.item.MultiBuildItem; -import io.quarkus.builder.item.NamedBuildItem; -import io.quarkus.builder.item.NamedMultiBuildItem; import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.builder.location.Location; @@ -57,10 +55,7 @@ public String getBuildTargetName() { */ public void produce(BuildItem item) { Assert.checkNotNullParam("item", item); - if (item instanceof NamedBuildItem) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - doProduce(new ItemId(item.getClass(), null), item); + doProduce(new ItemId(item.getClass()), item); } /** @@ -72,7 +67,7 @@ public void produce(BuildItem item) { public void produce(List items) { Assert.checkNotNullParam("items", items); for (MultiBuildItem item : items) { - doProduce(new ItemId(item.getClass(), null), item); + doProduce(new ItemId(item.getClass()), item); } } @@ -88,42 +83,7 @@ public void produce(List items) { */ public void produce(Class type, T item) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - doProduce(new ItemId(type, null), type.cast(item)); - } - - /** - * Produce the given item. If the {@code type} refers to a item which is declared with multiplicity, then this - * method can be called more than once for the given {@code type}, otherwise it must be called no more than once. - * - * @param name the build item name (must not be {@code null}) - * @param item the item value (must not be {@code null}) - * @throws IllegalArgumentException if the item does not allow multiplicity but this method is called more than one time, - * or if the type of item could not be determined - */ - public void produce(N name, NamedBuildItem item) { - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("item", item); - doProduce(new ItemId(item.getClass(), name), item); - } - - /** - * Produce the given item. If the {@code type} refers to a item which is declared with multiplicity, then this - * method can be called more than once for the given {@code type}, otherwise it must be called no more than once. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @param item the item value (must not be {@code null}) - * @throws IllegalArgumentException if the item does not allow multiplicity but this method is called more than one time, - * or if the type of item could not be determined - */ - public > void produce(Class type, N name, NamedBuildItem item) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("item", item); - doProduce(new ItemId(type, name), item); + doProduce(new ItemId(type), type.cast(item)); } /** @@ -140,33 +100,7 @@ public T consume(Class type) { if (!running) { throw Messages.msg.buildStepNotRunning(); } - final ItemId id = new ItemId(type, null); - if (id.isMulti()) { - throw Messages.msg.cannotMulti(id); - } - if (!stepInfo.getConsumes().contains(id)) { - throw Messages.msg.undeclaredItem(id); - } - return type.cast(execution.getSingles().get(id)); - } - - /** - * Consume the value produced for the named item. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return the produced item (may be {@code null}) - * @throws IllegalArgumentException if this deployer was not declared to consume {@code type}, or if {@code type} is - * {@code null} - * @throws ClassCastException if the cast failed - */ - public > T consume(Class type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - if (!running) { - throw Messages.msg.buildStepNotRunning(); - } - final ItemId id = new ItemId(type, name); + final ItemId id = new ItemId(type); if (id.isMulti()) { throw Messages.msg.cannotMulti(id); } @@ -191,35 +125,7 @@ public List consumeMulti(Class type) { if (!running) { throw Messages.msg.buildStepNotRunning(); } - final ItemId id = new ItemId(type, null); - if (!id.isMulti()) { - // can happen if obj changes base class - throw Messages.msg.cannotMulti(id); - } - if (!stepInfo.getConsumes().contains(id)) { - throw Messages.msg.undeclaredItem(id); - } - return new ArrayList<>((List) (List) execution.getMultis().getOrDefault(id, Collections.emptyList())); - } - - /** - * Consume all of the values produced for the named item. If the - * item type implements {@link Comparable}, it will be sorted by natural order before return. The returned list - * is a mutable copy. - * - * @param type the item element type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return the produced items (may be empty, will not be {@code null}) - * @throws IllegalArgumentException if this deployer was not declared to consume {@code type}, or if {@code type} is - * {@code null} - */ - public > List consumeMulti(Class type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - if (!running) { - throw Messages.msg.buildStepNotRunning(); - } - final ItemId id = new ItemId(type, name); + final ItemId id = new ItemId(type); if (!id.isMulti()) { // can happen if obj changes base class throw Messages.msg.cannotMulti(id); @@ -246,22 +152,6 @@ public List consumeMulti(Class type, Comparator return result; } - /** - * Consume all of the values produced for the named item, re-sorting it according - * to the given comparator. The returned list is a mutable copy. - * - * @param type the item element type (must not be {@code null}) - * @param comparator the comparator to use (must not be {@code null}) - * @return the produced items (may be empty, will not be {@code null}) - * @throws IllegalArgumentException if this deployer was not declared to consume {@code type}, or if {@code type} is - * {@code null} - */ - public > List consumeMulti(Class type, N name, Comparator comparator) { - final List result = consumeMulti(type, name); - result.sort(comparator); - return result; - } - /** * Determine if a item was produced and is therefore available to be {@linkplain #consume(Class) consumed}. * @@ -270,21 +160,7 @@ public > List consumeMulti(Class type, * not consume the named item */ public boolean isAvailableToConsume(Class type) { - final ItemId id = new ItemId(type, null); - return stepInfo.getConsumes().contains(id) && id.isMulti() - ? !execution.getMultis().getOrDefault(id, Collections.emptyList()).isEmpty() - : execution.getSingles().containsKey(id); - } - - /** - * Determine if a item was produced and is therefore available to be {@linkplain #consume(Class) consumed}. - * - * @param type the item type (must not be {@code null}) - * @return {@code true} if the item was produced and is available, {@code false} if it was not or if this deployer does - * not consume the named item - */ - public boolean isAvailableToConsume(Class> type, N name) { - final ItemId id = new ItemId(type, name); + final ItemId id = new ItemId(type); return stepInfo.getConsumes().contains(id) && id.isMulti() ? !execution.getMultis().getOrDefault(id, Collections.emptyList()).isEmpty() : execution.getSingles().containsKey(id); @@ -299,19 +175,7 @@ public boolean isAvailableToConsume(Class> type, * not produce the named item */ public boolean isConsumed(Class type) { - return execution.getBuildChain().getConsumed().contains(new ItemId(type, null)); - } - - /** - * Determine if a item will be consumed in this build. If a item is not consumed, then build steps are not - * required to produce it. - * - * @param type the item type (must not be {@code null}) - * @return {@code true} if the item will be consumed, {@code false} if it will not be or if this deployer does - * not produce the named item - */ - public boolean isConsumed(Class> type, N name) { - return execution.getBuildChain().getConsumed().contains(new ItemId(type, name)); + return execution.getBuildChain().getConsumed().contains(new ItemId(type)); } /** diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java index 59a9a0f9d233a..a2303b83993bd 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java @@ -9,7 +9,6 @@ import org.wildfly.common.Assert; import io.quarkus.builder.item.BuildItem; -import io.quarkus.builder.item.NamedBuildItem; /** * A builder for a deployer execution. @@ -49,10 +48,7 @@ public String getBuildTargetName() { */ public BuildExecutionBuilder produce(T item) { Assert.checkNotNullParam("item", item); - if (item instanceof NamedBuildItem) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - produce(new ItemId(item.getClass(), null), item); + produce(new ItemId(item.getClass()), item); return this; } @@ -69,45 +65,7 @@ public BuildExecutionBuilder produce(T item) { public BuildExecutionBuilder produce(Class type, T item) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("item", item); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - produce(new ItemId(type, null), item); - return this; - } - - /** - * Provide an initial item. - * - * @param name the build item name (must not be {@code null}) - * @param item the item value - * @return this builder - * @throws IllegalArgumentException if this deployer chain was not declared to initially produce {@code type}, - * or if the item does not allow multiplicity but this method is called more than one time - */ - public > BuildExecutionBuilder produce(N name, T item) { - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("item", item); - produce(new ItemId(item.getClass(), name), item); - return this; - } - - /** - * Provide an initial item. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @param item the item value - * @return this builder - * @throws IllegalArgumentException if this deployer chain was not declared to initially produce {@code type}, - * or if {@code type} is {@code null}, or if the item does not allow multiplicity but this method is called - * more than one time - */ - public > BuildExecutionBuilder produce(Class type, N name, T item) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("item", item); - produce(new ItemId(type, name), item); + produce(new ItemId(type), item); return this; } diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildResult.java b/core/builder/src/main/java/io/quarkus/builder/BuildResult.java index c37b132a8ef4d..2219e2dbd6f02 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildResult.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildResult.java @@ -42,7 +42,7 @@ public final class BuildResult { * @throws ClassCastException if the cast failed */ public T consume(Class type) { - final ItemId itemId = new ItemId(type, null); + final ItemId itemId = new ItemId(type); final Object item = simpleItems.get(itemId); if (item == null) { throw Messages.msg.undeclaredItem(itemId); @@ -58,7 +58,7 @@ public T consume(Class type) { * @throws ClassCastException if the cast failed */ public T consumeOptional(Class type) { - final ItemId itemId = new ItemId(type, null); + final ItemId itemId = new ItemId(type); final Object item = simpleItems.get(itemId); if (item == null) { return null; @@ -74,7 +74,7 @@ public T consumeOptional(Class type) { * @throws IllegalArgumentException if this deployer was not declared to consume {@code type} */ public List consumeMulti(Class type) { - final ItemId itemId = new ItemId(type, null); + final ItemId itemId = new ItemId(type); @SuppressWarnings("unchecked") final List items = (List) (List) multiItems.get(itemId); if (items == null) { diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java index b19f09d9162e4..37ca7451a0b4a 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java @@ -9,8 +9,6 @@ import io.quarkus.builder.item.BuildItem; import io.quarkus.builder.item.EmptyBuildItem; -import io.quarkus.builder.item.NamedBuildItem; -import io.quarkus.builder.item.SymbolicBuildItem; /** * A builder for build step instances within a chain. A build step can consume and produce items. It may also register @@ -47,10 +45,7 @@ public BuildStepBuilder setBuildStep(final BuildStep buildStep) { */ public BuildStepBuilder beforeConsume(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot consume a named build item without a name"); - } - addProduces(new ItemId(type, null), Constraint.ORDER_ONLY, ProduceFlags.NONE); + addProduces(new ItemId(type), Constraint.ORDER_ONLY, ProduceFlags.NONE); return this; } @@ -65,42 +60,7 @@ public BuildStepBuilder beforeConsume(Class type) { public BuildStepBuilder beforeConsume(Class type, ProduceFlag flag) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("flag", flag); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot consume a named build item without a name"); - } - addProduces(new ItemId(type, null), Constraint.ORDER_ONLY, ProduceFlags.of(flag)); - return this; - } - - /** - * This build step should complete before any build steps which consume the given item {@code type} are initiated. - * If no such build steps exist, no ordering constraint is enacted. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder beforeConsume(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - addProduces(new ItemId(type, name), Constraint.ORDER_ONLY, ProduceFlags.NONE); - return this; - } - - /** - * This build step should complete before any build steps which consume the given item {@code type} are initiated. - * If no such build steps exist, no ordering constraint is enacted. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @param flag the producer flag to apply (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder beforeConsume(Class> type, N name, ProduceFlag flag) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("flag", flag); - addProduces(new ItemId(type, name), Constraint.ORDER_ONLY, ProduceFlags.of(flag)); + addProduces(new ItemId(type), Constraint.ORDER_ONLY, ProduceFlags.of(flag)); return this; } @@ -113,25 +73,7 @@ public BuildStepBuilder beforeConsume(Class> typ */ public BuildStepBuilder afterProduce(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } - addConsumes(new ItemId(type, null), Constraint.ORDER_ONLY, ConsumeFlags.of(ConsumeFlag.OPTIONAL)); - return this; - } - - /** - * This build step should be initiated after any build steps which produce the given item {@code type} are completed. - * If no such build steps exist, no ordering constraint is enacted. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder afterProduce(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - addConsumes(new ItemId(type, name), Constraint.ORDER_ONLY, ConsumeFlags.of(ConsumeFlag.OPTIONAL)); + addConsumes(new ItemId(type), Constraint.ORDER_ONLY, ConsumeFlags.of(ConsumeFlag.OPTIONAL)); return this; } @@ -145,13 +87,10 @@ public BuildStepBuilder afterProduce(Class> type */ public BuildStepBuilder produces(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot produce an empty build item"); } - addProduces(new ItemId(type, null), Constraint.REAL, ProduceFlags.NONE); + addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.NONE); return this; } @@ -167,13 +106,10 @@ public BuildStepBuilder produces(Class type) { public BuildStepBuilder produces(Class type, ProduceFlag flag) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("flag", flag); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot produce an empty build item"); } - addProduces(new ItemId(type, null), Constraint.REAL, ProduceFlags.of(flag)); + addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.of(flag)); return this; } @@ -190,13 +126,10 @@ public BuildStepBuilder produces(Class type, ProduceFlag fl public BuildStepBuilder produces(Class type, ProduceFlag flag1, ProduceFlag flag2) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("flag", flag1); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot produce an empty build item"); } - addProduces(new ItemId(type, null), Constraint.REAL, ProduceFlags.of(flag1).with(flag2)); + addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.of(flag1).with(flag2)); return this; } @@ -212,73 +145,10 @@ public BuildStepBuilder produces(Class type, ProduceFlag fl public BuildStepBuilder produces(Class type, ProduceFlags flags) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("flag", flags); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot produce a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot produce an empty build item"); } - addProduces(new ItemId(type, null), Constraint.REAL, flags); - return this; - } - - /** - * Similarly to {@link #beforeConsume(Class)}, establish that this build step must come before the consumer(s) of the - * given item {@code type}; however, only one {@code producer} may exist for the given item. In addition, the - * build step may produce an actual value for this item, which will be shared to all consumers during deployment. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder produces(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - addProduces(new ItemId(type, name), Constraint.REAL, ProduceFlags.NONE); - return this; - } - - /** - * Similarly to {@link #beforeConsume(Class)}, establish that this build step must come before the consumer(s) of the - * given item {@code type}; however, only one {@code producer} may exist for the given item. In addition, the - * build step may produce an actual value for this item, which will be shared to all consumers during deployment. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @param flag the producer flag to apply (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder produces(Class> type, N name, ProduceFlag flag) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - Assert.checkNotNullParam("flag", flag); - addProduces(new ItemId(type, name), Constraint.REAL, ProduceFlags.of(flag)); - return this; - } - - /** - * Declare that the build step "produces" an empty item with the given identifier. - * - * @param symbolic the item identifier (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder beforeVirtual(Enum symbolic) { - Assert.checkNotNullParam("symbolic", symbolic); - addProduces(new ItemId(SymbolicBuildItem.class, symbolic), Constraint.ORDER_ONLY, ProduceFlags.NONE); - return this; - } - - /** - * Declare that the build step "produces" a virtual item with the given identifier. - * - * @param symbolic the item identifier (must not be {@code null}) - * @param flag the producer flag to apply (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder beforeVirtual(Enum symbolic, ProduceFlag flag) { - Assert.checkNotNullParam("symbolic", symbolic); - Assert.checkNotNullParam("flag", flag); - addProduces(new ItemId(SymbolicBuildItem.class, symbolic), Constraint.ORDER_ONLY, ProduceFlags.of(flag)); + addProduces(new ItemId(type), Constraint.REAL, flags); return this; } @@ -291,28 +161,10 @@ public BuildStepBuilder beforeVirtual(Enum symbolic, ProduceFlag flag) { */ public BuildStepBuilder consumes(Class type) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot consume a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot consume an empty build item"); } - addConsumes(new ItemId(type, null), Constraint.REAL, ConsumeFlags.NONE); - return this; - } - - /** - * This build step consumes the given produced item. The item must be produced somewhere in the chain. If - * no such producer exists, the chain will not be constructed; instead, an error will be raised. - * - * @param type the item type (must not be {@code null}) - * @param name the build item name (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder consumes(Class> type, N name) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - addConsumes(new ItemId(type, name), Constraint.REAL, ConsumeFlags.NONE); + addConsumes(new ItemId(type), Constraint.REAL, ConsumeFlags.NONE); return this; } @@ -326,51 +178,10 @@ public BuildStepBuilder consumes(Class> type, N */ public BuildStepBuilder consumes(Class type, ConsumeFlags flags) { Assert.checkNotNullParam("type", type); - if (NamedBuildItem.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Cannot consume a named build item without a name"); - } if (EmptyBuildItem.class.isAssignableFrom(type)) { throw new IllegalArgumentException("Cannot consume an empty build item"); } - addConsumes(new ItemId(type, null), Constraint.REAL, flags); - return this; - } - - /** - * This build step consumes the given produced item. The item must be produced somewhere in the chain. If - * no such producer exists, the chain will not be constructed; instead, an error will be raised. - * - * @param type the item type (must not be {@code null}) - * @param flags a set of flags which modify the consume operation (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder consumes(Class> type, N name, ConsumeFlags flags) { - Assert.checkNotNullParam("type", type); - Assert.checkNotNullParam("name", name); - addConsumes(new ItemId(type, name), Constraint.REAL, flags); - return this; - } - - /** - * Declare that the build step "consumes" a virtual item with the given identifier. - * - * @param symbolic the item identifier (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder afterVirtual(Enum symbolic) { - addConsumes(new ItemId(SymbolicBuildItem.class, symbolic), Constraint.REAL, ConsumeFlags.NONE); - return this; - } - - /** - * Declare that the build step "consumes" a virtual item with the given identifier. - * - * @param symbolic the item identifier (must not be {@code null}) - * @param flags a set of flags which modify the consume operation (must not be {@code null}) - * @return this builder - */ - public BuildStepBuilder afterVirtual(Enum symbolic, ConsumeFlags flags) { - addConsumes(new ItemId(SymbolicBuildItem.class, symbolic), Constraint.REAL, flags); + addConsumes(new ItemId(type), Constraint.REAL, flags); return this; } diff --git a/core/builder/src/main/java/io/quarkus/builder/ItemId.java b/core/builder/src/main/java/io/quarkus/builder/ItemId.java index d9a2bebb98565..e7f2708a2cb29 100644 --- a/core/builder/src/main/java/io/quarkus/builder/ItemId.java +++ b/core/builder/src/main/java/io/quarkus/builder/ItemId.java @@ -6,27 +6,19 @@ import io.quarkus.builder.item.BuildItem; import io.quarkus.builder.item.MultiBuildItem; -import io.quarkus.builder.item.NamedBuildItem; -import io.quarkus.builder.item.NamedMultiBuildItem; /** */ final class ItemId { private final Class itemType; - private final Object name; - ItemId(final Class itemType, final Object name) { + ItemId(final Class itemType) { Assert.checkNotNullParam("itemType", itemType); - if (NamedBuildItem.class.isAssignableFrom(itemType)) { - // todo: support default names - Assert.checkNotNullParam("name", name); - } this.itemType = itemType; - this.name = name; } boolean isMulti() { - return MultiBuildItem.class.isAssignableFrom(itemType) || NamedMultiBuildItem.class.isAssignableFrom(itemType); + return MultiBuildItem.class.isAssignableFrom(itemType); } @Override @@ -35,27 +27,17 @@ public boolean equals(Object obj) { } boolean equals(ItemId obj) { - return this == obj || obj != null && itemType == obj.itemType && Objects.equals(name, obj.name); + return this == obj || obj != null && itemType == obj.itemType; } @Override public int hashCode() { - return Objects.hashCode(name) * 31 + Objects.hashCode(itemType); + return Objects.hashCode(itemType); } @Override public String toString() { - final Object name = this.name; - final Class itemType = this.itemType; - if (name == null) { - assert itemType != null; - return itemType.toString(); - } else if (itemType == null) { - assert name != null; - return "name " + name.toString(); - } else { - return itemType.toString() + " with name " + name; - } + return itemType.toString(); } Class getType() { diff --git a/core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java deleted file mode 100644 index b156358bb708b..0000000000000 --- a/core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.quarkus.builder.item; - -/** - * A build item that can occur more than once in a build, discriminated by name. - * - * @param the name type - */ -@SuppressWarnings("unused") -public abstract class NamedBuildItem extends BuildItem { - NamedBuildItem() { - } -} diff --git a/core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java deleted file mode 100644 index 512de226103ef..0000000000000 --- a/core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.quarkus.builder.item; - -/** - * A build item that can occur more than once in a build, discriminated by name. - * - * @param the name type - */ -@SuppressWarnings("unused") -public abstract class NamedMultiBuildItem extends BuildItem { - NamedMultiBuildItem() { - } -} diff --git a/core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java deleted file mode 100644 index ac53fadc6dd91..0000000000000 --- a/core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.quarkus.builder.item; - -/** - * The symbolic build item. - */ -public final class SymbolicBuildItem extends NamedBuildItem> { - - private static final SymbolicBuildItem INSTANCE = new SymbolicBuildItem(); - - private SymbolicBuildItem() { - } - - /** - * Get the singleton instance. - * - * @return the singleton instance (not {@code null}) - */ - public static SymbolicBuildItem getInstance() { - return INSTANCE; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean equals(final Object obj) { - return obj == this; - } - - @Override - public String toString() { - return "symbolic"; - } -} From f15857b59316b3cfb25bd81fdbc4a0cfb7cb8562 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 12 Dec 2019 09:40:50 +1100 Subject: [PATCH 321/602] Make JSON logging default to enabled The docs say that this is enabled by default, and IMHO this makes sense. --- .../main/java/io/quarkus/logging/json/runtime/JsonConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java index 1d6f2d0972140..31e50df91d66c 100644 --- a/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java +++ b/extensions/logging-json/runtime/src/main/java/io/quarkus/logging/json/runtime/JsonConfig.java @@ -16,7 +16,7 @@ public class JsonConfig { /** * Determine whether to enable the JSON console formatting extension, which disables "normal" console formatting. */ - @ConfigItem(name = ConfigItem.PARENT) + @ConfigItem(name = ConfigItem.PARENT, defaultValue = "true") boolean enable; /** * Enable "pretty printing" of the JSON record. Note that some JSON parsers will fail to read pretty printed output. From 6e96ea306bb1ca18e0802899b5c5e90d6ba5b99a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Dec 2019 21:55:36 +0100 Subject: [PATCH 322/602] Bump tika-parsers from 1.21 to 1.22 in /bom/runtime Bumps [tika-parsers](https://github.com/apache/tika) from 1.21 to 1.22. - [Release notes](https://github.com/apache/tika/releases) - [Changelog](https://github.com/apache/tika/blob/master/CHANGES.txt) - [Commits](https://github.com/apache/tika/compare/1.21...1.22) Signed-off-by: dependabot[bot] --- bom/runtime/pom.xml | 2 +- .../test/java/io/quarkus/tika/deployment/TikaProcessorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index c1e81b26579f3..7c809d40e5c94 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -131,7 +131,7 @@ 0.9.5.Final 3.4.14 2.12.8 - 1.21 + 1.22 1.1.0 2.2.5 1.3.1 diff --git a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java index f91b52b559e51..e396c5db78a2b 100644 --- a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java +++ b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java @@ -76,6 +76,6 @@ public void testUnresolvableCustomAbbreviation() throws Exception { public void testAllSupportedParserNames() throws Exception { Optional parserNames = Optional.ofNullable(null); List names = TikaProcessor.getSupportedParserNames(parserNames); - assertEquals(68, names.size()); + assertEquals(69, names.size()); } } From b5369e46ed56b173bcacf0b18416c9d59c84058e Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 10 Dec 2019 16:38:01 +0100 Subject: [PATCH 323/602] Fix an artemis-commons dependency convergence issue We don't have it in Quarkus but we have it in Camel Quarkus. Artemis dependencies are not consistent. --- bom/runtime/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 7c809d40e5c94..5b86261c611c3 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -1323,6 +1323,11 @@ artemis-jms-client ${artemis.version} + + org.apache.activemq + artemis-commons + ${artemis.version} + org.apache.httpcomponents httpclient From 2392a4c88e6efa33562a1e6f724cbba59ce2312b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 11 Dec 2019 16:50:16 +0100 Subject: [PATCH 324/602] Add artemis-commons to Dependabot configuration --- .dependabot/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.dependabot/config.yml b/.dependabot/config.yml index 3e88b136060b1..6645a5f7e2dbe 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -13,6 +13,8 @@ update_configs: dependency_name: "org.apache.activemq:artemis-jms-client" - match: dependency_name: "org.apache.activemq:artemis-server" + - match: + dependency_name: "org.apache.activemq:artemis-commons" - match: dependency_name: "org.flywaydb:flyway-core" - match: From bd0af8254b7fbcf77136eebbbea87d20832ad64e Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 11 Dec 2019 22:29:42 -0300 Subject: [PATCH 325/602] Add Talkdesk to ADOPTERS.md --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 1e093f273a45f..a281e8315f20e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -13,4 +13,5 @@ If any organization would like get added or removed please make a pull request b | Organization | Reference | |---------------|------------| |GoWithFlow | https://quarkus.io/blog/gowithflow-chooses-quarkus-to-deliver-fast-to-production/ | +|Talkdesk | https://quarkus.io/blog/talkdesk-chooses-quarkus-for-fast-innovation/ | |Vodafone Greece| https://quarkus.io/blog/vodafone-greece-replaces-spring-boot/| From 544823b24ce01f2f24888b062c8214b1c31240a4 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Dec 2019 00:31:55 +0100 Subject: [PATCH 326/602] Fix the title of generated config doc for uncategorized properties --- .../processor/generate_doc/SummaryTableDocFormatter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java index 42aa12accdeae..d270d7c349cd5 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java @@ -33,7 +33,9 @@ public void format(Writer writer, String initialAnchorPrefix, boolean activateSe // make sure that section-less configs get a legend if (configDocItems.isEmpty() || configDocItems.get(0).isConfigKey()) { String anchor = anchorPrefix + getAnchor("configuration"); - writer.append(String.format(TABLE_SECTION_ROW_FORMAT, anchor, anchor, "Configuration property")); + writer.append(String.format(TABLE_SECTION_ROW_FORMAT, + String.format(SECTION_TITLE, anchor, anchor, "Configuration property"), + Constants.EMPTY)); } for (ConfigDocItem configDocItem : configDocItems) { From 871e44f98180a07751cb1b2f641d14cc75ecf4d9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Dec 2019 00:32:20 +0100 Subject: [PATCH 327/602] Use the generated doc for JSON logging configuration --- docs/src/main/asciidoc/logging.adoc | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 6e9eaf319769f..e37151f25e315 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -110,17 +110,7 @@ This means that the format string and the color settings (if any) will be ignore The JSON logging extension can be configured in various ways. The following properties are supported: -[cols=" Date: Thu, 12 Dec 2019 00:32:35 +0100 Subject: [PATCH 328/602] Fix styles of Type/Default titles They are overridden on the website anyway so better get these readable in the standard documentation. --- docs/src/main/asciidoc/stylesheet/config.css | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/main/asciidoc/stylesheet/config.css b/docs/src/main/asciidoc/stylesheet/config.css index 90dc54fcf58ed..8e3d860bc6ea3 100644 --- a/docs/src/main/asciidoc/stylesheet/config.css +++ b/docs/src/main/asciidoc/stylesheet/config.css @@ -37,7 +37,6 @@ table.configuration-reference.tableblock tbody tr td:nth-child(3) { table.configuration-reference.tableblock tbody tr th:nth-child(2) p, table.configuration-reference.tableblock tbody tr th:nth-child(3) p { font-weight: normal; - color: white; } table.configuration-reference.tableblock tbody tr th p { From d245930648d6841555a80acb755361eadcf27633 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Wed, 11 Dec 2019 14:03:40 -0300 Subject: [PATCH 329/602] Introduce JSch extension Fixes #6110 Add JSch integration tests --- bom/deployment/pom.xml | 5 + bom/runtime/pom.xml | 17 +++ ci-templates/stages.yml | 1 + extensions/jgit/deployment/pom.xml | 4 + .../jgit/deployment/JGitProcessor.java | 51 ++++++++ extensions/jgit/runtime/pom.xml | 4 + extensions/jsch/deployment/pom.xml | 45 +++++++ .../jsch/deployment/JSchProcessor.java} | 45 ++----- extensions/jsch/pom.xml | 21 ++++ extensions/jsch/runtime/pom.xml | 53 ++++++++ .../jsch}/runtime/PortWatcherRunTime.java | 2 +- .../runtime/PortWatcherSubstitutions.java | 2 +- .../resources/META-INF/quarkus-extension.yaml | 11 ++ extensions/pom.xml | 1 + integration-tests/jsch/pom.xml | 118 ++++++++++++++++++ .../java/io/quarkus/it/jsch/JSchResource.java | 24 ++++ .../java/io/quarkus/it/jsch/JSchTest.java | 55 ++++++++ .../java/io/quarkus/it/jsch/NativeJSchIT.java | 7 ++ integration-tests/pom.xml | 1 + 19 files changed, 429 insertions(+), 38 deletions(-) create mode 100644 extensions/jgit/deployment/src/main/java/io/quarkus/jgit/deployment/JGitProcessor.java create mode 100644 extensions/jsch/deployment/pom.xml rename extensions/{jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java => jsch/deployment/src/main/java/io/quarkus/jsch/deployment/JSchProcessor.java} (60%) create mode 100644 extensions/jsch/pom.xml create mode 100644 extensions/jsch/runtime/pom.xml rename extensions/{jgit/runtime/src/main/java/io/quarkus/jgit => jsch/runtime/src/main/java/io/quarkus/jsch}/runtime/PortWatcherRunTime.java (90%) rename extensions/{jgit/runtime/src/main/java/io/quarkus/jgit => jsch/runtime/src/main/java/io/quarkus/jsch}/runtime/PortWatcherSubstitutions.java (96%) create mode 100644 extensions/jsch/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 integration-tests/jsch/pom.xml create mode 100644 integration-tests/jsch/src/main/java/io/quarkus/it/jsch/JSchResource.java create mode 100644 integration-tests/jsch/src/test/java/io/quarkus/it/jsch/JSchTest.java create mode 100644 integration-tests/jsch/src/test/java/io/quarkus/it/jsch/NativeJSchIT.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index c7526647b8595..e5b0394e55ca2 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -496,6 +496,11 @@ quarkus-jgit-deployment ${project.version} + + io.quarkus + quarkus-jsch-deployment + ${project.version} + io.quarkus quarkus-vault-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 5b86261c611c3..0a8931d4a6241 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -173,6 +173,8 @@ 1.0.1.Final 8.0.1 1.13.0 + 0.1.55 + 1.1.1 @@ -697,6 +699,11 @@ quarkus-jgit ${project.version} + + io.quarkus + quarkus-jsch + ${project.version} + io.quarkus quarkus-narayana-stm @@ -1488,6 +1495,16 @@ plexus-component-annotations ${plexus-component-annotations.version} + + com.jcraft + jsch + ${jsch.version} + + + com.jcraft + jzlib + ${jzlib.version} + org.eclipse.jgit org.eclipse.jgit diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 68115c5247de9..2170d208a96a6 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -328,6 +328,7 @@ stages: modules: - jackson - jsonb + - jsch - jgit name: misc_1 diff --git a/extensions/jgit/deployment/pom.xml b/extensions/jgit/deployment/pom.xml index 718cadd3e92ab..37c9edb7af380 100644 --- a/extensions/jgit/deployment/pom.xml +++ b/extensions/jgit/deployment/pom.xml @@ -18,6 +18,10 @@ io.quarkus quarkus-core-deployment + + io.quarkus + quarkus-jsch-deployment + io.quarkus quarkus-jgit diff --git a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/deployment/JGitProcessor.java b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/deployment/JGitProcessor.java new file mode 100644 index 0000000000000..8d1beacf272d2 --- /dev/null +++ b/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/deployment/JGitProcessor.java @@ -0,0 +1,51 @@ +package io.quarkus.jgit.deployment; + +import java.util.Arrays; +import java.util.List; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; + +class JGitProcessor { + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.JGIT); + } + + @BuildStep + ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { + return new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.JGIT); + } + + @BuildStep + ReflectiveClassBuildItem reflection() { + //Classes that use reflection + return new ReflectiveClassBuildItem(true, true, + "org.eclipse.jgit.api.MergeCommand$FastForwardMode", + "org.eclipse.jgit.api.MergeCommand$FastForwardMode$Merge", + "org.eclipse.jgit.internal.JGitText", + "org.eclipse.jgit.lib.CoreConfig$AutoCRLF", + "org.eclipse.jgit.lib.CoreConfig$CheckStat", + "org.eclipse.jgit.lib.CoreConfig$EOL", + "org.eclipse.jgit.lib.CoreConfig$EolStreamType", + "org.eclipse.jgit.lib.CoreConfig$HideDotFiles", + "org.eclipse.jgit.lib.CoreConfig$SymLinks"); + } + + @BuildStep + List runtimeInitializedClasses() { + return Arrays.asList( + new RuntimeInitializedClassBuildItem("org.eclipse.jgit.transport.HttpAuthMethod$Digest"), + new RuntimeInitializedClassBuildItem("org.eclipse.jgit.lib.GpgSigner")); + } + + @BuildStep + NativeImageResourceBundleBuildItem includeResourceBundle() { + return new NativeImageResourceBundleBuildItem("org.eclipse.jgit.internal.JGitText"); + } +} diff --git a/extensions/jgit/runtime/pom.xml b/extensions/jgit/runtime/pom.xml index 86662c78b0c7e..aa16e954a1d42 100644 --- a/extensions/jgit/runtime/pom.xml +++ b/extensions/jgit/runtime/pom.xml @@ -18,6 +18,10 @@ org.graalvm.nativeimage svm + + io.quarkus + quarkus-jsch + org.eclipse.jgit org.eclipse.jgit diff --git a/extensions/jsch/deployment/pom.xml b/extensions/jsch/deployment/pom.xml new file mode 100644 index 0000000000000..8d49d3e7a269e --- /dev/null +++ b/extensions/jsch/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + io.quarkus + quarkus-jsch-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-jsch-deployment + Quarkus - JSch - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-jsch + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java b/extensions/jsch/deployment/src/main/java/io/quarkus/jsch/deployment/JSchProcessor.java similarity index 60% rename from extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java rename to extensions/jsch/deployment/src/main/java/io/quarkus/jsch/deployment/JSchProcessor.java index 4211b7a9c38c3..fdca53f4d33d7 100644 --- a/extensions/jgit/deployment/src/main/java/io/quarkus/jgit/runtime/deployment/JGitProcessor.java +++ b/extensions/jsch/deployment/src/main/java/io/quarkus/jsch/deployment/JSchProcessor.java @@ -1,26 +1,21 @@ -package io.quarkus.jgit.runtime.deployment; - -import java.util.Arrays; -import java.util.List; +package io.quarkus.jsch.deployment; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; +import io.quarkus.deployment.builditem.EnableAllSecurityServicesBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; -import io.quarkus.jgit.runtime.PortWatcherRunTime; +import io.quarkus.jsch.runtime.PortWatcherRunTime; -class JGitProcessor { +class JSchProcessor { @BuildStep - FeatureBuildItem feature() { - return new FeatureBuildItem(FeatureBuildItem.JGIT); + EnableAllSecurityServicesBuildItem enableAllSecurityServices() { + return new EnableAllSecurityServicesBuildItem(); } @BuildStep - ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { - return new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.JGIT); + RuntimeInitializedClassBuildItem runtimeInitialized() { + return new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName()); } @BuildStep @@ -75,28 +70,6 @@ ReflectiveClassBuildItem reflection() { "com.jcraft.jsch.UserAuthKeyboardInteractive", "com.jcraft.jsch.UserAuthNone", "com.jcraft.jsch.UserAuthPassword", - "com.jcraft.jsch.UserAuthPublicKey", - "org.eclipse.jgit.api.MergeCommand$FastForwardMode", - "org.eclipse.jgit.api.MergeCommand$FastForwardMode$Merge", - "org.eclipse.jgit.internal.JGitText", - "org.eclipse.jgit.lib.CoreConfig$AutoCRLF", - "org.eclipse.jgit.lib.CoreConfig$CheckStat", - "org.eclipse.jgit.lib.CoreConfig$EOL", - "org.eclipse.jgit.lib.CoreConfig$EolStreamType", - "org.eclipse.jgit.lib.CoreConfig$HideDotFiles", - "org.eclipse.jgit.lib.CoreConfig$SymLinks"); - } - - @BuildStep - List runtimeInitializedClasses() { - return Arrays.asList( - new RuntimeInitializedClassBuildItem("org.eclipse.jgit.transport.HttpAuthMethod$Digest"), - new RuntimeInitializedClassBuildItem("org.eclipse.jgit.lib.GpgSigner"), - new RuntimeInitializedClassBuildItem(PortWatcherRunTime.class.getName())); - } - - @BuildStep - NativeImageResourceBundleBuildItem includeResourceBundle() { - return new NativeImageResourceBundleBuildItem("org.eclipse.jgit.internal.JGitText"); + "com.jcraft.jsch.UserAuthPublicKey"); } } diff --git a/extensions/jsch/pom.xml b/extensions/jsch/pom.xml new file mode 100644 index 0000000000000..4cd0b125115df --- /dev/null +++ b/extensions/jsch/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + + quarkus-jsch-parent + Quarkus - JSch - Parent + + pom + + deployment + runtime + + diff --git a/extensions/jsch/runtime/pom.xml b/extensions/jsch/runtime/pom.xml new file mode 100644 index 0000000000000..a25f26e47fe8c --- /dev/null +++ b/extensions/jsch/runtime/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + io.quarkus + quarkus-jsch-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-jsch + Quarkus - JSch - Runtime + JSch is a pure Java implementation of SSH2 and allows you to connect to an sshd server and use port forwarding, X11 forwarding, file transfer, etc. + + + + org.graalvm.nativeimage + svm + + + com.jcraft + jsch + + + com.jcraft + jzlib + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java b/extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherRunTime.java similarity index 90% rename from extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java rename to extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherRunTime.java index 16e0c2d15cd43..c31ffe81d82a3 100644 --- a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherRunTime.java +++ b/extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherRunTime.java @@ -1,4 +1,4 @@ -package io.quarkus.jgit.runtime; +package io.quarkus.jsch.runtime; import java.net.InetAddress; import java.net.UnknownHostException; diff --git a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java b/extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherSubstitutions.java similarity index 96% rename from extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java rename to extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherSubstitutions.java index f4123fcf39142..3640228b26021 100644 --- a/extensions/jgit/runtime/src/main/java/io/quarkus/jgit/runtime/PortWatcherSubstitutions.java +++ b/extensions/jsch/runtime/src/main/java/io/quarkus/jsch/runtime/PortWatcherSubstitutions.java @@ -1,4 +1,4 @@ -package io.quarkus.jgit.runtime; +package io.quarkus.jsch.runtime; import java.net.InetAddress; diff --git a/extensions/jsch/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/jsch/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..8063b678d7ca1 --- /dev/null +++ b/extensions/jsch/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "JSch" +metadata: + keywords: + - "jsch" + - "ssh" + - "ssh2" + categories: + - "miscellaneous" + status: "stable" + unlisted: "true" \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index bf78ad80e2d89..0e1ab681ad6bd 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -78,6 +78,7 @@ panache hibernate-search-elasticsearch elasticsearch-rest-client + jsch jgit kafka-client kafka-streams diff --git a/integration-tests/jsch/pom.xml b/integration-tests/jsch/pom.xml new file mode 100644 index 0000000000000..ae4372af8484b --- /dev/null +++ b/integration-tests/jsch/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + quarkus-integration-test-jsch + Quarkus - Integration Tests - JSch + JSch integration tests module + + + true + + + + + io.quarkus + quarkus-jsch + + + + io.quarkus + quarkus-resteasy + + + + + io.quarkus + quarkus-junit5 + test + + + + org.apache.sshd + sshd-core + 2.3.0 + test + + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + + diff --git a/integration-tests/jsch/src/main/java/io/quarkus/it/jsch/JSchResource.java b/integration-tests/jsch/src/main/java/io/quarkus/it/jsch/JSchResource.java new file mode 100644 index 0000000000000..cc7f038d214f0 --- /dev/null +++ b/integration-tests/jsch/src/main/java/io/quarkus/it/jsch/JSchResource.java @@ -0,0 +1,24 @@ +package io.quarkus.it.jsch; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; + +@Path("/jsch") +public class JSchResource { + + @GET + public Response connect(@QueryParam("host") String host, @QueryParam("port") int port) throws Exception { + JSch jsch = new JSch(); + Session session = jsch.getSession(null, host, port); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(); + String serverVersion = session.getServerVersion(); + session.disconnect(); + return Response.ok(serverVersion).build(); + } +} diff --git a/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/JSchTest.java b/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/JSchTest.java new file mode 100644 index 0000000000000..f6cc8e2432ec0 --- /dev/null +++ b/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/JSchTest.java @@ -0,0 +1,55 @@ +package io.quarkus.it.jsch; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.core.Is.is; + +import java.net.InetAddress; +import java.nio.file.Files; + +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.auth.hostbased.AcceptAllHostBasedAuthenticator; +import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator; +import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.server.shell.UnknownCommandFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class JSchTest { + + private SshServer sshd; + + @BeforeEach + public void setupSSHDServer() throws Exception { + sshd = SshServer.setUpDefaultServer(); + sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(Files.createTempFile("host", "key"))); + sshd.setHostBasedAuthenticator(AcceptAllHostBasedAuthenticator.INSTANCE); + sshd.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE); + sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE); + sshd.setCommandFactory(UnknownCommandFactory.INSTANCE); + sshd.setHost(InetAddress.getLocalHost().getHostName()); + sshd.start(); + } + + @Test + void shouldConnect() { + given().queryParam("host", sshd.getHost()) + .queryParam("port", sshd.getPort()) + .get("/jsch") + .then() + .statusCode(is(200)) + .body(endsWith(sshd.getVersion())); + } + + @AfterEach + void stopServer() throws Exception { + if (sshd != null) { + sshd.stop(true); + } + } +} diff --git a/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/NativeJSchIT.java b/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/NativeJSchIT.java new file mode 100644 index 0000000000000..23d3fb19c5eb5 --- /dev/null +++ b/integration-tests/jsch/src/test/java/io/quarkus/it/jsch/NativeJSchIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.jsch; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativeJSchIT extends JSchTest { +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 19ae199ca53fd..07a44e6242e93 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -74,6 +74,7 @@ jsonb resteasy-jackson jgit + jsch virtual-http virtual-http-resteasy artemis-core From e19a27cb5f54b04b02b58c67adc941a96c2dc016 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Tue, 10 Dec 2019 23:16:54 +0100 Subject: [PATCH 330/602] Substitute BootLoader.hasClassPath() to work around a JDK 11 NPE --- .../graal/BootLoaderSubstitutions.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java new file mode 100644 index 0000000000000..46def1012a5a0 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java @@ -0,0 +1,45 @@ +package io.quarkus.runtime.graal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK11OrLater; + +/* + * The `hasClassPath()` method substitution from this class is required to work around a NPE when `BootLoader.hasClassPath()` + * is called. This was fixed in a GraalVM commit (https://github.com/oracle/graal/commit/68027de) which will be backported to + * 19.3.1. We copied the entire GraalVM commit content into Quarkus for the sake of consistency. + * See https://github.com/oracle/graal/issues/1966 for more details about the NPE. + */ +@TargetClass(className = "jdk.internal.loader.BootLoader", onlyWith = JDK11OrLater.class) +final class Target_jdk_internal_loader_BootLoader { + + @Substitute + private static boolean hasClassPath() { + return true; + } + + @Substitute + private static URL findResource(String mn, String name) { + return ClassLoader.getSystemClassLoader().getResource(name); + } + + @Substitute + private static InputStream findResourceAsStream(String mn, String name) { + return ClassLoader.getSystemClassLoader().getResourceAsStream(name); + } + + @Substitute + private static URL findResource(String name) { + return ClassLoader.getSystemClassLoader().getResource(name); + } + + @Substitute + private static Enumeration findResources(String name) throws IOException { + return ClassLoader.getSystemClassLoader().getResources(name); + } +} From cfcc75dd15c0040f0206a29f24c8d003f790efaa Mon Sep 17 00:00:00 2001 From: Alexander Zimmermann Date: Wed, 11 Dec 2019 17:09:10 +0100 Subject: [PATCH 331/602] Add documentation about YAML configuration --- docs/src/main/asciidoc/config.adoc | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 020d3df4611b4..5dfdd3fea0d54 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -573,6 +573,61 @@ priority of 100. NOTE: This new converter also needs to be listed in a service file, i.e. `META-INF/services/org.eclipse.microprofile.config.spi.Converter`. +[[yaml]] +== YAML for Configuration + +=== Add YAML Config Support + +You might want to use YAML over properties for configuration. +Since link:https://github.com/smallrye/smallrye-config[SmallRye Config] brings support for YAML +configuration, Quarkus supports this as well. + +First you will need to add the YAML extension to your `pom.xml`: + +[source,xml] +---- + + io.quarkus + quarkus-config-yaml + +---- +// TODO: Add the command to install this extension with maven. + +Now Quarkus can read YAML configuration files. +The config directories and priorities are the same as before. + +NOTE: Quarkus will choose an `application.properties` over an `application.yaml`. +YAML files are just an alternative way to configure your application. +You should decide and keep one configuration type to avoid errors. + +==== Configuration Example +[source,yaml] +---- +# YAML supports comments +quarkus: + datasource: + url: jdbc:postgresql://localhost:5432/some-database + driver: org.postgresql.Driver + username: quarkus + password: quarkus +---- + +=== Profile dependent configuration + +Providing profile depedent configuration with YAML is done like with properties. +Just add the `%profile` wrapped in quotation marks before defining the key-value pairs: + +[source,yaml] +---- +"%dev": + quarkus: + datasource: + url: jdbc:postgresql://localhost:5432/some-database + driver: org.postgresql.Driver + username: quarkus + password: quarkus +---- + == More info on how to configure Quarkus relies on Eclipse MicroProfile and inherits its features. From d7697389d9023f35b789ed24f0e13122ca834448 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 11 Dec 2019 19:03:06 +0100 Subject: [PATCH 332/602] Documentation for Flyway support for multiple datasources #3449 --- docs/src/main/asciidoc/flyway.adoc | 66 ++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/flyway.adoc b/docs/src/main/asciidoc/flyway.adoc index 4bbe25d9e7785..c2b69a45d9919 100644 --- a/docs/src/main/asciidoc/flyway.adoc +++ b/docs/src/main/asciidoc/flyway.adoc @@ -43,8 +43,10 @@ In your `pom.xml`, add the following dependencies: -- -Flyway support relies on the Quarkus default datasource config, you must add the default datasource properties -to the `{config-file}` file in order to allow Flyway to manage the schema. +Flyway support relies on the Quarkus datasource config. +It can be customized for the default datasource as well as for every <>. +First, you need to add the datasource config to the `{config-file}` file +in order to allow Flyway to manage the schema. Also, you can customize the Flyway behaviour by using the following properties: `quarkus.flyway.migrate-at-start`:: @@ -170,6 +172,52 @@ public class MigrationService { <1> Inject the Flyway object if you want to use it directly +== Multiple datasources + +Flyway can be configured for multiple datasources. +The Flyway properties are prefixed exactly the same way as the named datasources, for example: + +[source,properties] +-- +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:default +quarkus.datasource.username=username-default +quarkus.datasource.min-size=3 +quarkus.datasource.max-size=13 + +quarkus.datasource.users.driver=org.h2.Driver +quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:users +quarkus.datasource.users.username=username1 +quarkus.datasource.users.min-size=1 +quarkus.datasource.users.max-size=11 + +quarkus.datasource.inventory.driver=org.h2.Driver +quarkus.datasource.inventory.url=jdbc:h2:tcp://localhost/mem:inventory +quarkus.datasource.inventory.username=username2 +quarkus.datasource.inventory.min-size=2 +quarkus.datasource.inventory.max-size=12 + +# Flyway configuration for the default datasource +quarkus.flyway.schemas=DEFAULT_TEST_SCHEMA +quarkus.flyway.locations=db/default/location1,db/default/location2 +quarkus.flyway.migrate-at-start=true + +# Flyway configuration for the "users" datasource +quarkus.flyway.users.schemas=USERS_TEST_SCHEMA +quarkus.flyway.users.locations=db/users/location1,db/users/location2 +quarkus.flyway.users.migrate-at-start=true + +# Flyway configuration for the "inventory" datasource +quarkus.flyway.inventory.schemas=INVENTORY_TEST_SCHEMA +quarkus.flyway.inventory.locations=db/inventory/location1,db/inventory/location2 +quarkus.flyway.inventory.migrate-at-start=true +-- + +Notice there's an extra bit in the key. +The syntax is as follows: `quarkus.flyway.[optional name.][datasource property]`. + +NOTE: Without configuration, Flyway is set up for every datasource using the default settings. + == Using the Flyway object In case you are interested in using the `Flyway` object directly, you can inject it as follows: @@ -185,9 +233,17 @@ public class MigrationService { @Inject Flyway flyway; <1> + @Inject + @FlywayDataSource("inventory") <2> + Flyway flywayForInventory; + + @Inject + @Named("flyway_users") <3> + Flyway flywayForUsers; + public void checkMigration() { // Use the flyway instance manually - flyway.clean(); <2> + flyway.clean(); <4> flyway.migrate(); // This will print 1.0.0 System.out.println(flyway.info().current().getVersion().toString()); @@ -196,5 +252,7 @@ public class MigrationService { -- <1> Inject the Flyway object if you want to use it directly -<2> Use the Flyway instance directly +<2> Inject Flyway for named datasources using the Quarkus `FlywayDataSource` qualifier +<3> Inject Flyway for named datasources +<4> Use the Flyway instance directly From 1b3c59b18ff9126a55c785f031fb0c4725b80762 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 9 Dec 2019 13:38:18 +0000 Subject: [PATCH 333/602] Update KC authorization test to return the data only if 'from-body' key exists --- .../src/main/java/io/quarkus/it/keycloak/ProtectedResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java index beb96fe320251..2a41c6268256f 100644 --- a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -59,7 +59,7 @@ public List httpResponseClaimProtected() { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public List bodyClaim(Map body, @Context HttpServerRequest request) { - if (body == null && !body.containsKey("from-body")) { + if (body == null || !body.containsKey("from-body")) { return Collections.emptyList(); } return identity.getAttribute("permissions"); From 8f99ccf5c348161cc1fb65b9ccfcfd9528bb1537 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Tue, 10 Dec 2019 16:27:12 +0100 Subject: [PATCH 334/602] Micro versions bump --- bom/runtime/pom.xml | 12 ++++++++---- test-framework/vault/pom.xml | 5 ----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 0a8931d4a6241..581321c15a6de 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -20,7 +20,7 @@ 0.31.0 0.4.1 0.2.3 - 0.1.5 + 0.1.8 0.2.0 0.0.12 0.34.0 @@ -33,10 +33,10 @@ 1.3.4 1.5.0 2.1.0 - 2.3.1 + 2.3.2 1.1.20 1.3.2 - 2.1.2 + 2.1.3 2.0.10 1.0.11 1.0.10 @@ -1484,7 +1484,11 @@ junit-jupiter ${test-containers.version} - + + org.testcontainers + postgresql + ${test-containers.version} + org.codehaus.plexus plexus-utils diff --git a/test-framework/vault/pom.xml b/test-framework/vault/pom.xml index bee5486a61bf7..c15a3fd4ad139 100644 --- a/test-framework/vault/pom.xml +++ b/test-framework/vault/pom.xml @@ -14,10 +14,6 @@ quarkus-vault-test Quarkus - Vault - Test - - 1.12.0 - - @@ -31,7 +27,6 @@ org.testcontainers postgresql - ${test-containers.version} javax.xml.bind From 1fae2f94f6b7e07317b1711d5a3970099ec30aa3 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 12 Dec 2019 09:47:08 -0300 Subject: [PATCH 335/602] Fixed annotation name in writing extensions documentation --- docs/src/main/asciidoc/writing-extensions.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 4940bd430e058..3e4a2fff423ef 100755 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -633,7 +633,7 @@ conceptual goal that does not have a concrete representation. * causing this step to be run. */ @BuildStep -@Produces(NativeImageBuildItem.class) +@Produce(NativeImageBuildItem.class) void produceNativeImage() { // ... // (produce the native image) @@ -649,7 +649,7 @@ void produceNativeImage() { * an instance of {@code SomeOtherBuildItem}. */ @BuildStep -@Consumes(NativeImageBuildItem.class) +@Consume(NativeImageBuildItem.class) SomeOtherBuildItem secondBuildStep() { return new SomeOtherBuildItem("foobar"); } @@ -691,7 +691,7 @@ A build step may produce values for subsequent steps in several possible ways: - By returning a <> or <> instance - By returning a `List` of a multi build item class - By injecting a `BuildProducer` of a simple or multi build item class -- By annotating the method with `@io.quarkus.deployment.annotations.Produces`, giving the class name of a +- By annotating the method with `@io.quarkus.deployment.annotations.Produce`, giving the class name of a <> If a simple build item is declared on a build step, it _must_ be produced during that build step, otherwise an error @@ -711,7 +711,7 @@ A build step may consume values from previous steps in the following ways: - By injecting a <> - By injecting an `Optional` of a simple build item class - By injecting a `List` of a <> class -- By annotating the method with `@io.quarkus.deployment.annotations.Consumes`, giving the class name of a +- By annotating the method with `@io.quarkus.deployment.annotations.Consume`, giving the class name of a <> Normally it is an error for a step which is included to consume a simple build item that is not produced by any other From ccc4ee72cb546042c6c61898c0bb73bd43d7bf6e Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 11 Dec 2019 11:33:28 +0100 Subject: [PATCH 336/602] ArC - introduce ObserverTransformer build extension --- .../quarkus/arc/deployment/ArcProcessor.java | 13 +- .../ObserverTransformerBuildItem.java | 20 ++ .../observer/ObserverTransformerTest.java | 122 ++++++++ .../AbstractAnnotationsTransformation.java | 74 +++++ .../arc/processor/AnnotationStore.java | 42 +-- .../processor/AnnotationsTransformation.java | 79 +++++ .../AnnotationsTransformationContext.java | 62 ++++ .../arc/processor/AnnotationsTransformer.java | 5 +- .../quarkus/arc/processor/BeanDeployment.java | 34 ++- .../quarkus/arc/processor/BeanProcessor.java | 27 +- .../arc/processor/InjectionPointModifier.java | 57 +--- .../processor/InjectionPointsTransformer.java | 104 +------ .../quarkus/arc/processor/ObserverInfo.java | 289 +++++++++++++++--- .../arc/processor/ObserverTransformer.java | 133 ++++++++ .../quarkus/arc/processor/Transformation.java | 112 +------ .../io/quarkus/arc/processor/TypesTest.java | 4 + .../io/quarkus/arc/test/ArcTestContainer.java | 18 +- 17 files changed, 845 insertions(+), 350 deletions(-) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverTransformerBuildItem.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/observer/ObserverTransformerTest.java create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractAnnotationsTransformation.java create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformation.java create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformationContext.java create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index c46dc8d18a281..26d063318d417 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -97,6 +97,7 @@ public ContextRegistrationPhaseBuildItem initialize( ApplicationArchivesBuildItem applicationArchivesBuildItem, List annotationTransformers, List injectionPointTransformers, + List observerTransformers, List interceptorBindingRegistrarBuildItems, List additionalStereotypeBuildItems, List applicationClassPredicates, @@ -172,12 +173,16 @@ public void transform(TransformationContext transformationContext) { builder.addResourceAnnotations( resourceAnnotations.stream().map(ResourceAnnotationBuildItem::getName).collect(Collectors.toList())); // register all annotation transformers - for (AnnotationsTransformerBuildItem transformerItem : annotationTransformers) { - builder.addAnnotationTransformer(transformerItem.getAnnotationsTransformer()); + for (AnnotationsTransformerBuildItem transformer : annotationTransformers) { + builder.addAnnotationTransformer(transformer.getAnnotationsTransformer()); } // register all injection point transformers - for (InjectionPointTransformerBuildItem transformerItem : injectionPointTransformers) { - builder.addInjectionPointTransformer(transformerItem.getInjectionPointsTransformer()); + for (InjectionPointTransformerBuildItem transformer : injectionPointTransformers) { + builder.addInjectionPointTransformer(transformer.getInjectionPointsTransformer()); + } + // register all observer transformers + for (ObserverTransformerBuildItem transformer : observerTransformers) { + builder.addObserverTransformer(transformer.getInstance()); } // register additional interceptor bindings for (InterceptorBindingRegistrarBuildItem bindingRegistrar : interceptorBindingRegistrarBuildItems) { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverTransformerBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverTransformerBuildItem.java new file mode 100644 index 0000000000000..296cd1a527630 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ObserverTransformerBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.arc.processor.ObserverTransformer; +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This build item is used to register an {@link ObserverTransformer} instance. + */ +public final class ObserverTransformerBuildItem extends MultiBuildItem { + + private final ObserverTransformer transformer; + + public ObserverTransformerBuildItem(ObserverTransformer transformer) { + this.transformer = transformer; + } + + public ObserverTransformer getInstance() { + return transformer; + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/observer/ObserverTransformerTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/observer/ObserverTransformerTest.java new file mode 100644 index 0000000000000..5e6d57a1ca69c --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/observer/ObserverTransformerTest.java @@ -0,0 +1,122 @@ +package io.quarkus.arc.test.observer; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import javax.enterprise.event.Event; +import javax.enterprise.event.Observes; +import javax.inject.Qualifier; +import javax.inject.Singleton; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.google.inject.Inject; + +import io.quarkus.arc.deployment.ObserverTransformerBuildItem; +import io.quarkus.arc.processor.ObserverTransformer; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.test.QuarkusUnitTest; + +public class ObserverTransformerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyObserver.class, AlphaQualifier.class, BravoQualifier.class)) + .addBuildChainCustomizer(buildCustomizer()); + + static Consumer buildCustomizer() { + return new Consumer() { + + @Override + public void accept(BuildChainBuilder builder) { + builder.addBuildStep(new BuildStep() { + + @Override + public void execute(BuildContext context) { + context.produce(new ObserverTransformerBuildItem(new ObserverTransformer() { + + @Override + public boolean appliesTo(Type observedType, Set qualifiers) { + return observedType.name().equals(DotName.createSimple(MyEvent.class.getName())); + } + + @Override + public void transform(TransformationContext context) { + if (context.getMethod().name().equals("")) { + context.transform().removeAll().done(); + } + } + + })); + } + }).produces(ObserverTransformerBuildItem.class).build(); + } + }; + } + + @BravoQualifier + @Inject + Event event; + + @Test + public void testTransformation() { + MyEvent myEvent = new MyEvent(); + event.fire(myEvent); + // MyObserver.onMyEventRemoveQualifiers() would not match without transformation + assertEquals(1, myEvent.log.size()); + assertEquals("onMyEventRemoveQualifiers", myEvent.log.get(0)); + } + + @Singleton + static class MyObserver { + + void onMyEventRemoveQualifiers(@Observes @BravoQualifier MyEvent event) { + event.log.add("onMyEventRemoveQualifiers"); + } + + } + + @Qualifier + @Inherited + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + public @interface AlphaQualifier { + + } + + @Qualifier + @Inherited + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + public @interface BravoQualifier { + + } + + static class MyEvent { + + final List log = new ArrayList<>(); + + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractAnnotationsTransformation.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractAnnotationsTransformation.java new file mode 100644 index 0000000000000..fe916de11bad8 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractAnnotationsTransformation.java @@ -0,0 +1,74 @@ +package io.quarkus.arc.processor; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.function.Predicate; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; + +abstract class AbstractAnnotationsTransformation, C extends Collection> + implements AnnotationsTransformation { + + private final AnnotationTarget target; + private final Consumer resultConsumer; + protected final C modifiedAnnotations; + + /** + * + * @param annotations Mutable collection of annotations + * @param target + * @param resultConsumer + */ + public AbstractAnnotationsTransformation(C annotations, AnnotationTarget target, + Consumer resultConsumer) { + this.target = target; + this.resultConsumer = resultConsumer; + this.modifiedAnnotations = annotations; + } + + public T add(AnnotationInstance annotation) { + modifiedAnnotations.add(annotation); + return self(); + } + + public T addAll(Collection annotations) { + modifiedAnnotations.addAll(annotations); + return self(); + } + + public T addAll(AnnotationInstance... annotations) { + Collections.addAll(modifiedAnnotations, annotations); + return self(); + } + + public T add(Class annotationType, AnnotationValue... values) { + add(DotNames.create(annotationType.getName()), values); + return self(); + } + + public T add(DotName name, AnnotationValue... values) { + add(AnnotationInstance.create(name, target, values)); + return self(); + } + + public T remove(Predicate predicate) { + modifiedAnnotations.removeIf(predicate); + return self(); + } + + public T removeAll() { + modifiedAnnotations.clear(); + return self(); + } + + public void done() { + resultConsumer.accept(modifiedAnnotations); + } + + protected abstract T self(); + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java index 95546c919b2bc..8b5a0ffe7b380 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationStore.java @@ -2,7 +2,6 @@ import io.quarkus.arc.processor.AnnotationsTransformer.TransformationContext; import io.quarkus.arc.processor.BuildExtension.BuildContext; -import io.quarkus.arc.processor.BuildExtension.Key; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -95,7 +94,7 @@ private Collection transform(AnnotationTarget target) { if (transformers.isEmpty()) { return annotations; } - TransformationContextImpl transformationContext = new TransformationContextImpl(target, annotations); + TransformationContextImpl transformationContext = new TransformationContextImpl(buildContext, target, annotations); for (AnnotationsTransformer transformer : transformers) { transformer.transform(transformationContext); } @@ -130,44 +129,17 @@ private List initTransformers(Kind kind, Collection> + implements TransformationContext { - private final AnnotationTarget target; - - private Collection annotations; - - TransformationContextImpl(AnnotationTarget target, Collection annotations) { - this.target = target; - this.annotations = annotations; - } - - @Override - public V get(Key key) { - return buildContext.get(key); - } - - @Override - public V put(Key key, V value) { - return buildContext.put(key, value); - } - - @Override - public AnnotationTarget getTarget() { - return target; - } - - @Override - public Collection getAnnotations() { - return annotations; - } - - void setAnnotations(Collection annotations) { - this.annotations = annotations; + public TransformationContextImpl(BuildContext buildContext, AnnotationTarget target, + Collection annotations) { + super(buildContext, target, annotations); } @Override public Transformation transform() { - return new Transformation(this); + return new Transformation(new ArrayList<>(getAnnotations()), getTarget(), this::setAnnotations); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformation.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformation.java new file mode 100644 index 0000000000000..32523819f2d0b --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformation.java @@ -0,0 +1,79 @@ +package io.quarkus.arc.processor; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.function.Predicate; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodParameterInfo; + +/** + * Represents a transformation of a collection of {@link AnnotationInstance} instances. + */ +public interface AnnotationsTransformation> { + + /** + * + * @param annotation + * @return self + */ + T add(AnnotationInstance annotation); + + /** + * + * @param annotations + * @return self + */ + T addAll(Collection annotations); + + /** + * + * @param annotations + * @return self + */ + T addAll(AnnotationInstance... annotations); + + /** + * NOTE: The annotation target is derived from the transformation context. If you need to add an annotation instance + * to a method parameter use methods consuming {@link AnnotationInstance} directly and supply the correct + * {@link MethodParameterInfo}. + * + * @param annotationType + * @param values + * @return self + */ + T add(Class annotationType, AnnotationValue... values); + + /** + * NOTE: The annotation target is derived from the transformation context.. If you need to add an annotation instance + * to a method parameter use methods consuming {@link AnnotationInstance} directly and supply the correct + * {@link MethodParameterInfo}. + * + * @param name + * @param values + * @return self + */ + T add(DotName name, AnnotationValue... values); + + /** + * Remove all annotations matching the given predicate. + * + * @param predicate + * @return self + */ + T remove(Predicate predicate); + + /** + * Remove all annotations. + * + * @return self + */ + T removeAll(); + + /** + * Applies the transformation. + */ + void done(); + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformationContext.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformationContext.java new file mode 100644 index 0000000000000..b98dcad2aed23 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformationContext.java @@ -0,0 +1,62 @@ +package io.quarkus.arc.processor; + +import io.quarkus.arc.processor.BuildExtension.BuildContext; +import io.quarkus.arc.processor.BuildExtension.Key; +import java.util.Collection; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; + +/** + * Transformation context base for an {@link AnnotationsTransformation}. + */ +abstract class AnnotationsTransformationContext> implements BuildContext { + + private final BuildContext buildContext; + private final AnnotationTarget target; + private C annotations; + + /** + * + * @param buildContext + * @param target + * @param annotations Mutable collection of annotations + */ + public AnnotationsTransformationContext(BuildContext buildContext, AnnotationTarget target, + C annotations) { + this.buildContext = buildContext; + this.target = target; + this.annotations = annotations; + } + + @Override + public V get(Key key) { + return buildContext.get(key); + } + + @Override + public V put(Key key, V value) { + return buildContext.put(key, value); + } + + public AnnotationTarget getTarget() { + return target; + } + + public C getAnnotations() { + return annotations; + } + + void setAnnotations(C annotations) { + this.annotations = annotations; + } + + public Collection getAllAnnotations() { + AnnotationStore annotationStore = get(BuildExtension.Key.ANNOTATION_STORE); + if (annotationStore == null) { + throw new IllegalStateException( + "Attempted to use the getAllAnnotations() method but AnnotationStore wasn't initialized."); + } + return annotationStore.getAnnotations(getTarget()); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformer.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformer.java index 7d02597538a83..c24a44fb15ca9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformer.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationsTransformer.java @@ -9,8 +9,7 @@ * Allows a build-time extension to override the annotations that exist on bean classes. *

* The container should use {@link AnnotationStore} to obtain annotations of any {@link org.jboss.jandex.ClassInfo}, - * {@link org.jboss.jandex.FieldInfo} and - * {@link org.jboss.jandex.MethodInfo}. + * {@link org.jboss.jandex.FieldInfo} and {@link org.jboss.jandex.MethodInfo}. * * @author Martin Kouba */ @@ -45,7 +44,7 @@ interface TransformationContext extends BuildContext { Collection getAnnotations(); /** - * The transformation is not applied until {@link Transformation#done()} is invoked. + * The transformation is not applied until the {@link Transformation#done()} method is invoked. * * @return a new transformation */ diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index ac4d0e5475d70..ab626e2a2838e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -78,6 +78,8 @@ public class BeanDeployment { private final InjectionPointModifier injectionPointTransformer; + private final List observerTransformers; + private final Set resourceAnnotations; private final List injectionPoints; @@ -94,12 +96,14 @@ public class BeanDeployment { BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null, false, null, Collections.emptyMap(), Collections.emptyList(), false); } BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers, List injectionPointsTransformers, + List observerTransformers, Collection resourceAnnotations, BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions, Map> additionalStereotypes, @@ -117,6 +121,7 @@ public class BeanDeployment { buildContext.putInternal(Key.ANNOTATION_STORE.asString(), annotationStore); } this.injectionPointTransformer = new InjectionPointModifier(injectionPointsTransformers, buildContext); + this.observerTransformers = observerTransformers; this.removeUnusedBeans = removeUnusedBeans; this.unusedExclusions = removeUnusedBeans ? unusedExclusions : null; this.removedBeans = new CopyOnWriteArraySet<>(); @@ -737,14 +742,14 @@ private List findBeans(Collection beanDefiningAnnotations, Li } } - for (Map.Entry> syncObserverEntry : syncObserverMethods.entrySet()) { - registerObserverMethods(syncObserverEntry.getValue(), observers, injectionPoints, - beanClassToBean, syncObserverEntry.getKey(), false); + for (Map.Entry> entry : syncObserverMethods.entrySet()) { + registerObserverMethods(entry.getValue(), observers, injectionPoints, + beanClassToBean, entry.getKey(), false, observerTransformers); } - for (Map.Entry> syncObserverEntry : asyncObserverMethods.entrySet()) { - registerObserverMethods(syncObserverEntry.getValue(), observers, injectionPoints, - beanClassToBean, syncObserverEntry.getKey(), true); + for (Map.Entry> entry : asyncObserverMethods.entrySet()) { + registerObserverMethods(entry.getValue(), observers, injectionPoints, + beanClassToBean, entry.getKey(), true, observerTransformers); } if (LOGGER.isTraceEnabled()) { @@ -755,19 +760,24 @@ private List findBeans(Collection beanDefiningAnnotations, Li return beans; } - private void registerObserverMethods(Collection classes, + private void registerObserverMethods(Collection beanClasses, List observers, List injectionPoints, Map beanClassToBean, MethodInfo observerMethod, - boolean async) { - for (ClassInfo key : classes) { - BeanInfo declaringBean = beanClassToBean.get(key); + boolean async, List observerTransformers) { + + for (ClassInfo beanClass : beanClasses) { + BeanInfo declaringBean = beanClassToBean.get(beanClass); if (declaringBean != null) { Injection injection = Injection.forObserver(observerMethod, declaringBean.getImplClazz(), this, injectionPointTransformer); - observers.add(new ObserverInfo(declaringBean, observerMethod, injection, async)); - injectionPoints.addAll(injection.injectionPoints); + ObserverInfo observer = ObserverInfo.create(declaringBean, observerMethod, injection, async, + observerTransformers, buildContext); + if (observer != null) { + observers.add(observer); + injectionPoints.addAll(injection.injectionPoints); + } } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 66358dfe3635b..4d5fe245e645e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -66,14 +66,19 @@ public static Builder builder() { private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, ResourceOutput output, - boolean sharedAnnotationLiterals, ReflectionRegistration reflectionRegistration, + boolean sharedAnnotationLiterals, + ReflectionRegistration reflectionRegistration, List annotationTransformers, List injectionPointsTransformers, - Collection resourceAnnotations, List beanRegistrars, + List observerTransformers, + Collection resourceAnnotations, + List beanRegistrars, List contextRegistrars, - List beanDeploymentValidators, Predicate applicationClassPredicate, + List beanDeploymentValidators, + Predicate applicationClassPredicate, boolean unusedBeansRemovalEnabled, - List> unusedExclusions, Map> additionalStereotypes, + List> unusedExclusions, + Map> additionalStereotypes, List interceptorBindingRegistrars, boolean removeFinalForProxyableMethods) { this.reflectionRegistration = reflectionRegistration; @@ -91,7 +96,9 @@ private BeanProcessor(String name, IndexView index, Collection annotationTransformers = new ArrayList<>(); private final List injectionPointTransformers = new ArrayList<>(); + private final List observerTransformers = new ArrayList<>(); private final List beanRegistrars = new ArrayList<>(); private final List contextRegistrars = new ArrayList<>(); private final List additionalInterceptorBindingRegistrars = new ArrayList<>(); @@ -304,6 +312,11 @@ public Builder addInjectionPointTransformer(InjectionPointsTransformer transform return this; } + public Builder addObserverTransformer(ObserverTransformer transformer) { + this.observerTransformers.add(transformer); + return this; + } + public Builder addResourceAnnotations(Collection resourceAnnotations) { this.resourceAnnotations.addAll(resourceAnnotations); return this; @@ -369,8 +382,8 @@ public Builder setRemoveFinalFromProxyableMethods(boolean removeFinalForProxyabl public BeanProcessor build() { return new BeanProcessor(name, index, additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals, - reflectionRegistration, annotationTransformers, injectionPointTransformers, resourceAnnotations, - beanRegistrars, contextRegistrars, beanDeploymentValidators, + reflectionRegistration, annotationTransformers, injectionPointTransformers, observerTransformers, + resourceAnnotations, beanRegistrars, contextRegistrars, beanDeploymentValidators, applicationClassPredicate, removeUnusedBeans, removalExclusions, additionalStereotypes, additionalInterceptorBindingRegistrars, removeFinalForProxyableMethods); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointModifier.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointModifier.java index 5f46647f00209..89ab63f3e5b5a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointModifier.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointModifier.java @@ -1,6 +1,7 @@ package io.quarkus.arc.processor; -import java.util.Collection; +import io.quarkus.arc.processor.BuildExtension.BuildContext; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.jboss.jandex.AnnotationInstance; @@ -19,12 +20,10 @@ public class InjectionPointModifier { private List transformers; private BuildExtension.BuildContext buildContext; - private AnnotationStore annotationStore; InjectionPointModifier(List transformers, BuildExtension.BuildContext buildContext) { this.buildContext = buildContext; this.transformers = transformers; - this.annotationStore = buildContext != null ? buildContext.get(BuildExtension.Key.ANNOTATION_STORE) : null; } public Set applyTransformers(Type type, AnnotationTarget target, Set qualifiers) { @@ -32,8 +31,7 @@ public Set applyTransformers(Type type, AnnotationTarget tar if (transformers.isEmpty()) { return qualifiers; } - TransformationContextImpl transformationContext = new TransformationContextImpl(target, qualifiers, - annotationStore); + TransformationContextImpl transformationContext = new TransformationContextImpl(buildContext, target, qualifiers); for (InjectionPointsTransformer transformer : transformers) { if (transformer.appliesTo(type)) { transformer.transform(transformationContext); @@ -42,55 +40,24 @@ public Set applyTransformers(Type type, AnnotationTarget tar return transformationContext.getQualifiers(); } - class TransformationContextImpl implements InjectionPointsTransformer.TransformationContext { + class TransformationContextImpl extends AnnotationsTransformationContext> + implements InjectionPointsTransformer.TransformationContext { - private AnnotationTarget target; - private Set qualifiers; - private AnnotationStore annotationStore; - - TransformationContextImpl(AnnotationTarget target, Set qualifiers, - AnnotationStore annotationStore) { - this.target = target; - this.qualifiers = qualifiers; - this.annotationStore = annotationStore; - } - - @Override - public AnnotationTarget getTarget() { - return target; - } - - @Override - public Set getQualifiers() { - return qualifiers; - } - - @Override - public Collection getAllAnnotations() { - if (annotationStore == null) { - throw new IllegalStateException( - "Attempted to use TransformationContext#getAllAnnotations but AnnotationStore wasn't initialized."); - } - return annotationStore.getAnnotations(getTarget()); + public TransformationContextImpl(BuildContext buildContext, AnnotationTarget target, + Set annotations) { + super(buildContext, target, annotations); } @Override public InjectionPointsTransformer.Transformation transform() { - return new InjectionPointsTransformer.Transformation(this); + return new InjectionPointsTransformer.Transformation(new HashSet<>(getAnnotations()), getTarget(), + this::setAnnotations); } @Override - public V get(BuildExtension.Key key) { - return buildContext.get(key); - } - - @Override - public V put(BuildExtension.Key key, V value) { - return buildContext.put(key, value); + public Set getQualifiers() { + return getAnnotations(); } - public void setQualifiers(Set qualifiers) { - this.qualifiers = qualifiers; - } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointsTransformer.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointsTransformer.java index c8882e54ee80b..e6d79b4b2937c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointsTransformer.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointsTransformer.java @@ -1,15 +1,10 @@ package io.quarkus.arc.processor; -import java.lang.annotation.Annotation; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Set; -import java.util.function.Predicate; +import java.util.function.Consumer; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.DotName; import org.jboss.jandex.Type; /** @@ -50,7 +45,7 @@ interface TransformationContext extends BuildExtension.BuildContext { * * @return the annotation instances */ - Collection getQualifiers(); + Set getQualifiers(); /** * Retrieves all annotations attached to the {@link AnnotationTarget} that this transformer operates on @@ -66,8 +61,7 @@ interface TransformationContext extends BuildExtension.BuildContext { Collection getAllAnnotations(); /** - * /** - * The transformation is not applied until {@link Transformation#done()} is invoked. + * The transformation is not applied until the {@link Transformation#done()} method is invoked. * * @return a new transformation */ @@ -75,96 +69,18 @@ interface TransformationContext extends BuildExtension.BuildContext { } - final class Transformation { - private final InjectionPointModifier.TransformationContextImpl transformationContext; + final class Transformation extends AbstractAnnotationsTransformation> { - private final Set modified; - - Transformation(InjectionPointModifier.TransformationContextImpl transformationContext) { - this.transformationContext = transformationContext; - this.modified = new HashSet<>(transformationContext.getQualifiers()); - } - - /** - * - * @param annotation - * @return self - */ - public Transformation add(AnnotationInstance annotation) { - modified.add(annotation); - return this; + Transformation(Set annotations, AnnotationTarget target, + Consumer> transformationConsumer) { + super(annotations, target, transformationConsumer); } - /** - * - * @param annotations - * @return self - */ - public Transformation addAll(Collection annotations) { - modified.addAll(annotations); + @Override + protected Transformation self() { return this; } - /** - * - * @param annotations - * @return self - */ - public Transformation addAll(AnnotationInstance... annotations) { - Collections.addAll(modified, annotations); - return this; - } - - /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. - * - * @param annotationType - * @param values - * @return self - */ - public Transformation add(Class annotationType, AnnotationValue... values) { - add(DotNames.create(annotationType.getName()), values); - return this; - } - - /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. - * - * @param name - * @param values - * @return self - */ - public Transformation add(DotName name, AnnotationValue... values) { - add(AnnotationInstance.create(name, transformationContext.getTarget(), values)); - return this; - } - - /** - * - * @param predicate - * @return self - */ - public Transformation remove(Predicate predicate) { - modified.removeIf(predicate); - return this; - } - - /** - * - * @return self - */ - public Transformation removeAll() { - modified.clear(); - return this; - } - - /** - * Applies the transformation. - * - * @see TransformationContext#getQualifiers() - */ - public void done() { - transformationContext.setQualifiers(modified); - } } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java index 32c840c279abe..580033c60d7bc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java @@ -1,5 +1,8 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.BuildExtension.BuildContext; +import io.quarkus.arc.processor.ObserverTransformer.ObserverTransformation; +import io.quarkus.arc.processor.ObserverTransformer.TransformationContext; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -10,6 +13,7 @@ import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.ObserverMethod; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; @@ -24,7 +28,63 @@ * @author Martin Kouba */ public class ObserverInfo implements InjectionTargetInfo { + private static final Logger LOGGER = Logger.getLogger(ObserverInfo.class.getName()); + + static ObserverInfo create(BeanInfo declaringBean, MethodInfo observerMethod, Injection injection, boolean isAsync, + List transformers, BuildContext buildContext) { + // Initialize attributes + MethodParameterInfo eventParameter = initEventParam(observerMethod, declaringBean.getDeployment()); + Type observedType = observerMethod.parameters().get(eventParameter.position()); + Set qualifiers = initQualifiers(declaringBean.getDeployment(), observerMethod, eventParameter); + Reception reception = initReception(isAsync, declaringBean.getDeployment(), observerMethod); + TransactionPhase transactionPhase = initTransactionPhase(isAsync, declaringBean.getDeployment(), observerMethod); + AnnotationInstance priorityAnnotation = observerMethod.annotation(DotNames.PRIORITY); + Integer priority; + if (priorityAnnotation != null && priorityAnnotation.target().equals(eventParameter)) { + priority = priorityAnnotation.value().asInt(); + } else { + priority = ObserverMethod.DEFAULT_PRIORITY; + } + + if (!transformers.isEmpty()) { + // Transform attributes if needed + ObserverTransformationContext context = new ObserverTransformationContext(buildContext, observerMethod, + observedType, qualifiers, reception, transactionPhase, priority, isAsync); + + for (ObserverTransformer transformer : transformers) { + if (transformer.appliesTo(observedType, qualifiers)) { + transformer.transform(context); + if (context.vetoed) { + LOGGER.debugf("Observer method %s.%s() vetoed by %s", observerMethod.declaringClass().name(), + observerMethod.name(), transformer.getClass().getName()); + break; + } + } + } + if (context.vetoed) { + // Veto the observer method + return null; + } + qualifiers = context.getQualifiers(); + reception = context.getReception(); + transactionPhase = context.getTransactionPhase(); + priority = context.getPriority(); + isAsync = context.isAsync(); + } + + if (!TransactionPhase.IN_PROGRESS.equals(transactionPhase)) { + final ClassInfo clazz = observerMethod.declaringClass(); + LOGGER.warnf("The method %s#%s makes use of '%s' transactional observers which are not implemented yet.", clazz, + observerMethod.name(), transactionPhase); + } + + // Create an immutable observer metadata + return new ObserverInfo(declaringBean, observerMethod, injection, eventParameter, isAsync, priority, reception, + transactionPhase, qualifiers); + + } + private final BeanInfo declaringBean; private final MethodInfo observerMethod; @@ -39,19 +99,25 @@ public class ObserverInfo implements InjectionTargetInfo { private final boolean isAsync; - ObserverInfo(BeanInfo declaringBean, MethodInfo observerMethod, Injection injection, boolean isAsync) { + private final Reception reception; + + private final TransactionPhase transactionPhase; + + private final Set qualifiers; + + ObserverInfo(BeanInfo declaringBean, MethodInfo observerMethod, Injection injection, MethodParameterInfo eventParameter, + boolean isAsync, int priority, Reception reception, TransactionPhase transactionPhase, + Set qualifiers) { this.declaringBean = declaringBean; this.observerMethod = observerMethod; this.injection = injection; - this.eventParameter = initEventParam(observerMethod); + this.eventParameter = eventParameter; this.eventMetadataParameterPosition = initEventMetadataParam(observerMethod); - AnnotationInstance priorityAnnotation = observerMethod.annotation(DotNames.PRIORITY); - if (priorityAnnotation != null && priorityAnnotation.target().equals(eventParameter)) { - this.priority = priorityAnnotation.value().asInt(); - } else { - this.priority = ObserverMethod.DEFAULT_PRIORITY; - } this.isAsync = isAsync; + this.priority = priority; + this.reception = reception; + this.transactionPhase = transactionPhase; + this.qualifiers = qualifiers; } @Override @@ -76,6 +142,14 @@ public MethodParameterInfo getEventParameter() { return eventParameter; } + public Reception getReception() { + return reception; + } + + public TransactionPhase getTransactionPhase() { + return transactionPhase; + } + int getEventMetadataParameterPosition() { return eventMetadataParameterPosition; } @@ -88,10 +162,24 @@ public boolean isAsync() { return isAsync; } - public Reception getReception() { + public Type getObservedType() { + return observerMethod.parameters().get(eventParameter.position()); + } + + public Set getQualifiers() { + return qualifiers; + } + + void init(List errors) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + Beans.resolveInjectionPoint(declaringBean.getDeployment(), this, injectionPoint, errors); + } + } + + static Reception initReception(boolean isAsync, BeanDeployment beanDeployment, MethodInfo observerMethod) { AnnotationInstance observesAnnotation = isAsync - ? declaringBean.getDeployment().getAnnotation(observerMethod, DotNames.OBSERVES_ASYNC) - : declaringBean.getDeployment().getAnnotation(observerMethod, DotNames.OBSERVES); + ? beanDeployment.getAnnotation(observerMethod, DotNames.OBSERVES_ASYNC) + : beanDeployment.getAnnotation(observerMethod, DotNames.OBSERVES); AnnotationValue receptionValue = observesAnnotation.value("notifyObserver"); if (receptionValue == null) { return Reception.ALWAYS; @@ -99,21 +187,23 @@ public Reception getReception() { return Reception.valueOf(receptionValue.asEnum()); } - void init(List errors) { - for (InjectionPointInfo injectionPoint : injection.injectionPoints) { - Beans.resolveInjectionPoint(declaringBean.getDeployment(), this, injectionPoint, errors); + static TransactionPhase initTransactionPhase(boolean isAsync, BeanDeployment beanDeployment, MethodInfo observerMethod) { + AnnotationInstance observesAnnotation = isAsync + ? beanDeployment.getAnnotation(observerMethod, DotNames.OBSERVES_ASYNC) + : beanDeployment.getAnnotation(observerMethod, DotNames.OBSERVES); + AnnotationValue duringValue = observesAnnotation.value("during"); + if (duringValue == null) { + return TransactionPhase.IN_PROGRESS; } + return TransactionPhase.valueOf(duringValue.asEnum()); } - public Type getObservedType() { - return observerMethod.parameters().get(eventParameter.position()); - } - - public Set getQualifiers() { + static Set initQualifiers(BeanDeployment beanDeployment, MethodInfo observerMethod, + MethodParameterInfo eventParameter) { Set qualifiers = new HashSet<>(); - for (AnnotationInstance annotation : declaringBean.getDeployment().getAnnotations(observerMethod)) { + for (AnnotationInstance annotation : beanDeployment.getAnnotations(observerMethod)) { if (annotation.target().equals(eventParameter) - && declaringBean.getDeployment().getQualifier(annotation.name()) != null) { + && beanDeployment.getQualifier(annotation.name()) != null) { qualifiers.add(annotation); } } @@ -124,27 +214,14 @@ int getPriority() { return priority; } - MethodParameterInfo initEventParam(MethodInfo observerMethod) { + static MethodParameterInfo initEventParam(MethodInfo observerMethod, BeanDeployment beanDeployment) { List eventParams = new ArrayList<>(); - - for (AnnotationInstance annotation : declaringBean.getDeployment().getAnnotations(observerMethod)) { + for (AnnotationInstance annotation : beanDeployment.getAnnotations(observerMethod)) { if (Kind.METHOD_PARAMETER == annotation.target().kind() && (annotation.name().equals(DotNames.OBSERVES) || annotation.name().equals(DotNames.OBSERVES_ASYNC))) { eventParams.add(annotation.target().asMethodParameter()); - - /** - * Log a warning for usage of transactional observers other than IN_PROGRESS since they are not implemented yet: - * https://github.com/quarkusio/quarkus/issues/2224. - */ - final AnnotationValue during = annotation.value("during"); - if (isTransactionalObserver(during) && !isDuringTransactionInProgress(during)) { - final ClassInfo clazz = observerMethod.declaringClass(); - final String transactionalObserverWarning = "The method %s#%s makes use of '%s' transactional observers which are not implemented yet."; - LOGGER.warnf(transactionalObserverWarning, clazz, observerMethod.name(), during.asEnum()); - } } } - if (eventParams.isEmpty()) { throw new DefinitionException("No event parameters found for " + observerMethod); } else if (eventParams.size() > 1) { @@ -153,15 +230,6 @@ MethodParameterInfo initEventParam(MethodInfo observerMethod) { return eventParams.get(0); } - private boolean isTransactionalObserver(AnnotationValue during) { - return during != null && AnnotationValue.Kind.ENUM.equals(during.kind()) - && during.asEnumType().equals(DotNames.TRANSACTION_PHASE); - } - - private boolean isDuringTransactionInProgress(AnnotationValue during) { - return TransactionPhase.IN_PROGRESS.name().equals(during.asEnum()); - } - int initEventMetadataParam(MethodInfo observerMethod) { for (ListIterator iterator = observerMethod.parameters().listIterator(); iterator.hasNext();) { if (iterator.next().name().equals(DotNames.EVENT_METADATA)) { @@ -171,4 +239,135 @@ int initEventMetadataParam(MethodInfo observerMethod) { return -1; } + private static class ObserverTransformationContext extends AnnotationsTransformationContext> + implements TransformationContext { + + private final Type observedType; + private Reception reception; + private TransactionPhase transactionPhase; + private Integer priority; + private boolean vetoed; + private boolean async; + + public ObserverTransformationContext(BuildContext buildContext, AnnotationTarget target, + Type observedType, Set qualifiers, Reception reception, TransactionPhase transactionPhase, + Integer priority, boolean async) { + super(buildContext, target, qualifiers); + this.observedType = observedType; + this.reception = reception; + this.transactionPhase = transactionPhase; + this.priority = priority; + this.async = async; + } + + @Override + public MethodInfo getMethod() { + return getTarget().asMethod(); + } + + @Override + public Type getObservedType() { + return observedType; + } + + @Override + public Set getQualifiers() { + return getAnnotations(); + } + + @Override + public Reception getReception() { + return reception; + } + + @Override + public TransactionPhase getTransactionPhase() { + return transactionPhase; + } + + public Integer getPriority() { + return priority; + } + + @Override + public boolean isAsync() { + return async; + } + + @Override + public void veto() { + this.vetoed = true; + } + + @Override + public ObserverTransformation transform() { + return new ObserverTransformationImpl(new HashSet<>(getAnnotations()), getMethod(), this); + } + + } + + private static class ObserverTransformationImpl + extends AbstractAnnotationsTransformation> + implements ObserverTransformation { + + private final ObserverTransformationContext context; + private Integer priority; + private Reception reception; + private TransactionPhase transactionPhase; + private Boolean async; + + public ObserverTransformationImpl(Set qualifiers, MethodInfo observerMethod, + ObserverTransformationContext context) { + super(qualifiers, observerMethod, null); + this.context = context; + } + + @Override + protected ObserverTransformation self() { + return this; + } + + @Override + public ObserverTransformation priority(int priority) { + this.priority = priority; + return this; + } + + @Override + public ObserverTransformation reception(Reception reception) { + this.reception = reception; + return this; + } + + @Override + public ObserverTransformation transactionPhase(TransactionPhase transactionPhase) { + this.transactionPhase = transactionPhase; + return this; + } + + @Override + public ObserverTransformation async(boolean value) { + this.async = value; + return this; + } + + @Override + public void done() { + context.setAnnotations(modifiedAnnotations); + if (reception != null) { + context.reception = reception; + } + if (priority != null) { + context.priority = priority; + } + if (transactionPhase != null) { + context.transactionPhase = transactionPhase; + } + if (async != null) { + context.async = async; + } + } + + } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java new file mode 100644 index 0000000000000..2450e3716298e --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java @@ -0,0 +1,133 @@ +package io.quarkus.arc.processor; + +import java.util.Collection; +import java.util.Set; +import javax.enterprise.event.Reception; +import javax.enterprise.event.TransactionPhase; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +/** + * Allows a build-time extension to: + *

    + *
  • override the set of qualifiers and priority of any observer method,
  • + *
  • instruct the container to veto the observer method.
  • + *
+ */ +public interface ObserverTransformer extends BuildExtension { + + /** + * + * @param observedType + * @param qualifiers + * @return {@code true} if this transformer is meant to be applied to the supplied attributes of an observer method, + * {@code false} otherwise + */ + boolean appliesTo(Type observedType, Set qualifiers); + + /** + * @param context + */ + void transform(TransformationContext context); + + /** + * Transformation context. + */ + interface TransformationContext extends BuildExtension.BuildContext { + + /** + * + * @return the corresponding observer method + */ + MethodInfo getMethod(); + + /** + * + * @return the observed type + */ + Type getObservedType(); + + /** + * + * @return the set of qualifiers + */ + Set getQualifiers(); + + /** + * + * @return the reception + */ + Reception getReception(); + + /** + * + * @return the transaction phase + */ + TransactionPhase getTransactionPhase(); + + /** + * + * @return true if the observer is asynchronous + */ + boolean isAsync(); + + /** + * Retrieves all annotations declared on the observer method. This method is preferred to manual inspection + * of {@link #getMethod()} which may, in some corner cases, hold outdated information. + *

+ * The resulting set of annotations contains contains annotations that belong to the method itself + * as well as to its parameters. + * + * @return collection of all annotations + */ + Collection getAllAnnotations(); + + /** + * Instruct the container to ignore the observer method. + * + * @return self + */ + void veto(); + + /** + * The transformation is not applied until the {@link AnnotationsTransformation#done()} method is invoked. + * + * @return a new transformation + */ + ObserverTransformation transform(); + + } + + interface ObserverTransformation extends AnnotationsTransformation { + + /** + * + * @param priority + * @return self + */ + ObserverTransformation priority(int priority); + + /** + * + * @param reception + * @return self + */ + ObserverTransformation reception(Reception reception); + + /** + * + * @param reception + * @return self + */ + ObserverTransformation transactionPhase(TransactionPhase transactionPhase); + + /** + * + * @param value + * @return + */ + ObserverTransformation async(boolean value); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Transformation.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Transformation.java index f8c86f8d37e2a..7c810c76ea594 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Transformation.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Transformation.java @@ -1,116 +1,20 @@ package io.quarkus.arc.processor; -import io.quarkus.arc.processor.AnnotationStore.TransformationContextImpl; -import io.quarkus.arc.processor.AnnotationsTransformer.TransformationContext; -import java.lang.annotation.Annotation; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; +import java.util.function.Consumer; import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.DotName; -import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.AnnotationTarget; -/** - * Convenient helper class. - */ -public final class Transformation { +public final class Transformation extends AbstractAnnotationsTransformation> { - private final TransformationContextImpl transformationContext; - - private final List modified; - - Transformation(TransformationContextImpl transformationContext) { - this.transformationContext = transformationContext; - this.modified = new ArrayList<>(transformationContext.getAnnotations()); - } - - /** - * - * @param annotation - * @return self - */ - public Transformation add(AnnotationInstance annotation) { - modified.add(annotation); - return this; - } - - /** - * - * @param annotations - * @return self - */ - public Transformation addAll(Collection annotations) { - modified.addAll(annotations); - return this; - } - - /** - * - * @param annotations - * @return self - */ - public Transformation addAll(AnnotationInstance... annotations) { - Collections.addAll(modified, annotations); - return this; + public Transformation(Collection annotations, AnnotationTarget target, + Consumer> transformationConsumer) { + super(annotations, target, transformationConsumer); } - /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. If you need to add an annotation instance - * to a method parameter use - * methods consuming {@link AnnotationInstance} directly and supply the correct {@link MethodParameterInfo}. - * - * @param annotationType - * @param values - * @return self - */ - public Transformation add(Class annotationType, AnnotationValue... values) { - add(DotNames.create(annotationType.getName()), values); + @Override + protected Transformation self() { return this; } - /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. If you need to add an annotation instance - * to a method parameter use - * methods consuming {@link AnnotationInstance} directly and supply the correct {@link MethodParameterInfo}. - * - * @param name - * @param values - * @return self - */ - public Transformation add(DotName name, AnnotationValue... values) { - add(AnnotationInstance.create(name, transformationContext.getTarget(), values)); - return this; - } - - /** - * - * @param predicate - * @return self - */ - public Transformation remove(Predicate predicate) { - modified.removeIf(predicate); - return this; - } - - /** - * - * @return self - */ - public Transformation removeAll() { - modified.clear(); - return this; - } - - /** - * Applies the transformation. - * - * @see TransformationContext#getAnnotations() - */ - public void done() { - transformationContext.setAnnotations(modified); - } - } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index ddcbd139cdecf..f73df0be66390 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -37,6 +37,7 @@ public void testGetTypeClosure() throws IOException { Set bazTypes = Types.getTypeClosure(index.getClassByName(bazName), null, Collections.emptyMap(), new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), null, false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false), resolvedTypeVariables::put); @@ -54,6 +55,7 @@ public void testGetTypeClosure() throws IOException { // Foo, Object Set fooTypes = Types.getClassBeanTypeClosure(fooClass, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), null, false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(2, fooTypes.size()); @@ -70,6 +72,7 @@ public void testGetTypeClosure() throws IOException { // Object is the sole type Set producerMethodTypes = Types.getProducerMethodTypeClosure(producerMethod, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), null, false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(1, producerMethodTypes.size()); @@ -78,6 +81,7 @@ public void testGetTypeClosure() throws IOException { FieldInfo producerField = producerClass.field(producersName); Set producerFieldTypes = Types.getProducerFieldTypeClosure(producerField, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), null, false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(1, producerFieldTypes.size()); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java index 4ab4663cddc45..f7b2e7a4175b0 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java @@ -12,6 +12,7 @@ import io.quarkus.arc.processor.ContextRegistrar; import io.quarkus.arc.processor.InjectionPointsTransformer; import io.quarkus.arc.processor.InterceptorBindingRegistrar; +import io.quarkus.arc.processor.ObserverTransformer; import io.quarkus.arc.processor.ResourceOutput; import java.io.File; import java.io.FileOutputStream; @@ -68,6 +69,7 @@ public static class Builder { private final List interceptorBindingRegistrars; private final List annotationsTransformers; private final List injectionsPointsTransformers; + private final List observerTransformers; private final List beanDeploymentValidators; private boolean shouldFail = false; private boolean removeUnusedBeans = false; @@ -82,6 +84,7 @@ public Builder() { interceptorBindingRegistrars = new ArrayList<>(); annotationsTransformers = new ArrayList<>(); injectionsPointsTransformers = new ArrayList<>(); + observerTransformers = new ArrayList<>(); beanDeploymentValidators = new ArrayList<>(); exclusions = new ArrayList<>(); } @@ -122,6 +125,11 @@ public Builder injectionPointsTransformers(InjectionPointsTransformer... transfo return this; } + public Builder observerTransformers(ObserverTransformer... transformers) { + Collections.addAll(this.observerTransformers, transformers); + return this; + } + public Builder interceptorBindingRegistrars(InterceptorBindingRegistrar... registrars) { Collections.addAll(this.interceptorBindingRegistrars, registrars); return this; @@ -150,6 +158,7 @@ public Builder shouldFail() { public ArcTestContainer build() { return new ArcTestContainer(resourceReferenceProviders, beanClasses, resourceAnnotations, beanRegistrars, contextRegistrars, interceptorBindingRegistrars, annotationsTransformers, injectionsPointsTransformers, + observerTransformers, beanDeploymentValidators, shouldFail, removeUnusedBeans, exclusions); } @@ -171,6 +180,8 @@ public ArcTestContainer build() { private final List injectionPointsTransformers; + private final List observerTransformers; + private final List beanDeploymentValidators; private final boolean shouldFail; @@ -181,7 +192,7 @@ public ArcTestContainer build() { public ArcTestContainer(Class... beanClasses) { this(Collections.emptyList(), Arrays.asList(beanClasses), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, false, Collections.emptyList()); } @@ -191,6 +202,7 @@ public ArcTestContainer(List> resourceReferenceProviders, List List beanRegistrars, List contextRegistrars, List bindingRegistrars, List annotationsTransformers, List ipTransformers, + List observerTransformers, List beanDeploymentValidators, boolean shouldFail, boolean removeUnusedBeans, List> exclusions) { this.resourceReferenceProviders = resourceReferenceProviders; @@ -201,6 +213,7 @@ public ArcTestContainer(List> resourceReferenceProviders, List this.bindingRegistrars = bindingRegistrars; this.annotationsTransformers = annotationsTransformers; this.injectionPointsTransformers = ipTransformers; + this.observerTransformers = observerTransformers; this.beanDeploymentValidators = beanDeploymentValidators; this.buildFailure = new AtomicReference(null); this.shouldFail = shouldFail; @@ -316,6 +329,9 @@ private ClassLoader init(ExtensionContext context) { for (InjectionPointsTransformer injectionPointsTransformer : injectionPointsTransformers) { beanProcessorBuilder.addInjectionPointTransformer(injectionPointsTransformer); } + for (ObserverTransformer observerTransformer : observerTransformers) { + beanProcessorBuilder.addObserverTransformer(observerTransformer); + } for (BeanDeploymentValidator validator : beanDeploymentValidators) { beanProcessorBuilder.addBeanDeploymentValidator(validator); } From f17729dfa0ff914bf39bbf3d7432554f28d78171 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 11 Dec 2019 16:39:40 +0100 Subject: [PATCH 337/602] Docs - observer transformation --- docs/src/main/asciidoc/cdi-reference.adoc | 40 ++++++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index eda96eefe6117..15520dca5d028 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -554,17 +554,18 @@ The following sample shows how to apply transformation to injection points with [source,java] ---- @BuildStep -InjectionPointTransformerBuildItem transform() { +InjectionPointTransformerBuildItem transformer() { return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() { public boolean appliesTo(Type requiredType) { - return requiredType.equals(Type.create(DotName.createSimple(Foo.class.getName()), Type.Kind.CLASS)); + return requiredType.name().equals(DotName.createSimple(Foo.class.getName())); } - public void transform(TransformationContext transformationContext) { - if (transformationContext.getQualifiers().stream() + public void transform(TransformationContext context) { + if (context.getQualifiers().stream() .anyMatch(a -> a.name().equals(DotName.createSimple(MyQualifier.class.getName())))) { - transformationContext.transform().removeAll() + context.transform() + .removeAll() .add(DotName.createSimple(MyOtherQualifier.class.getName())) .done(); } @@ -573,6 +574,35 @@ InjectionPointTransformerBuildItem transform() { } ---- +=== Observer Transformation + +Any https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#observer_methods[observer method] definition can be vetoed or transformed using an `ObserverTransformerBuildItem`. +The attributes that can be transformed include: + +- https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/spi/ObserverMethod.html#getObservedQualifiers--[qualifiers] +- https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/spi/ObserverMethod.html#getReception--[reception] +- https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/spi/ObserverMethod.html#getPriority--[priority] +- https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/spi/ObserverMethod.html#getTransactionPhase--[transaction phase] +- https://docs.jboss.org/cdi/api/2.0/javax/enterprise/inject/spi/ObserverMethod.html#isAsync--[asynchronous] + +[source,java] +---- +@BuildStep +ObserverTransformerBuildItem transformer() { + return new ObserverTransformerBuildItem(new ObserverTransformer() { + + public boolean appliesTo(Type observedType, Set qualifiers) { + return observedType.name.equals(DotName.createSimple(MyEvent.class.getName())); + } + + public void transform(TransformationContext context) { + // Veto all observers of MyEvent + context.veto(); + } + }); +} +---- + === Bean Deployment Validation Once the bean deployment is ready an extension can perform additional validations and inspect the found beans, observers and injection points. From 5d114cb847df10f2cb44beaedd7e0992b2545f4f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 11 Dec 2019 17:36:50 +0100 Subject: [PATCH 338/602] SmallRye FT - initialize Hystrix during RUNTIME_INIT - veto HystrixInitializer.init() observer - add HystrixInitializerStartup - resolves #6024 --- docs/src/main/asciidoc/cdi-reference.adoc | 2 + .../SmallRyeFaultToleranceProcessor.java | 34 +++++++++++- .../runtime/HystrixInitializerStarter.java | 24 +++++++++ .../io/quarkus/arc/processor/Annotations.java | 54 +++++++++++++++++-- .../quarkus/arc/processor/ObserverInfo.java | 2 +- .../arc/processor/ObserverTransformer.java | 4 +- 6 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/HystrixInitializerStarter.java diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 15520dca5d028..7016ad06a7c0d 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -712,6 +712,8 @@ Here is a summary of which extensions can access which metadata: ** Has access to `ANNOTATION_STORE` * `InjectionPointsTransformer` ** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES` +* `ObserverTransformer` +** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES` * `BeanRegistrar` ** Has access to `ANNOTATION_STORE`, `QUALIFIERS`, `INTERCEPTOR_BINDINGS`, `STEREOTYPES`, `BEANS` * `BeanDeploymentValidator` diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index fdf2493bc33af..89fe790d69d00 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -20,12 +20,14 @@ import org.eclipse.microprofile.faulttolerance.FallbackHandler; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; import com.netflix.hystrix.HystrixCircuitBreaker; @@ -34,10 +36,14 @@ import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; +import io.quarkus.arc.deployment.ObserverTransformerBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; +import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.ObserverTransformer; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -51,6 +57,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.smallrye.faulttolerance.runtime.HystrixInitializerStarter; import io.quarkus.smallrye.faulttolerance.runtime.NoopMetricRegistry; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFallbackHandlerProvider; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFaultToleranceOperationProvider; @@ -64,6 +71,8 @@ public class SmallRyeFaultToleranceProcessor { + static final DotName HYSTRIX_INITIALIZER_NAME = DotName.createSimple(HystrixInitializer.class.getName()); + @Inject BuildProducer reflectiveClass; @@ -148,7 +157,8 @@ public void transform(TransformationContext context) { DefaultHystrixConcurrencyStrategy.class, QuarkusFaultToleranceOperationProvider.class, QuarkusFallbackHandlerProvider.class, DefaultCommandListenersProvider.class, - MetricsCollectorFactory.class); + MetricsCollectorFactory.class, + HystrixInitializerStarter.class); additionalBean.produce(builder.build()); if (!capabilities.isCapabilityPresent(Capabilities.METRICS)) { @@ -230,4 +240,26 @@ public void clearStatic(SmallryeFaultToleranceRecorder recorder, ShutdownContext // this is needed so that shutdown context of FT is executed before Arc container shuts down recorder.resetCommandContextOnUndeploy(context); } + + @BuildStep + ObserverTransformerBuildItem vetoHystrixInitializerObserver() { + return new ObserverTransformerBuildItem(new ObserverTransformer() { + + @Override + public boolean appliesTo(Type observedType, Set qualifiers) { + AnnotationInstance initialized = Annotations.find(Annotations.getParameterAnnotations(qualifiers), + DotNames.INITIALIZED); + return initialized != null ? initialized.value().asClass().name().equals(BuiltinScope.APPLICATION.getName()) + : false; + } + + @Override + public void transform(TransformationContext context) { + if (context.getMethod().declaringClass().name().equals(HYSTRIX_INITIALIZER_NAME)) { + context.veto(); + } + } + + }); + } } diff --git a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/HystrixInitializerStarter.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/HystrixInitializerStarter.java new file mode 100644 index 0000000000000..d8e29a2186c0e --- /dev/null +++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/HystrixInitializerStarter.java @@ -0,0 +1,24 @@ +package io.quarkus.smallrye.faulttolerance.runtime; + +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; + +import io.quarkus.runtime.StartupEvent; +import io.smallrye.faulttolerance.HystrixInitializer; + +@Dependent +public class HystrixInitializerStarter { + + /** + * This is a replacement for io.smallrye.faulttolerance.HystrixInitializer.init(Object) observer method which + * is vetoed because we don't want initialize Hystrix during static init. + * + * @param event + * @param initializer + */ + void startup(@Observes StartupEvent event, HystrixInitializer initializer) { + // HystrixInitializer is a normal scoped bean so we have to invoke a method upon the injected proxy to force the instantiation + initializer.toString(); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java index 0eaf0783c44e5..29957cd269c5b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Annotations.java @@ -1,6 +1,7 @@ package io.quarkus.arc.processor; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.jboss.jandex.AnnotationInstance; @@ -13,7 +14,13 @@ public final class Annotations { private Annotations() { } - static AnnotationInstance find(Collection annotations, DotName name) { + /** + * + * @param annotations + * @param name + * @return the first matching annotation instance with the given name or null + */ + public static AnnotationInstance find(Collection annotations, DotName name) { if (annotations.isEmpty()) { return null; } @@ -25,7 +32,13 @@ static AnnotationInstance find(Collection annotations, DotNa return null; } - static boolean contains(Collection annotations, DotName name) { + /** + * + * @param annotations + * @param name + * @return {@code true} if the given collection contains an annotation instance with the given name, {@code false} otherwise + */ + public static boolean contains(Collection annotations, DotName name) { if (annotations.isEmpty()) { return false; } @@ -37,7 +50,14 @@ static boolean contains(Collection annotations, DotName name return false; } - static boolean containsAny(Collection annotations, Iterable names) { + /** + * + * @param annotations + * @param names + * @return {@code true} if the given collection contains an annotation instance with any of the given names, {@code false} + * otherwise + */ + public static boolean containsAny(Collection annotations, Iterable names) { if (annotations.isEmpty()) { return false; } @@ -51,7 +71,33 @@ static boolean containsAny(Collection annotations, Iterable< return false; } - static Set getParameterAnnotations(BeanDeployment beanDeployment, MethodInfo method, int position) { + /** + * + * @param annotations + * @return the parameter annotations + */ + public static Set getParameterAnnotations(Collection annotations) { + if (annotations.isEmpty()) { + return Collections.emptySet(); + } + Set ret = new HashSet<>(); + for (AnnotationInstance annotation : annotations) { + if (Kind.METHOD_PARAMETER == annotation.target().kind()) { + ret.add(annotation); + } + } + return ret; + } + + /** + * + * @param beanDeployment + * @param method + * @param position + * @return the parameter annotations for the given position + */ + public static Set getParameterAnnotations(BeanDeployment beanDeployment, MethodInfo method, + int position) { Set annotations = new HashSet<>(); for (AnnotationInstance annotation : beanDeployment.getAnnotations(method)) { if (Kind.METHOD_PARAMETER == annotation.target().kind() diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java index 580033c60d7bc..78f02e66f6342 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java @@ -344,7 +344,7 @@ public ObserverTransformation transactionPhase(TransactionPhase transactionPhase this.transactionPhase = transactionPhase; return this; } - + @Override public ObserverTransformation async(boolean value) { this.async = value; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java index 2450e3716298e..b18d49748ba7c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverTransformer.java @@ -71,7 +71,7 @@ interface TransformationContext extends BuildExtension.BuildContext { * @return true if the observer is asynchronous */ boolean isAsync(); - + /** * Retrieves all annotations declared on the observer method. This method is preferred to manual inspection * of {@link #getMethod()} which may, in some corner cases, hold outdated information. @@ -114,7 +114,7 @@ interface ObserverTransformation extends AnnotationsTransformation Date: Thu, 12 Dec 2019 12:46:13 +0100 Subject: [PATCH 339/602] Make config-yaml a full extension With the existing set up, it wasn't possible to install it. --- .../builditem/FeatureBuildItem.java | 1 + extensions/config-yaml/deployment/pom.xml | 44 +++++++++++++++++++ .../yaml/deployment/ConfigYamlProcessor.java | 12 +++++ extensions/config-yaml/pom.xml | 1 + extensions/config-yaml/runtime/pom.xml | 21 +++++++++ .../yaml/runtime/ApplicationYamlProvider.java | 1 + .../resources/META-INF/quarkus-extension.yaml | 11 +++++ 7 files changed, 91 insertions(+) create mode 100644 extensions/config-yaml/deployment/pom.xml create mode 100644 extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java create mode 100644 extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index afc54a00a8a52..602826188c27e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -14,6 +14,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String ARTEMIS_CORE = "artemis-core"; public static final String ARTEMIS_JMS = "artemis-jms"; public static final String CDI = "cdi"; + public static final String CONFIG_YAML = "config-yaml"; public static final String DYNAMODB = "dynamodb"; public static final String ELASTICSEARCH_REST_CLIENT = "elasticsearch-rest-client"; public static final String FLYWAY = "flyway"; diff --git a/extensions/config-yaml/deployment/pom.xml b/extensions/config-yaml/deployment/pom.xml new file mode 100644 index 0000000000000..4f1e1ebd315eb --- /dev/null +++ b/extensions/config-yaml/deployment/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + io.quarkus + quarkus-config-yaml-parent + 999-SNAPSHOT + ../ + + + quarkus-config-yaml-deployment + Quarkus - Configuration - YAML - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-config-yaml + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java b/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java new file mode 100644 index 0000000000000..60b5803880c86 --- /dev/null +++ b/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java @@ -0,0 +1,12 @@ +package io.quarkus.config.yaml.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +public final class ConfigYamlProcessor { + + @BuildStep + public FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.CONFIG_YAML); + } +} diff --git a/extensions/config-yaml/pom.xml b/extensions/config-yaml/pom.xml index ee4ec1f790f7a..f2e2c099a56b7 100644 --- a/extensions/config-yaml/pom.xml +++ b/extensions/config-yaml/pom.xml @@ -15,6 +15,7 @@ pom runtime + deployment diff --git a/extensions/config-yaml/runtime/pom.xml b/extensions/config-yaml/runtime/pom.xml index 470c269bdae81..0be9a852ee550 100644 --- a/extensions/config-yaml/runtime/pom.xml +++ b/extensions/config-yaml/runtime/pom.xml @@ -38,4 +38,25 @@ test + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + \ No newline at end of file diff --git a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java index a1ef82911e39d..cfc7c03f06373 100644 --- a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java +++ b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java @@ -25,6 +25,7 @@ public final class ApplicationYamlProvider implements ConfigSourceProvider { static final String APPLICATION_YAML = "application.yaml"; + @Override public Iterable getConfigSources(final ClassLoader forClassLoader) { List sources = Collections.emptyList(); // mirror the in-JAR application.properties diff --git a/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..be50cd6e20284 --- /dev/null +++ b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,11 @@ +--- +name: "YAML Configuration" +metadata: + keywords: + - "config" + - "configuration" + - "yaml" + categories: + - "core" + status: "stable" + guide: "https://quarkus.io/guides/config#yaml" From f260a83fe55aad02573eb816a38499ace111efaf Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Wed, 11 Dec 2019 13:46:48 +0100 Subject: [PATCH 340/602] Update to Neo4j 4.0.0 driver, add more information to health check. --- bom/runtime/pom.xml | 2 +- docs/src/main/asciidoc/neo4j.adoc | 8 +-- .../neo4j/runtime/heath/Neo4jHealthCheck.java | 71 ++++++++++++++++++- integration-tests/neo4j/pom.xml | 4 +- .../io/quarkus/it/neo4j/Neo4jResource.java | 30 ++------ .../it/neo4j/Neo4jFunctionalityTest.java | 10 +-- 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 581321c15a6de..e2e6f84a0d3da 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -148,7 +148,7 @@ 5.5.1.201910021850-r 6.1.1 1.0.5 - 4.0.0-beta03 + 4.0.0 3.10.2 1.11.0 2.10.1 diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index acb271115287f..4c1148799cf60 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -4,7 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Neo4j -:neo4j_version: 3.5.6 +:neo4j_version: 3.5.13 include::./attributes.adoc[] :extension-status: preview @@ -429,7 +429,7 @@ Please add the following dependency management and dependency to your `pom.xml` io.projectreactor reactor-bom - Californium-SR4 + Dysprosium-RELEASE pom import @@ -460,7 +460,7 @@ import javax.ws.rs.core.MediaType; import org.neo4j.driver.Driver; import org.neo4j.driver.reactive.RxSession; -import org.neo4j.driver.reactive.RxStatementResult; +import org.neo4j.driver.reactive.RxResult; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -477,7 +477,7 @@ public class ReactiveFruitResource { @Produces(MediaType.SERVER_SENT_EVENTS) public Publisher get() { return Flux.using(driver::rxSession, session -> session.readTransaction(tx -> { - RxStatementResult result = tx.run("MATCH (f:Fruit) RETURN f.name as name ORDER BY f.name"); + RxResult result = tx.run("MATCH (f:Fruit) RETURN f.name as name ORDER BY f.name"); return Flux.from(result.records()).map(record -> record.get("name").asString()); }), RxSession::close); } diff --git a/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java index 979eceeea446d..f236bbde913b3 100644 --- a/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java +++ b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java @@ -7,22 +7,89 @@ import org.eclipse.microprofile.health.HealthCheckResponse; import org.eclipse.microprofile.health.HealthCheckResponseBuilder; import org.eclipse.microprofile.health.Readiness; +import org.jboss.logging.Logger; +import org.neo4j.driver.AccessMode; import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.exceptions.SessionExpiredException; +import org.neo4j.driver.summary.ResultSummary; +import org.neo4j.driver.summary.ServerInfo; @Readiness @ApplicationScoped public class Neo4jHealthCheck implements HealthCheck { + + private static final Logger log = Logger.getLogger(Neo4jHealthCheck.class); + + /** + * The Cypher statement used to verify Neo4j is up. + */ + private static final String CYPHER = "RETURN 1 AS result"; + /** + * Message indicating that the health check failed. + */ + private static final String MESSAGE_HEALTH_CHECK_FAILED = "Neo4j health check failed"; + /** + * Message logged before retrying a health check. + */ + private static final String MESSAGE_SESSION_EXPIRED = "Neo4j session has expired, retrying one single time to retrieve server health."; + /** + * The default session config to use while connecting. + */ + private static final SessionConfig DEFAULT_SESSION_CONFIG = SessionConfig.builder() + .withDefaultAccessMode(AccessMode.WRITE) + .build(); + @Inject Driver driver; @Override public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = HealthCheckResponse.named("Neo4j connection health check").up(); try { - driver.verifyConnectivity(); - return builder.build(); + ResultSummary resultSummary; + // Retry one time when the session has been expired + try { + resultSummary = runHealthCheckQuery(); + } catch (SessionExpiredException sessionExpiredException) { + log.warn(MESSAGE_SESSION_EXPIRED); + resultSummary = runHealthCheckQuery(); + } + return buildStatusUp(resultSummary, builder); } catch (Exception e) { return builder.down().withData("reason", e.getMessage()).build(); } } + + /** + * Applies the given {@link ResultSummary} to the {@link HealthCheckResponseBuilder builder} and calls {@code build} + * afterwards. + * + * @param resultSummary the result summary returned by the server + * @param builder the health builder to be modified + * @return the final {@link HealthCheckResponse health check response} + */ + private static HealthCheckResponse buildStatusUp(ResultSummary resultSummary, HealthCheckResponseBuilder builder) { + ServerInfo serverInfo = resultSummary.server(); + + builder.withData("server", serverInfo.version() + "@" + serverInfo.address()); + + String databaseName = resultSummary.database().name(); + if (!(databaseName == null || databaseName.trim().isEmpty())) { + builder.withData("database", databaseName.trim()); + } + + return builder.build(); + } + + private ResultSummary runHealthCheckQuery() { + // We use WRITE here to make sure UP is returned for a server that supports + // all possible workloads + try (Session session = this.driver.session(DEFAULT_SESSION_CONFIG)) { + ResultSummary resultSummary = session.run(CYPHER).consume(); + return resultSummary; + } + } } diff --git a/integration-tests/neo4j/pom.xml b/integration-tests/neo4j/pom.xml index 8f02212fe5caf..049bad9cba8f7 100644 --- a/integration-tests/neo4j/pom.xml +++ b/integration-tests/neo4j/pom.xml @@ -25,7 +25,7 @@ io.projectreactor reactor-bom - Californium-SR9 + Dysprosium-RELEASE pom import @@ -203,7 +203,7 @@ - neo4j:3.5.6 + neo4j/neo4j-experimental:4.0.0-rc01 60513:7687 diff --git a/integration-tests/neo4j/src/main/java/io/quarkus/it/neo4j/Neo4jResource.java b/integration-tests/neo4j/src/main/java/io/quarkus/it/neo4j/Neo4jResource.java index fab3d6501b9f0..1099f8997cb16 100644 --- a/integration-tests/neo4j/src/main/java/io/quarkus/it/neo4j/Neo4jResource.java +++ b/integration-tests/neo4j/src/main/java/io/quarkus/it/neo4j/Neo4jResource.java @@ -14,14 +14,13 @@ import javax.ws.rs.Produces; import org.neo4j.driver.Driver; +import org.neo4j.driver.Result; import org.neo4j.driver.Session; -import org.neo4j.driver.StatementResult; import org.neo4j.driver.Transaction; import org.neo4j.driver.Values; import org.neo4j.driver.async.AsyncSession; -import org.neo4j.driver.async.StatementResultCursor; +import org.neo4j.driver.reactive.RxResult; import org.neo4j.driver.reactive.RxSession; -import org.neo4j.driver.reactive.RxStatementResult; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -75,9 +74,9 @@ public CompletionStage> doStuffWithNeo4jAsynchronous() { public Publisher doStuffWithNeo4jReactive() { return Flux.using(driver::rxSession, session -> session.readTransaction(tx -> { - RxStatementResult result = tx.run("UNWIND range(1, 3) AS x RETURN x", Collections.emptyMap()); + RxResult result = tx.run("UNWIND range(1, 3) AS x RETURN x", Collections.emptyMap()); return Flux.from(result.records()).map(record -> record.get("x").asInt()); - }), RxSession::close); + }), RxSession::close).doOnNext(System.out::println); } private static void createNodes(Driver driver) { @@ -92,7 +91,7 @@ private static void createNodes(Driver driver) { private static void readNodes(Driver driver) { try (Session session = driver.session(); Transaction transaction = session.beginTransaction()) { - StatementResult result = transaction + Result result = transaction .run("MATCH (f:Framework {name: $name}) - [:CAN_USE] -> (n) RETURN f, n", Values.parameters("name", "Quarkus")); result.forEachRemaining( @@ -102,25 +101,6 @@ record -> System.out.println(String.format("%s works with %s", record.get("n").g } } - private static void readNodesAsync(Driver driver) { - AsyncSession session = driver.asyncSession(); - session - .runAsync("UNWIND range(1, 3) AS x RETURN x") - .thenCompose(StatementResultCursor::listAsync) - .whenComplete((records, error) -> { - if (records != null) { - System.out.println(records); - } else { - error.printStackTrace(); - } - }) - .thenCompose(records -> { - System.out.println("clsoing!!!"); - return session.closeAsync() - .thenApply(ignore -> records); - }); - } - private void reportException(String errorMessage, final Exception e, final PrintWriter writer) { if (errorMessage != null) { writer.write(errorMessage); diff --git a/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java b/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java index ebaaf7ef05917..19058362bf4e4 100644 --- a/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java +++ b/integration-tests/neo4j/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java @@ -5,7 +5,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; @@ -16,7 +15,7 @@ * Can quickly start a matching database with: * *

- *     docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/music' neo4j:3.5.3
+ *     docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/music' neo4j/neo4j-experimental:4.0.0-rc01
  * 
*/ @QuarkusTest @@ -36,7 +35,6 @@ public void testAsynchronousNeo4jFunctionality() { } @Test - @Disabled // Works only with Neo4j 4.0 public void testReactiveNeo4jFunctionality() { RestAssured.given() .when().get("/neo4j/reactive") @@ -46,10 +44,12 @@ public void testReactiveNeo4jFunctionality() { } @Test - public void health() throws Exception { + public void health() { RestAssured.when().get("/health/ready").then() + .log().all() .body("status", is("UP"), "checks.status", containsInAnyOrder("UP"), - "checks.name", containsInAnyOrder("Neo4j connection health check")); + "checks.name", containsInAnyOrder("Neo4j connection health check"), + "checks.data.server", containsInAnyOrder(matchesRegex("Neo4j/.*@.*:\\d*"))); } } From fa54ca693969bb33b1fca68ac34c5440e0b94358 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 12 Dec 2019 15:19:36 +0000 Subject: [PATCH 341/602] Allow JAXB to be a runtime dependency of Hibernate ORM --- extensions/hibernate-orm/deployment/pom.xml | 17 ----------------- extensions/hibernate-orm/runtime/pom.xml | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/extensions/hibernate-orm/deployment/pom.xml b/extensions/hibernate-orm/deployment/pom.xml index 5b6294a216614..5aa35eb56f053 100644 --- a/extensions/hibernate-orm/deployment/pom.xml +++ b/extensions/hibernate-orm/deployment/pom.xml @@ -33,7 +33,6 @@ io.quarkus quarkus-arc-deployment - io.quarkus quarkus-junit5-internal @@ -71,22 +70,6 @@ hibernate-envers test - - org.glassfish.jaxb - jaxb-runtime - test - - - jakarta.xml.bind - jakarta.xml.bind-api - - - - - org.jboss.spec.javax.xml.bind - jboss-jaxb-api_2.3_spec - test - diff --git a/extensions/hibernate-orm/runtime/pom.xml b/extensions/hibernate-orm/runtime/pom.xml index 68d973162fc06..3719a1bc5425e 100644 --- a/extensions/hibernate-orm/runtime/pom.xml +++ b/extensions/hibernate-orm/runtime/pom.xml @@ -50,7 +50,7 @@ javassist - + javax.xml.bind jaxb-api @@ -67,6 +67,21 @@ + + + org.glassfish.jaxb + jaxb-runtime + + + jakarta.xml.bind + jakarta.xml.bind-api + + + + + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + jakarta.persistence jakarta.persistence-api From 02a5561d1a2d8a2b6d29398a02bbb81070f38023 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 12 Dec 2019 12:33:15 +1100 Subject: [PATCH 342/602] Use the DeploymentClassLoader to transform classes Fixes #6091 Using the RuntimeRunner itself can result in deadlocks as it may try and load a class that is already in the process of being loaded. --- .../io/quarkus/runner/RuntimeClassLoader.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index c323f8ba4de00..cc07cb92f2643 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -32,7 +32,6 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -44,7 +43,6 @@ import org.objectweb.asm.ClassWriter; import io.quarkus.deployment.ClassOutput; -import io.quarkus.deployment.QuarkusClassWriter; public class RuntimeClassLoader extends ClassLoader implements ClassOutput, TransformerTarget { @@ -56,6 +54,7 @@ public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Tran private final Map resources = new ConcurrentHashMap<>(); private volatile Map>> bytecodeTransformers = null; + private volatile ClassLoader transformerSafeClassLoader; private final List applicationClassDirectories; @@ -68,7 +67,7 @@ public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Tran private static final String DEBUG_CLASSES_DIR = System.getProperty("quarkus.debug.generated-classes-dir"); - private final ConcurrentHashMap>> loadingClasses = new ConcurrentHashMap<>(); + private final ConcurrentHashMap loadingClasses = new ConcurrentHashMap<>(); static { registerAsParallelCapable(); @@ -207,11 +206,15 @@ protected Class findClass(String name) throws ClassNotFoundException { Path classLoc = getClassInApplicationClassPaths(name); if (classLoc != null) { - CompletableFuture> res = new CompletableFuture<>(); - Future> loadingClass = loadingClasses.putIfAbsent(name, res); + LoadingClass res = new LoadingClass(new CompletableFuture<>(), Thread.currentThread()); + LoadingClass loadingClass = loadingClasses.putIfAbsent(name, res); if (loadingClass != null) { + if (loadingClass.initiator == Thread.currentThread()) { + throw new LinkageError( + "Load caused recursion in RuntimeClassLoader, this is a Quarkus bug loading class: " + name); + } try { - return loadingClass.get(); + return loadingClass.value.get(); } catch (Exception e) { throw new ClassNotFoundException("Failed to load " + name, e); } @@ -225,13 +228,13 @@ protected Class findClass(String name) throws ClassNotFoundException { bytes = handleTransform(name, bytes); definePackage(name); Class clazz = defineClass(name, bytes, 0, bytes.length, defaultProtectionDomain); - res.complete(clazz); + res.value.complete(clazz); return clazz; } catch (RuntimeException e) { - res.completeExceptionally(e); + res.value.completeExceptionally(e); throw e; } catch (Throwable e) { - res.completeExceptionally(e); + res.value.completeExceptionally(e); throw e; } } @@ -301,6 +304,7 @@ public Writer writeSource(final String className) { @Override public void setTransformers(Map>> functions) { this.bytecodeTransformers = functions; + this.transformerSafeClassLoader = Thread.currentThread().getContextClassLoader(); } public void setApplicationArchives(List archives) { @@ -422,7 +426,12 @@ private byte[] handleTransform(String name, byte[] bytes) { } ClassReader cr = new ClassReader(bytes); - ClassWriter writer = new QuarkusClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + ClassWriter writer = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { + @Override + protected ClassLoader getClassLoader() { + return transformerSafeClassLoader; + } + }; ClassVisitor visitor = writer; for (BiFunction i : transformers) { visitor = i.apply(name, visitor); @@ -537,4 +546,13 @@ private ProtectionDomain createDefaultProtectionDomain(Path applicationClasspath return protectionDomain; } + static final class LoadingClass { + final CompletableFuture> value; + final Thread initiator; + + LoadingClass(CompletableFuture> value, Thread initiator) { + this.value = value; + this.initiator = initiator; + } + } } From 2d978e74281b0ee6f6ac47cfdebba145a3be4dc9 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 12 Dec 2019 19:38:53 +0100 Subject: [PATCH 343/602] Include the 1.1 branch in CI --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 995fffc882737..2425fffbc6149 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,6 +3,7 @@ trigger: branches: include: - master + - '1.1' pr: branches: From a5c59fc1097451265ca9881eb7418a2abeece36f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 9 Dec 2019 15:00:38 +0100 Subject: [PATCH 344/602] Update Kafka to 2.3.1 and Debezium to 0.10.0 --- bom/runtime/pom.xml | 9 +++++---- .../kafka/client/runtime/graal/SubstituteSnappy.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index e2e6f84a0d3da..dfa7dfb31d42e 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -126,11 +126,12 @@ 1.12.4 3.3.2.Final 0.0.10 - 2.2.1 - 2.2.1 - 0.9.5.Final + 2.3.1 + 2.3.1 + 0.10.0.Final 3.4.14 - 2.12.8 + + 2.12.9 1.22 1.1.0 2.2.5 diff --git a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SubstituteSnappy.java b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SubstituteSnappy.java index 32ecec78610f9..ca0d6d2410424 100644 --- a/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SubstituteSnappy.java +++ b/extensions/kafka-client/runtime/src/main/java/io/quarkus/kafka/client/runtime/graal/SubstituteSnappy.java @@ -67,7 +67,7 @@ public static CompressionType forId(int id) { final class RemoveJMXAccess { @Substitute - public static synchronized void registerAppInfo(String prefix, String id, Metrics metrics) { + public static synchronized void registerAppInfo(String prefix, String id, Metrics metrics, long nowMs) { } From 9b37272676c78bd8f72a1f9edd54cb6557a7cb04 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 25 Sep 2019 12:10:26 +0100 Subject: [PATCH 345/602] Support for the parser configuration in the properties --- .../TikaParsersConfigBuildItem.java | 21 ++++ .../tika/deployment/TikaProcessor.java | 109 +++++++++++++++--- .../tika/deployment/TestConfigSource.java | 24 ---- .../tika/deployment/TikaProcessorTest.java | 70 +++++++++-- ...lipse.microprofile.config.spi.ConfigSource | 1 - .../tika/runtime/TikaConfiguration.java | 32 ++++- .../tika/runtime/TikaParserParameter.java | 41 +++++++ .../io/quarkus/tika/runtime/TikaRecorder.java | 37 ++++-- .../it/tika/TikaEmdeddedContentResource.java | 5 +- .../quarkus/it/tika/TikaParserResource.java | 6 +- .../src/main/resources/application.properties | 3 +- .../tika/src/main/resources/tika-config.xml | 9 -- 12 files changed, 279 insertions(+), 79 deletions(-) create mode 100644 extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaParsersConfigBuildItem.java delete mode 100644 extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TestConfigSource.java delete mode 100644 extensions/tika/deployment/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource create mode 100644 extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaParserParameter.java delete mode 100644 integration-tests/tika/src/main/resources/tika-config.xml diff --git a/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaParsersConfigBuildItem.java b/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaParsersConfigBuildItem.java new file mode 100644 index 0000000000000..69952bb6c7463 --- /dev/null +++ b/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaParsersConfigBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.tika.deployment; + +import java.util.List; +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.tika.runtime.TikaParserParameter; + +public final class TikaParsersConfigBuildItem extends SimpleBuildItem { + + private final Map> parsersConfig; + + public TikaParsersConfigBuildItem(Map> parsersConfig) { + this.parsersConfig = parsersConfig; + } + + public Map> getConfiguration() { + return parsersConfig; + } + +} diff --git a/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaProcessor.java b/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaProcessor.java index a6dda4d54a8aa..6ca09a9b5f1b1 100644 --- a/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaProcessor.java +++ b/extensions/tika/deployment/src/main/java/io/quarkus/tika/deployment/TikaProcessor.java @@ -2,18 +2,19 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.tika.detect.Detector; import org.apache.tika.detect.EncodingDetector; import org.apache.tika.parser.Parser; -import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.Capabilities; @@ -28,7 +29,9 @@ import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.tika.TikaParseException; import io.quarkus.tika.runtime.TikaConfiguration; +import io.quarkus.tika.runtime.TikaParserParameter; import io.quarkus.tika.runtime.TikaRecorder; public class TikaProcessor { @@ -51,8 +54,12 @@ public class TikaProcessor { @BuildStep @Record(ExecutionTime.STATIC_INIT) - void initializeTikaParser(BeanContainerBuildItem beanContainer, TikaRecorder recorder) throws Exception { - recorder.initTikaParser(beanContainer.getValue(), config, getSupportedParserNames(config.parsers)); + TikaParsersConfigBuildItem initializeTikaParser(BeanContainerBuildItem beanContainer, TikaRecorder recorder) + throws Exception { + Map> parsersConfig = getSupportedParserConfig(config.tikaConfigPath, config.parsers, + config.parserOptions, config.parser); + recorder.initTikaParser(beanContainer.getValue(), config, parsersConfig); + return new TikaParsersConfigBuildItem(parsersConfig); } @BuildStep @@ -95,9 +102,11 @@ public void registerPdfBoxResources(BuildProducer } @BuildStep - public void registerTikaProviders(BuildProducer serviceProvider) throws Exception { + public void registerTikaProviders(BuildProducer serviceProvider, + TikaParsersConfigBuildItem parserConfigItem) throws Exception { serviceProvider.produce( - new ServiceProviderBuildItem(Parser.class.getName(), getSupportedParserNames(config.parsers))); + new ServiceProviderBuildItem(Parser.class.getName(), + new ArrayList<>(parserConfigItem.getConfiguration().keySet()))); serviceProvider.produce( new ServiceProviderBuildItem(Detector.class.getName(), getProviderNames(Detector.class.getName()))); serviceProvider.produce( @@ -110,31 +119,95 @@ static List getProviderNames(String serviceProviderName) throws Exceptio "META-INF/services/" + serviceProviderName)); } - static List getSupportedParserNames(Optional requiredParsers) throws Exception { + static Map> getSupportedParserConfig(Optional tikaConfigPath, + Optional requiredParsers, + Map> parserParamMaps, + Map parserAbbreviations) throws Exception { Predicate pred = p -> !NOT_NATIVE_READY_PARSERS.contains(p); List providerNames = getProviderNames(Parser.class.getName()); - if (!requiredParsers.isPresent()) { - return providerNames.stream().filter(pred).collect(Collectors.toList()); + if (tikaConfigPath.isPresent() || !requiredParsers.isPresent()) { + return providerNames.stream().filter(pred).collect(Collectors.toMap(Function.identity(), + p -> Collections. emptyList())); } else { List abbreviations = Arrays.stream(requiredParsers.get().split(",")).map(s -> s.trim()) .collect(Collectors.toList()); - Set requiredParsersFullNames = abbreviations.stream() - .map(p -> getParserNameFromConfig(p)).collect(Collectors.toSet()); + Map fullNamesAndAbbreviations = abbreviations.stream() + .collect(Collectors.toMap(p -> getParserNameFromConfig(p, parserAbbreviations), Function.identity())); - return providerNames.stream().filter(pred).filter(p -> requiredParsersFullNames.contains(p)) - .collect(Collectors.toList()); + return providerNames.stream().filter(pred).filter(p -> fullNamesAndAbbreviations.containsKey(p)) + .collect(Collectors.toMap(Function.identity(), + p -> getParserConfig(p, parserParamMaps.get(fullNamesAndAbbreviations.get(p))))); } } - private static String getParserNameFromConfig(String abbreviation) { + static List getParserConfig(String parserName, Map parserParamMap) { + List parserParams = new LinkedList<>(); + if (parserParamMap != null) { + for (Map.Entry entry : parserParamMap.entrySet()) { + String paramName = unhyphenate(entry.getKey()); + String paramType = getParserParamType(parserName, paramName); + parserParams.add(new TikaParserParameter(paramName, entry.getValue(), paramType)); + } + } + return parserParams; + } + + private static String getParserNameFromConfig(String abbreviation, Map parserAbbreviations) { if (PARSER_ABBREVIATIONS.containsKey(abbreviation)) { return PARSER_ABBREVIATIONS.get(abbreviation); } + + if (parserAbbreviations.containsKey(abbreviation)) { + return parserAbbreviations.get(abbreviation); + } + + throw new IllegalStateException("The custom abbreviation `" + abbreviation + + "` can not be resolved to a parser class name, please set a " + + "quarkus.tika.parser-name." + abbreviation + " property"); + } + + // Convert a property name such as "sort-by-position" to "sortByPosition" + private static String unhyphenate(String paramName) { + StringBuilder sb = new StringBuilder(); + String[] words = paramName.split("-"); + for (int i = 0; i < words.length; i++) { + sb.append(i > 0 ? capitalize(words[i]) : words[i]); + } + return sb.toString(); + } + + private static String capitalize(String paramName) { + char[] chars = paramName.toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + return new String(chars); + } + + // TODO: Remove the reflection code below once TikaConfig becomes capable + // of loading the parameters without the type attribute: TIKA-2944 + + private static Class loadParserClass(String parserName) { + try { + return TikaProcessor.class.getClassLoader().loadClass(parserName); + } catch (Throwable t) { + final String errorMessage = "Parser " + parserName + " can not be loaded"; + throw new TikaParseException(errorMessage); + } + } + + private static String getParserParamType(String parserName, String paramName) { try { - return ConfigProvider.getConfig().getValue(abbreviation, String.class); - } catch (NoSuchElementException ex) { - throw new IllegalStateException("The custom abbreviation " + abbreviation - + " can not be resolved to a parser class name"); + Class parserClass = loadParserClass(parserName); + String paramType = parserClass.getMethod("get" + capitalize(paramName), new Class[] {}).getReturnType() + .getSimpleName().toLowerCase(); + if (paramType.equals(boolean.class.getSimpleName())) { + // TikaConfig Param class does not recognize 'boolean', only 'bool' + // This whole reflection code is temporary anyway + paramType = "bool"; + } + return paramType; + } catch (Throwable t) { + final String errorMessage = "Parser " + parserName + " has no " + paramName + " property"; + throw new TikaParseException(errorMessage); } } } diff --git a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TestConfigSource.java b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TestConfigSource.java deleted file mode 100644 index 5c20074540117..0000000000000 --- a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TestConfigSource.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.quarkus.tika.deployment; - -import java.util.Collections; -import java.util.Map; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -public class TestConfigSource implements ConfigSource { - - @Override - public Map getProperties() { - return Collections.singletonMap("opendoc", "org.apache.tika.parser.odf.OpenDocumentParser"); - } - - @Override - public String getValue(String propertyName) { - return getProperties().get(propertyName); - } - - @Override - public String getName() { - return "test-source"; - } -} diff --git a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java index e396c5db78a2b..6f73c2152d27f 100644 --- a/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java +++ b/extensions/tika/deployment/src/test/java/io/quarkus/tika/deployment/TikaProcessorTest.java @@ -4,8 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; @@ -14,6 +17,7 @@ import org.junit.jupiter.api.Test; import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.quarkus.tika.runtime.TikaParserParameter; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; @@ -45,27 +49,55 @@ public static void tearItDown() { } @Test - public void testSupportedParserNames() throws Exception { - Optional parserNames = Optional.of("pdf"); - List names = TikaProcessor.getSupportedParserNames(parserNames); + public void testPDFParserName() throws Exception { + Set names = getParserNames(null, "pdf"); assertEquals(1, names.size()); - assertEquals("org.apache.tika.parser.pdf.PDFParser", names.get(0)); + assertTrue(names.contains("org.apache.tika.parser.pdf.PDFParser")); + } + + @Test + public void testODFParserName() throws Exception { + Set names = getParserNames(null, "odf"); + assertEquals(1, names.size()); + assertTrue(names.contains("org.apache.tika.parser.odf.OpenDocumentParser")); + } + + @Test + public void testSupportedParserNames() throws Exception { + Set names = getParserNames(null, "pdf,odf"); + assertEquals(2, names.size()); + assertTrue(names.contains("org.apache.tika.parser.pdf.PDFParser")); + assertTrue(names.contains("org.apache.tika.parser.odf.OpenDocumentParser")); } @Test public void testResolvableCustomAbbreviation() throws Exception { - Optional parserNames = Optional.of("pdf,opendoc"); - List names = TikaProcessor.getSupportedParserNames(parserNames); + Set names = getParserConfig(null, "pdf,opendoc", Collections.emptyMap(), + Collections.singletonMap("opendoc", + "org.apache.tika.parser.odf.OpenDocumentParser")).keySet(); assertEquals(2, names.size()); assertTrue(names.contains("org.apache.tika.parser.pdf.PDFParser")); assertTrue(names.contains("org.apache.tika.parser.odf.OpenDocumentParser")); } + @Test + public void testPdfParserConfig() throws Exception { + Map> parserConfig = getParserConfig(null, "pdf", + Collections.singletonMap("pdf", + Collections.singletonMap("sort-by-position", "true")), + Collections.emptyMap()); + assertEquals(1, parserConfig.size()); + + String pdfParserFullName = "org.apache.tika.parser.pdf.PDFParser"; + assertEquals(1, parserConfig.get(pdfParserFullName).size()); + assertEquals("sortByPosition", parserConfig.get(pdfParserFullName).get(0).getName()); + assertEquals("true", parserConfig.get(pdfParserFullName).get(0).getValue()); + } + @Test public void testUnresolvableCustomAbbreviation() throws Exception { - Optional parserNames = Optional.of("classparser"); try { - TikaProcessor.getSupportedParserNames(parserNames); + getParserNames(null, "classparser"); fail("'classparser' is not resolvable"); } catch (IllegalStateException ex) { // expected @@ -74,8 +106,26 @@ public void testUnresolvableCustomAbbreviation() throws Exception { @Test public void testAllSupportedParserNames() throws Exception { - Optional parserNames = Optional.ofNullable(null); - List names = TikaProcessor.getSupportedParserNames(parserNames); + assertEquals(69, getParserNames(null, null).size()); + } + + @Test + public void testSupportedParserNamesWithTikaConfigPath() throws Exception { + Set names = getParserNames("tika-config.xml", "pdf"); assertEquals(69, names.size()); } + + private Set getParserNames(String tikaConfigPath, String parsers) throws Exception { + return TikaProcessor.getSupportedParserConfig( + Optional.ofNullable(tikaConfigPath), Optional.ofNullable(parsers), + Collections.emptyMap(), Collections.emptyMap()).keySet(); + } + + private Map> getParserConfig(String tikaConfigPath, String parsers, + Map> parserParamMaps, + Map parserAbbreviations) throws Exception { + return TikaProcessor.getSupportedParserConfig( + Optional.ofNullable(tikaConfigPath), Optional.ofNullable(parsers), + parserParamMaps, parserAbbreviations); + } } diff --git a/extensions/tika/deployment/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/extensions/tika/deployment/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource deleted file mode 100644 index 9243c9d00b395..0000000000000 --- a/extensions/tika/deployment/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.tika.deployment.TestConfigSource diff --git a/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaConfiguration.java b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaConfiguration.java index f561771179427..1069b37faad10 100644 --- a/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaConfiguration.java +++ b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaConfiguration.java @@ -1,5 +1,6 @@ package io.quarkus.tika.runtime; +import java.util.Map; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; @@ -26,16 +27,15 @@ public class TikaConfiguration { * property is recommended to achieve both optimizations. *

* Either the abbreviated or full parser class names can be used. - * At the moment only PDF parser can be listed using a reserved 'pdf' abbreviation. + * Only PDF and OpenDocument format parsers can be listed using the reserved 'pdf' and 'odf' abbreviations. * Custom class name abbreviations have to be used for all other parsers. * For example: * *

      * // Only PDF parser is required:
-     * tika-parsers = pdf
-     * // Only PDF and Java class parsers are required:
-     * tika-parsers = pdf,classparser
-     * classparser = org.apache.tika.parser.asm.ClassParser
+     * quarkus.tika.parsers = pdf
+     * // Only PDF and OpenDocument parsers are required:
+     * quarkus.tika.parsers = pdf,odf
      * 
* * This property will have no effect if the `tikaConfigPath' property has been set. @@ -43,6 +43,28 @@ public class TikaConfiguration { @ConfigItem public Optional parsers; + /** + * Configuration of the individual parsers. + * For example: + * + *
+     * quarkus.tika.parsers = pdf,odf
+     * quarkus.tika.parser-options.pdf.sort-by-position = true
+     */
+    @ConfigItem
+    public Map> parserOptions;
+
+    /**
+     * Full parser class name for a given parser abbreviation.
+     * For example:
+     *
+     * 
+     * quarkus.tika.parsers = classparser
+     * quarkus.tika.parser.classparser = org.apache.tika.parser.asm.ClassParser
+     */
+    @ConfigItem
+    public Map parser;
+
     /**
      * Controls how the content of the embedded documents is parsed.
      * By default it is appended to the master document content.
diff --git a/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaParserParameter.java b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaParserParameter.java
new file mode 100644
index 0000000000000..057532068b8ad
--- /dev/null
+++ b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaParserParameter.java
@@ -0,0 +1,41 @@
+package io.quarkus.tika.runtime;
+
+public class TikaParserParameter {
+    private String name;
+    private String value;
+    private String type;
+
+    public TikaParserParameter() {
+
+    }
+
+    public TikaParserParameter(String name, String value, String type) {
+        this.name = name;
+        this.value = value;
+        this.type = type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaRecorder.java b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaRecorder.java
index b263907c96d39..f0330428e3cf6 100644
--- a/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaRecorder.java
+++ b/extensions/tika/runtime/src/main/java/io/quarkus/tika/runtime/TikaRecorder.java
@@ -4,6 +4,8 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.apache.tika.config.TikaConfig;
 import org.apache.tika.parser.AutoDetectParser;
@@ -18,16 +20,17 @@
 @Recorder
 public class TikaRecorder {
 
-    public void initTikaParser(BeanContainer container, TikaConfiguration config, List supportedParserNames) {
-        TikaParser parser = initializeParser(config, supportedParserNames);
+    public void initTikaParser(BeanContainer container, TikaConfiguration config,
+            Map> parserConfig) {
+        TikaParser parser = initializeParser(config, parserConfig);
         TikaParserProducer producer = container.instance(TikaParserProducer.class);
         producer.initialize(parser);
     }
 
-    private TikaParser initializeParser(TikaConfiguration config, List supportedParserNames) {
+    private TikaParser initializeParser(TikaConfiguration config, Map> parserConfig) {
         TikaConfig tikaConfig = null;
 
-        try (InputStream stream = getTikaConfigStream(config, supportedParserNames)) {
+        try (InputStream stream = getTikaConfigStream(config, parserConfig)) {
             tikaConfig = new TikaConfig(stream);
         } catch (Exception ex) {
             final String errorMessage = "Invalid tika-config.xml";
@@ -44,7 +47,8 @@ private TikaParser initializeParser(TikaConfiguration config, List suppo
         return new TikaParser(nativeParser, config.appendEmbeddedContent);
     }
 
-    private static InputStream getTikaConfigStream(TikaConfiguration config, List supportedParserNames) {
+    private static InputStream getTikaConfigStream(TikaConfiguration config,
+            Map> parserConfig) {
         // Load tika-config.xml resource
         InputStream is = null;
         if (config.tikaConfigPath.isPresent()) {
@@ -56,20 +60,35 @@ private static InputStream getTikaConfigStream(TikaConfiguration config, List supportedParserNames) {
+    private static InputStream generateTikaConfig(Map> parserConfig) {
         StringBuilder sb = new StringBuilder();
         sb.append("");
         sb.append("");
-        for (String parserName : supportedParserNames) {
-            sb.append("");
+        for (Entry> parserEntry : parserConfig.entrySet()) {
+            sb.append("");
+            if (!parserEntry.getValue().isEmpty()) {
+                appendParserParameters(sb, parserEntry.getValue());
+            }
+            sb.append("");
         }
         sb.append("");
         sb.append("");
         return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
     }
+
+    private static void appendParserParameters(StringBuilder sb, List parserParams) {
+        sb.append("");
+        for (TikaParserParameter parserParam : parserParams) {
+            sb.append("");
+            sb.append(parserParam.getValue());
+            sb.append("");
+        }
+        sb.append("");
+    }
 }
diff --git a/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaEmdeddedContentResource.java b/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaEmdeddedContentResource.java
index 54a502ffb89dd..995ecbb18e524 100644
--- a/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaEmdeddedContentResource.java
+++ b/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaEmdeddedContentResource.java
@@ -10,6 +10,8 @@
 
 import org.apache.tika.parser.AutoDetectParser;
 import org.apache.tika.parser.RecursiveParserWrapper;
+import org.apache.tika.parser.microsoft.OfficeParser;
+import org.apache.tika.parser.pdf.PDFParser;
 
 import io.quarkus.tika.TikaContent;
 import io.quarkus.tika.TikaParser;
@@ -18,7 +20,8 @@
 public class TikaEmdeddedContentResource {
 
     // Avoiding the injection, otherwise the recorded tika-config.xml intended for TikaPdfInvoiceTest is used
-    TikaParser parser = new TikaParser(new RecursiveParserWrapper(new AutoDetectParser(), true), false);
+    TikaParser parser = new TikaParser(new RecursiveParserWrapper(
+            new AutoDetectParser(new OfficeParser(), new PDFParser()), true), false);
 
     @POST
     @Path("/outerText")
diff --git a/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaParserResource.java b/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaParserResource.java
index 7ecaac0caccc5..d22c507a2a86c 100644
--- a/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaParserResource.java
+++ b/integration-tests/tika/src/main/java/io/quarkus/it/tika/TikaParserResource.java
@@ -9,13 +9,17 @@
 import javax.ws.rs.core.MediaType;
 
 import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.csv.TextAndCSVParser;
+import org.apache.tika.parser.odf.OpenDocumentParser;
+import org.apache.tika.parser.pdf.PDFParser;
 
 import io.quarkus.tika.TikaParser;
 
 @Path("/parse")
 public class TikaParserResource {
     // Avoiding the injection, otherwise the recorded tika-config.xml intended for TikaPdfInvoiceTest is used
-    TikaParser parser = new TikaParser(new AutoDetectParser(), true);
+    TikaParser parser = new TikaParser(
+            new AutoDetectParser(new PDFParser(), new OpenDocumentParser(), new TextAndCSVParser()), true);
 
     @POST
     @Path("/text")
diff --git a/integration-tests/tika/src/main/resources/application.properties b/integration-tests/tika/src/main/resources/application.properties
index d3eddbefbbee9..e93410248ccb4 100644
--- a/integration-tests/tika/src/main/resources/application.properties
+++ b/integration-tests/tika/src/main/resources/application.properties
@@ -1 +1,2 @@
-quarkus.tika.tika-config-path=tika-config.xml
\ No newline at end of file
+quarkus.tika.parsers=pdf
+quarkus.tika.parser-options.pdf.sort-by-position=true
diff --git a/integration-tests/tika/src/main/resources/tika-config.xml b/integration-tests/tika/src/main/resources/tika-config.xml
deleted file mode 100644
index 7649ac5628376..0000000000000
--- a/integration-tests/tika/src/main/resources/tika-config.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-  
-    
-      
-        true
-      
-    
-  
-
\ No newline at end of file

From 057d225d74b8ec166e30593a6de0771221b8e78d Mon Sep 17 00:00:00 2001
From: "David M. Lloyd" 
Date: Fri, 13 Dec 2019 07:11:46 -0600
Subject: [PATCH 346/602] Add a doc note about YAML config prefix ambiguity

---
 docs/src/main/asciidoc/config.adoc | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc
index 5dfdd3fea0d54..51f54b34dedd0 100644
--- a/docs/src/main/asciidoc/config.adoc
+++ b/docs/src/main/asciidoc/config.adoc
@@ -628,6 +628,28 @@ Just add the `%profile` wrapped in quotation marks before defining the key-value
         password: quarkus
 ----
 
+=== Configuration key conflicts
+
+The Microprofile Configuration specification defines configuration keys as an arbitrary `.`-delimited string.
+However, structured formats like YAML naively only support a subset of the possible configuration namespace.
+For example, consider the two configuration properties `quarkus.http.cors` and `quarkus.http.cors.methods`.
+One property is the prefix of another, so it may not be immediately evident how to specify both keys in your YAML configuration.
+
+This is solved by using a null key (normally represented by `~`) for any YAML property which is a prefix of another one.  Here's an example:
+
+.An example YAML configuration resolving prefix-related key name conflicts
+[source,yaml]
+----
+quarkus:
+    http:
+        cors:
+            ~: true
+            methods: GET,PUT,POST
+----
+
+In general, null YAML keys are not included in assembly of the configuration property name, allowing them to be used to
+any level for disambiguating configuration keys.
+
 == More info on how to configure
 
 Quarkus relies on Eclipse MicroProfile and inherits its features.

From 5d5b816103f94ef5b5741e5fb4d0fe49ddbce74d Mon Sep 17 00:00:00 2001
From: Guillaume Smet 
Date: Fri, 13 Dec 2019 11:40:51 +0100
Subject: [PATCH 347/602] Update Kogito to 0.6.1

---
 bom/runtime/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index dfa7dfb31d42e..f6079d5b70f15 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -159,7 +159,7 @@
         3.1.0
         3.1.7
         0.1.0
-        0.6.0
+        0.6.1
         4.6.4
         0.19.1 
         2.2.0

From 92c20cfc2b0259b9b99f7a198c24f2753c21008b Mon Sep 17 00:00:00 2001
From: "David M. Lloyd" 
Date: Wed, 11 Dec 2019 11:50:38 -0600
Subject: [PATCH 348/602] Upgrade SmallRye Config to 1.5.1

Fixes issue described in smallrye/smallrye-config#211
---
 bom/runtime/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index f6079d5b70f15..1607ba7c937cd 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -31,7 +31,7 @@
         1.3.1
         1.0
         1.3.4
-        1.5.0
+        1.5.1
         2.1.0
         2.3.2
         1.1.20

From 4f2e35c1675d68fa83c8da07d6f0cb2ff640a5dc Mon Sep 17 00:00:00 2001
From: Gytis Trikleris 
Date: Tue, 10 Dec 2019 10:36:49 +0100
Subject: [PATCH 349/602] [#5816] Set correct content type in
 RestControllerAdvice

---
 ...dviceAbstractExceptionMapperGenerator.java | 183 ++++++++++--------
 .../web/deployment/ResponseBuilder.java       |  46 +++++
 .../web/deployment/SpringWebProcessor.java    |   8 +-
 .../runtime/ResponseContentTypeResolver.java  |  52 +++++
 .../web/runtime/ResponseEntityConverter.java  |  21 +-
 .../quarkus/it/spring/web/CustomAdvice.java   |  31 ++-
 .../java/io/quarkus/it/spring/web/Error.java  |  13 +-
 .../web/ExceptionThrowingController.java      |  65 ++++---
 .../it/spring/web/HandledPojoException.java   |   8 +
 .../web/HandledResponseEntityException.java   |  21 ++
 .../it/spring/web/HandledStringException.java |   8 +
 .../web/HandledUnannotatedException.java      |   4 +
 .../it/spring/web/UnannotatedException.java   |   4 -
 ....java => UnhandledAnnotatedException.java} |   4 +-
 ...> UnhandledAnnotatedRuntimeException.java} |   2 +-
 .../it/spring/web/ExceptionHandlingIT.java    |   7 +
 .../it/spring/web/ExceptionHandlingTest.java  | 166 ++++++++++++++++
 .../it/spring/web/SpringControllerTest.java   |  76 --------
 18 files changed, 507 insertions(+), 212 deletions(-)
 create mode 100644 extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseBuilder.java
 create mode 100644 extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java
 create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledPojoException.java
 create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledResponseEntityException.java
 create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledStringException.java
 create mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledUnannotatedException.java
 delete mode 100644 integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnannotatedException.java
 rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/{FirstException.java => UnhandledAnnotatedException.java} (55%)
 rename integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/{SecondException.java => UnhandledAnnotatedRuntimeException.java} (72%)
 create mode 100644 integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingIT.java
 create mode 100644 integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingTest.java

diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
index d24544fdc0ac6..346407d9ca411 100644
--- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
+++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
@@ -2,6 +2,7 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Modifier;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -9,6 +10,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
@@ -27,12 +29,19 @@
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.MethodDescriptor;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.spring.web.runtime.ResponseContentTypeResolver;
 import io.quarkus.spring.web.runtime.ResponseEntityConverter;
 
 class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractExceptionMapperGenerator {
 
     private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");
-    private static final DotName STRING = DotName.createSimple(String.class.getName());
+
+    // Preferred content types order for String or primitive type responses
+    private static final List TEXT_MEDIA_TYPES = Arrays.asList(
+            MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML);
+    // Preferred content types order for object type responses
+    private static final List OBJECT_MEDIA_TYPES = Arrays.asList(
+            MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.TEXT_PLAIN);
 
     private final MethodInfo controllerAdviceMethod;
     private final TypesUtil typesUtil;
@@ -42,6 +51,8 @@ class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractException
 
     private final Map parameterTypeToField = new HashMap<>();
 
+    private FieldDescriptor httpHeadersField;
+
     ControllerAdviceAbstractExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
             ClassOutput classOutput, TypesUtil typesUtil) {
         super(exceptionDotName, classOutput);
@@ -104,89 +115,87 @@ protected void preGenerateMethodBody(ClassCreator cc) {
                     "Parameter type " + parameterTypes.get(notAllowedParameterIndex).name() + " is not supported for method"
                             + controllerAdviceMethod.name() + " of class" + controllerAdviceMethod.declaringClass().name());
         }
+
+        createHttpHeadersField(cc);
+    }
+
+    private void createHttpHeadersField(ClassCreator classCreator) {
+        FieldCreator httpHeadersFieldCreator = classCreator
+                .getFieldCreator("httpHeaders", HttpHeaders.class)
+                .setModifiers(Modifier.PRIVATE);
+        httpHeadersFieldCreator.addAnnotation(Context.class);
+        httpHeadersField = httpHeadersFieldCreator.getFieldDescriptor();
     }
 
     @Override
     void generateMethodBody(MethodCreator toResponse) {
-        if (returnType.kind() == Type.Kind.VOID) {
-            AnnotationInstance responseStatusInstance = controllerAdviceMethod.annotation(RESPONSE_STATUS);
-
-            // invoke the @ExceptionHandler method
-            exceptionHandlerMethodResponse(toResponse);
-
-            // build a JAX-RS response
-            ResultHandle status = toResponse
-                    .load(responseStatusInstance != null ? getHttpStatusFromAnnotation(responseStatusInstance)
-                            : Response.Status.NO_CONTENT.getStatusCode());
-            ResultHandle responseBuilder = toResponse.invokeStaticMethod(
-                    MethodDescriptor.ofMethod(Response.class, "status", Response.ResponseBuilder.class, int.class),
-                    status);
-
-            ResultHandle httpResponseType = toResponse.load("text/plain");
-            toResponse.invokeVirtualMethod(
-                    MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "type", Response.ResponseBuilder.class,
-                            String.class),
-                    responseBuilder, httpResponseType);
-
-            ResultHandle response = toResponse.invokeVirtualMethod(
-                    MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "build", Response.class),
-                    responseBuilder);
-            toResponse.returnValue(response);
+        if (isVoidType(returnType)) {
+            generateVoidExceptionHandler(toResponse);
+        } else if (isEntityType(returnType)) {
+            generateResponseEntityExceptionHandler(toResponse);
         } else {
-            ResultHandle exceptionHandlerMethodResponse = exceptionHandlerMethodResponse(toResponse);
-
-            ResultHandle response;
-            if (RESPONSE_ENTITY.equals(returnType.name())) {
-                /*
-                 * By default we will send JSON back unless the ResponseEntity has a String body
-                 */
-                boolean addDefaultJsonContentType = true;
-                if (returnType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
-                    if (returnType.asParameterizedType().arguments().size() == 1) {
-                        Type responseEntityParameterType = returnType.asParameterizedType().arguments().get(0);
-                        if (STRING.equals(responseEntityParameterType.name())) {
-                            addDefaultJsonContentType = false;
-                        }
-                    }
-                }
+            generateGenericResponseExceptionHandler(toResponse);
+        }
+    }
 
-                // convert Spring's ResponseEntity to JAX-RS Response
-                response = toResponse.invokeStaticMethod(
-                        MethodDescriptor.ofMethod(ResponseEntityConverter.class.getName(), "toResponse",
-                                Response.class.getName(), RESPONSE_ENTITY.toString(), boolean.class.getName()),
-                        exceptionHandlerMethodResponse, toResponse.load(addDefaultJsonContentType));
-            } else {
-                ResultHandle status = toResponse.load(getStatus(controllerAdviceMethod.annotation(RESPONSE_STATUS)));
-
-                ResultHandle responseBuilder = toResponse.invokeStaticMethod(
-                        MethodDescriptor.ofMethod(Response.class, "status", Response.ResponseBuilder.class, int.class),
-                        status);
-                /*
-                 * By default we will send JSON back unless the ResponseEntity has a String body
-                 */
-                if (!STRING.equals(returnType.name())) {
-                    toResponse.invokeVirtualMethod(
-                            MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "type", Response.ResponseBuilder.class,
-                                    String.class),
-                            responseBuilder, toResponse.load(MediaType.APPLICATION_JSON));
-                }
+    private void generateVoidExceptionHandler(MethodCreator methodCreator) {
+        invokeExceptionHandlerMethod(methodCreator);
+        int status = getAnnotationStatusOrDefault(Response.Status.NO_CONTENT.getStatusCode());
+        ResultHandle result = new ResponseBuilder(methodCreator, status)
+                .withType(getResponseContentType(methodCreator, TEXT_MEDIA_TYPES))
+                .build();
+        methodCreator.returnValue(result);
+    }
 
-                toResponse.invokeVirtualMethod(
-                        MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "entity", Response.ResponseBuilder.class,
-                                Object.class),
-                        responseBuilder, exceptionHandlerMethodResponse);
+    private void generateResponseEntityExceptionHandler(MethodCreator methodCreator) {
+        ResultHandle result = methodCreator.invokeStaticMethod(
+                MethodDescriptor.ofMethod(ResponseEntityConverter.class.getName(), "toResponse",
+                        Response.class.getName(), RESPONSE_ENTITY.toString(), MediaType.class.getName()),
+                invokeExceptionHandlerMethod(methodCreator),
+                getResponseContentType(methodCreator, getSupportedMediaTypesForType(getResponseEntityType())));
 
-                response = toResponse.invokeVirtualMethod(
-                        MethodDescriptor.ofMethod(Response.ResponseBuilder.class, "build", Response.class),
-                        responseBuilder);
-            }
+        methodCreator.returnValue(result);
+    }
+
+    private Type getResponseEntityType() {
+        if (isParameterizedType(returnType) && returnType.asParameterizedType().arguments().size() == 1) {
+            return returnType.asParameterizedType().arguments().get(0);
+        }
+        return returnType;
+    }
+
+    private void generateGenericResponseExceptionHandler(MethodCreator methodCreator) {
+        int status = getAnnotationStatusOrDefault(Response.Status.OK.getStatusCode());
+        ResultHandle result = new ResponseBuilder(methodCreator, status)
+                .withEntity(invokeExceptionHandlerMethod(methodCreator))
+                .withType(getResponseContentType(methodCreator, getSupportedMediaTypesForType(returnType)))
+                .build();
 
-            toResponse.returnValue(response);
+        methodCreator.returnValue(result);
+    }
+
+    private List getSupportedMediaTypesForType(Type type) {
+        if (isStringType(type) || isPrimitiveType(type)) {
+            return TEXT_MEDIA_TYPES;
         }
+
+        return OBJECT_MEDIA_TYPES;
     }
 
-    private ResultHandle exceptionHandlerMethodResponse(MethodCreator toResponse) {
-        String returnTypeClassName = returnType.kind() == Type.Kind.VOID ? void.class.getName() : returnType.name().toString();
+    private ResultHandle getResponseContentType(MethodCreator methodCreator, List supportedMediaTypeStrings) {
+        ResultHandle[] supportedMediaTypes = supportedMediaTypeStrings.stream()
+                .map(methodCreator::load)
+                .toArray(ResultHandle[]::new);
+
+        return methodCreator.invokeStaticMethod(
+                MethodDescriptor.ofMethod(ResponseContentTypeResolver.class, "resolve", MediaType.class,
+                        HttpHeaders.class, String[].class),
+                methodCreator.readInstanceField(httpHeadersField, methodCreator.getThis()),
+                methodCreator.marshalAsArray(String.class, supportedMediaTypes));
+    }
+
+    private ResultHandle invokeExceptionHandlerMethod(MethodCreator toResponse) {
+        String returnTypeClassName = isVoidType(returnType) ? void.class.getName() : returnType.name().toString();
 
         if (parameterTypes.isEmpty()) {
             return toResponse.invokeVirtualMethod(
@@ -228,10 +237,32 @@ private ResultHandle controllerAdviceInstance(MethodCreator toResponse) {
         return toResponse.checkCast(bean, controllerAdviceMethod.declaringClass().name().toString());
     }
 
-    private int getStatus(AnnotationInstance instance) {
-        if (instance == null) {
-            return 200;
+    private int getAnnotationStatusOrDefault(int defaultValue) {
+        AnnotationInstance annotation = controllerAdviceMethod.annotation(RESPONSE_STATUS);
+        if (annotation == null) {
+            return defaultValue;
         }
-        return getHttpStatusFromAnnotation(instance);
+
+        return getHttpStatusFromAnnotation(annotation);
+    }
+
+    private boolean isVoidType(Type type) {
+        return Type.Kind.VOID.equals(type.kind());
+    }
+
+    private boolean isPrimitiveType(Type type) {
+        return Type.Kind.PRIMITIVE.equals(type.kind());
+    }
+
+    private boolean isStringType(Type type) {
+        return DotName.createSimple(String.class.getName()).equals(type.name());
+    }
+
+    private boolean isEntityType(Type type) {
+        return RESPONSE_ENTITY.equals(type.name());
+    }
+
+    private boolean isParameterizedType(Type type) {
+        return Type.Kind.PARAMETERIZED_TYPE.equals(type.kind());
     }
 }
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseBuilder.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseBuilder.java
new file mode 100644
index 0000000000000..e94a6130754ca
--- /dev/null
+++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ResponseBuilder.java
@@ -0,0 +1,46 @@
+package io.quarkus.spring.web.deployment;
+
+import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import io.quarkus.gizmo.MethodCreator;
+import io.quarkus.gizmo.ResultHandle;
+
+final class ResponseBuilder {
+
+    private final MethodCreator methodCreator;
+
+    private final ResultHandle delegate;
+
+    ResponseBuilder(MethodCreator methodCreator, int status) {
+        this.methodCreator = methodCreator;
+        this.delegate = withStatus(status);
+    }
+
+    public ResultHandle build() {
+        return methodCreator.invokeVirtualMethod(
+                ofMethod(Response.ResponseBuilder.class, "build", Response.class), delegate);
+    }
+
+    public ResponseBuilder withType(ResultHandle type) {
+        methodCreator.invokeVirtualMethod(
+                ofMethod(Response.ResponseBuilder.class, "type", Response.ResponseBuilder.class, MediaType.class),
+                delegate, type);
+        return this;
+    }
+
+    public ResponseBuilder withEntity(ResultHandle entity) {
+        methodCreator.invokeVirtualMethod(
+                ofMethod(Response.ResponseBuilder.class, "entity", Response.ResponseBuilder.class, Object.class),
+                delegate, entity);
+        return this;
+    }
+
+    private ResultHandle withStatus(int status) {
+        return methodCreator.invokeStaticMethod(
+                ofMethod(Response.class, "status", Response.ResponseBuilder.class, int.class),
+                methodCreator.load(status));
+    }
+}
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
index de194b9e3c385..e1e8d1064e664 100644
--- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
+++ b/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
@@ -59,12 +59,6 @@
 
 public class SpringWebProcessor {
 
-    private static final DotName EXCEPTION = DotName.createSimple("java.lang.Exception");
-    private static final DotName RUNTIME_EXCEPTION = DotName.createSimple("java.lang.RuntimeException");
-
-    private static final DotName OBJECT = DotName.createSimple("java.lang.Object");
-    private static final DotName STRING = DotName.createSimple("java.lang.String");
-
     private static final DotName REST_CONTROLLER_ANNOTATION = DotName
             .createSimple("org.springframework.web.bind.annotation.RestController");
 
@@ -100,7 +94,7 @@ public class SpringWebProcessor {
     private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");
 
     private static final Set DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES = new HashSet<>(Arrays.asList(
-            MODEL_AND_VIEW, VIEW, MODEL, HTTP_ENTITY, STRING));
+            MODEL_AND_VIEW, VIEW, MODEL, HTTP_ENTITY));
 
     @BuildStep
     FeatureBuildItem registerFeature() {
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java
new file mode 100644
index 0000000000000..141d891b76d30
--- /dev/null
+++ b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java
@@ -0,0 +1,52 @@
+package io.quarkus.spring.web.runtime;
+
+import static javax.ws.rs.core.HttpHeaders.ACCEPT;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Variant;
+
+import org.jboss.resteasy.core.request.ServerDrivenNegotiation;
+
+public final class ResponseContentTypeResolver {
+
+    private static final MediaType DEFAULT_MEDIA_TYPE = TEXT_PLAIN_TYPE;
+
+    public static MediaType resolve(HttpHeaders httpHeaders, String... supportedMediaTypes) {
+        Objects.requireNonNull(httpHeaders, "HttpHeaders cannot be null");
+        Objects.requireNonNull(supportedMediaTypes, "Supported media types array cannot be null");
+
+        Variant bestVariant = getBestVariant(httpHeaders.getRequestHeader(ACCEPT), getMediaTypeVariants(supportedMediaTypes));
+
+        if (bestVariant != null) {
+            return bestVariant.getMediaType();
+        }
+
+        if (supportedMediaTypes.length > 0) {
+            return MediaType.valueOf(supportedMediaTypes[0]);
+        }
+
+        return DEFAULT_MEDIA_TYPE;
+    }
+
+    private static Variant getBestVariant(List acceptHeaders, List variants) {
+        ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation();
+        negotiation.setAcceptHeaders(acceptHeaders);
+
+        return negotiation.getBestMatch(variants);
+    }
+
+    private static List getMediaTypeVariants(String... mediaTypes) {
+        Variant.VariantListBuilder variantListBuilder = Variant.VariantListBuilder.newInstance();
+
+        for (String mediaType : mediaTypes) {
+            variantListBuilder.mediaTypes(MediaType.valueOf(mediaType));
+        }
+
+        return variantListBuilder.build();
+    }
+}
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
index 8735c6282f2f3..13e6e0f6baf4c 100644
--- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
+++ b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
@@ -17,26 +17,25 @@
  */
 public class ResponseEntityConverter {
 
-    public static Response toResponse(ResponseEntity responseEntity, boolean addJsonContentTypeIfNotSet) {
-        return new BuiltResponse(
-                responseEntity.getStatusCodeValue(), toHeaders(responseEntity.getHeaders(), addJsonContentTypeIfNotSet),
+    public static Response toResponse(ResponseEntity responseEntity, MediaType defaultContentType) {
+        return new BuiltResponse(responseEntity.getStatusCodeValue(),
+                addContentTypeIfMissing(toJaxRsHeaders(responseEntity.getHeaders()), defaultContentType),
                 responseEntity.getBody(),
                 new Annotation[0]);
     }
 
-    private static Headers toHeaders(HttpHeaders springHeaders, boolean addJsonContentTypeIfNotSet) {
+    private static Headers toJaxRsHeaders(HttpHeaders springHeaders) {
         Headers jaxRsHeaders = new Headers<>();
         for (Map.Entry> entry : springHeaders.entrySet()) {
             jaxRsHeaders.addAll(entry.getKey(), entry.getValue().toArray(new Object[0]));
         }
-        /*
-         * We add the default application/json content type if no content type is specified
-         * since this is the default value when returning an object from a Spring RestController
-         */
-        if (addJsonContentTypeIfNotSet && !jaxRsHeaders.containsKey(javax.ws.rs.core.HttpHeaders.CONTENT_TYPE)) {
-            jaxRsHeaders.add(javax.ws.rs.core.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
-        }
         return jaxRsHeaders;
     }
 
+    private static Headers addContentTypeIfMissing(Headers headers, MediaType contentType) {
+        if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            headers.add(HttpHeaders.CONTENT_TYPE, contentType);
+        }
+        return headers;
+    }
 }
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java
index 342e44abc6e14..ce892699c5ee8 100644
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/CustomAdvice.java
@@ -1,7 +1,6 @@
 package io.quarkus.it.spring.web;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -18,21 +17,35 @@ public void handleRuntimeException() {
 
     }
 
-    @ExceptionHandler(UnannotatedException.class)
+    @ExceptionHandler(HandledUnannotatedException.class)
     public void unannotatedException() {
 
     }
 
-    @ExceptionHandler(IllegalStateException.class)
-    public ResponseEntity handleIllegalStateException(IllegalStateException e,
-            HttpServletRequest request, HttpServletResponse response) {
-        return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).header("custom-header", "custom-value")
-                .body(new Error(request.getRequestURI() + ":" + e.getMessage()));
+    @ExceptionHandler(HandledResponseEntityException.class)
+    public ResponseEntity handleResponseEntityException(HandledResponseEntityException e,
+            HttpServletRequest request) {
+
+        ResponseEntity.BodyBuilder bodyBuilder = ResponseEntity
+                .status(HttpStatus.PAYMENT_REQUIRED)
+                .header("custom-header", "custom-value");
+
+        if (e.getContentType() != null) {
+            bodyBuilder.contentType(e.getContentType());
+        }
+
+        return bodyBuilder.body(new Error(request.getRequestURI() + ":" + e.getMessage()));
     }
 
     @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
-    @ExceptionHandler(IllegalArgumentException.class)
-    public Error handleGreetingIllegalArgumentException(IllegalArgumentException e) {
+    @ExceptionHandler(HandledPojoException.class)
+    public Error handlePojoExcepton(HandledPojoException e) {
         return new Error(e.getMessage());
     }
+
+    @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)
+    @ExceptionHandler(HandledStringException.class)
+    public String handleStringException(HandledStringException e) {
+        return e.getMessage();
+    }
 }
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Error.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Error.java
index 318c483549e0f..a45058691defb 100644
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Error.java
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/Error.java
@@ -1,8 +1,15 @@
 package io.quarkus.it.spring.web;
 
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
 public class Error {
 
-    private final String message;
+    private String message;
+
+    public Error() {
+
+    }
 
     public Error(String message) {
         this.message = message;
@@ -11,4 +18,8 @@ public Error(String message) {
     public String getMessage() {
         return message;
     }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
 }
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java
index f7d08ad68096b..5b5d21bf446ad 100644
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/ExceptionThrowingController.java
@@ -1,5 +1,6 @@
 package io.quarkus.it.spring.web;
 
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -10,49 +11,63 @@
 @RequestMapping("/exception")
 public class ExceptionThrowingController {
 
-    @RequestMapping(method = RequestMethod.GET, path = "/first")
-    public void first() throws FirstException {
-        throw new FirstException("first");
+    @RequestMapping(method = RequestMethod.GET, path = "/unhandled/exception")
+    public void voidWithUnhandledAnnotatedException() throws UnhandledAnnotatedException {
+        throw new UnhandledAnnotatedException("unhandled");
     }
 
-    @GetMapping(path = "/second")
-    public String second() {
-        throw new SecondException();
+    @GetMapping(path = "/unhandled/runtime")
+    public String stringWithUnhandledAnnotatedRuntimeException() {
+        throw new UnhandledAnnotatedRuntimeException();
     }
 
-    @GetMapping("/void")
-    public void runtimeException() {
+    @GetMapping("/runtime")
+    public void voidWithRuntimeException() {
         throw new RuntimeException();
     }
 
     @GetMapping("/unannotated")
-    public void unannotated() {
-        throw new UnannotatedException();
+    public void voidWithUnannotatedException() {
+        throw new HandledUnannotatedException();
     }
 
-    @GetMapping("/responseEntity")
-    public Greeting handledByResponseEntity() {
-        throw new IllegalStateException("bad state");
+    @GetMapping("/re/re")
+    public ResponseEntity responseEntityWithResponseEntityException() {
+        throw new HandledResponseEntityException("bad state");
     }
 
-    @GetMapping("/responseEntityFromVoidReturningMethod")
-    public void handledByResponseEntityFromVoidReturningMethod() {
-        throw new IllegalStateException("bad state");
+    @GetMapping("/re/pojo")
+    public Greeting pojoWithResponseEntityException() {
+        throw new HandledResponseEntityException("bad state");
     }
 
-    @GetMapping("/pojo")
-    public Greeting greetingWithIllegalArgumentException() {
-        throw new IllegalArgumentException("hello from error");
+    @GetMapping("/re/void")
+    public void voidWithResponseEntityException() {
+        throw new HandledResponseEntityException("bad state");
     }
 
-    @GetMapping("/pojoWithVoidReturnType")
-    public void greetingWithIllegalArgumentExceptionAndVoidReturnType() {
-        throw new IllegalArgumentException("hello from error");
+    @GetMapping("/re/void/xml")
+    public void voidWithResponseEntityExceptionAsXml() {
+        throw new HandledResponseEntityException("bad state", MediaType.APPLICATION_XML);
     }
 
-    @GetMapping("/re")
-    public ResponseEntity responseEntityWithIllegalArgumentException() {
-        throw new IllegalStateException("hello from error");
+    @GetMapping("/pojo/re")
+    public ResponseEntity responseEntityWithPojoException() {
+        throw new HandledPojoException("bad state");
     }
 
+    @GetMapping("/pojo/pojo")
+    public Greeting pojoWithPojoException() {
+        throw new HandledPojoException("bad state");
+    }
+
+    @GetMapping("/pojo/void")
+    public void voidWithPojoException() {
+        throw new HandledPojoException("bad state");
+    }
+
+    @GetMapping("/string")
+    public String stringWithStringException() {
+        throw new HandledStringException("bad state");
+    }
 }
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledPojoException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledPojoException.java
new file mode 100644
index 0000000000000..d978305acda21
--- /dev/null
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledPojoException.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.spring.web;
+
+public class HandledPojoException extends RuntimeException {
+
+    public HandledPojoException(String message) {
+        super(message);
+    }
+}
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledResponseEntityException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledResponseEntityException.java
new file mode 100644
index 0000000000000..59c4514a3ce22
--- /dev/null
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledResponseEntityException.java
@@ -0,0 +1,21 @@
+package io.quarkus.it.spring.web;
+
+import org.springframework.http.MediaType;
+
+public class HandledResponseEntityException extends RuntimeException {
+
+    private final MediaType contentType;
+
+    public HandledResponseEntityException(String message) {
+        this(message, null);
+    }
+
+    public HandledResponseEntityException(String message, MediaType contentType) {
+        super(message);
+        this.contentType = contentType;
+    }
+
+    public MediaType getContentType() {
+        return contentType;
+    }
+}
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledStringException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledStringException.java
new file mode 100644
index 0000000000000..b48c1d3cb751a
--- /dev/null
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledStringException.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.spring.web;
+
+public class HandledStringException extends RuntimeException {
+
+    public HandledStringException(String message) {
+        super(message);
+    }
+}
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledUnannotatedException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledUnannotatedException.java
new file mode 100644
index 0000000000000..3075539234f8c
--- /dev/null
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/HandledUnannotatedException.java
@@ -0,0 +1,4 @@
+package io.quarkus.it.spring.web;
+
+public class HandledUnannotatedException extends RuntimeException {
+}
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnannotatedException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnannotatedException.java
deleted file mode 100644
index bfcf01f897077..0000000000000
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnannotatedException.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package io.quarkus.it.spring.web;
-
-public class UnannotatedException extends RuntimeException {
-}
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/FirstException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedException.java
similarity index 55%
rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/FirstException.java
rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedException.java
index 4c94dd6fdd083..2ef968113f57e 100644
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/FirstException.java
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedException.java
@@ -3,8 +3,8 @@
 import org.springframework.web.bind.annotation.ResponseStatus;
 
 @ResponseStatus
-public class FirstException extends Exception {
-    public FirstException(String message) {
+public class UnhandledAnnotatedException extends Exception {
+    public UnhandledAnnotatedException(String message) {
         super(message);
     }
 }
diff --git a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/SecondException.java b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedRuntimeException.java
similarity index 72%
rename from integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/SecondException.java
rename to integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedRuntimeException.java
index 1a5604c35efd1..6a8fe92bf349d 100644
--- a/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/SecondException.java
+++ b/integration-tests/spring-web/src/main/java/io/quarkus/it/spring/web/UnhandledAnnotatedRuntimeException.java
@@ -4,6 +4,6 @@
 import org.springframework.web.bind.annotation.ResponseStatus;
 
 @ResponseStatus(code = HttpStatus.SERVICE_UNAVAILABLE)
-public class SecondException extends RuntimeException {
+public class UnhandledAnnotatedRuntimeException extends RuntimeException {
 
 }
diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingIT.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingIT.java
new file mode 100644
index 0000000000000..2a41602f75e05
--- /dev/null
+++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingIT.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.spring.web;
+
+import io.quarkus.test.junit.NativeImageTest;
+
+@NativeImageTest
+public class ExceptionHandlingIT extends ExceptionHandlingTest {
+}
diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingTest.java
new file mode 100644
index 0000000000000..e7b5e0023a93c
--- /dev/null
+++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/ExceptionHandlingTest.java
@@ -0,0 +1,166 @@
+package io.quarkus.it.spring.web;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.is;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+
+@QuarkusTest
+public class ExceptionHandlingTest {
+
+    @Test
+    public void testUnhandledAnnotatedException() {
+        RestAssured.when().get("/exception/unhandled/exception").then()
+                .contentType("text/plain")
+                .body(containsString("unhandled"))
+                .statusCode(500);
+    }
+
+    @Test
+    public void testUnhandledAnnotatedRuntimeException() {
+        RestAssured.when().get("/exception/unhandled/runtime").then()
+                .contentType("text/plain")
+                .body(is(emptyString()))
+                .statusCode(503);
+    }
+
+    @Test
+    public void testHandledRuntimeException() {
+        RestAssured.when().get("/exception/runtime").then()
+                .contentType("text/plain")
+                .body(is(emptyString()))
+                .statusCode(400);
+    }
+
+    @Test
+    public void testHandledRuntimeExceptionAsXml() {
+        RestAssured.given().accept("application/xml")
+                .when().get("/exception/runtime").then()
+                .contentType("application/xml")
+                .body(is(emptyString()))
+                .statusCode(400);
+    }
+
+    @Test
+    public void testHandledUnannotatedException() {
+        RestAssured.when().get("/exception/unannotated").then()
+                .contentType("text/plain")
+                .body(is(emptyString()))
+                .statusCode(204);
+    }
+
+    @Test
+    public void testHandledUnannotatedExceptionAsXml() {
+        RestAssured.given().accept("application/xml")
+                .when().get("/exception/unannotated").then()
+                .contentType("application/xml")
+                .body(is(emptyString()))
+                .statusCode(204);
+    }
+
+    @Test
+    public void testResponseEntityWithResponseEntityException() {
+        RestAssured.when().get("/exception/re/re").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .body(containsString("/exception/re/re"))
+                .statusCode(402)
+                .header("custom-header", "custom-value");
+    }
+
+    @Test
+    public void testPojoWithResponseEntityException() {
+        RestAssured.when().get("/exception/re/pojo").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .body(containsString("/exception/re/pojo"))
+                .statusCode(402)
+                .header("custom-header", "custom-value");
+    }
+
+    @Test
+    public void testVoidWithResponseEntityException() {
+        RestAssured.when().get("/exception/re/void").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .body(containsString("/exception/re/void"))
+                .statusCode(402)
+                .header("custom-header", "custom-value");
+    }
+
+    @Test
+    public void testVoidWithResponseEntityExceptionAsXml() {
+        RestAssured.given().accept("application/xml")
+                .when().get("/exception/re/void").then()
+                .contentType("application/xml")
+                .body(containsString("bad state"))
+                .body(containsString("/exception/re/void"))
+                .statusCode(402)
+                .header("custom-header", "custom-value");
+    }
+
+    @Test
+    public void testVoidWithResponseEntityExceptionAsHardcodedXml() {
+        RestAssured.given().accept("application/json")
+                .when().get("/exception/re/void/xml").then()
+                .contentType("application/xml")
+                .body(containsString("bad state"))
+                .body(containsString("/exception/re/void/xml"))
+                .statusCode(402)
+                .header("custom-header", "custom-value");
+    }
+
+    @Test
+    public void testResponseEntityWithPojoException() {
+        RestAssured.when().get("/exception/pojo/re").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .statusCode(417);
+    }
+
+    @Test
+    public void testPojoWithPojoException() {
+        RestAssured.when().get("/exception/pojo/pojo").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .statusCode(417);
+    }
+
+    @Test
+    public void testVoidWithPojoException() {
+        RestAssured.when().get("/exception/pojo/void").then()
+                .contentType("application/json")
+                .body(containsString("bad state"))
+                .statusCode(417);
+    }
+
+    @Test
+    public void testVoidWithPojoExceptionAsXml() {
+        RestAssured.given().accept("application/xml")
+                .when().get("/exception/pojo/void").then()
+                .contentType("application/xml")
+                .body(containsString("bad state"))
+                .statusCode(417);
+    }
+
+    @Test
+    public void testStringWithStringException() {
+        RestAssured.when().get("/exception/string").then()
+                .statusCode(418)
+                .contentType("text/plain")
+                .body(is("bad state"));
+    }
+
+    @Test
+    public void testStringWithStringExceptionAsXml() {
+        RestAssured.given().accept("application/xml")
+                .when().get("/exception/string").then()
+                .statusCode(418)
+                .contentType("application/xml")
+                .body(is("bad state"));
+    }
+}
diff --git a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java
index 8d12461c8a247..0cf9ce1bf292d 100644
--- a/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java
+++ b/integration-tests/spring-web/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java
@@ -1,8 +1,6 @@
 package io.quarkus.it.spring.web;
 
 import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.emptyString;
-import static org.hamcrest.Matchers.is;
 
 import java.util.Optional;
 
@@ -125,86 +123,12 @@ public void testJsonInputAndResult() {
                 .body(containsString("hello George"));
     }
 
-    @Test
-    public void testFirstResponseStatusHoldingException() {
-        RestAssured.when().get("/exception/first").then()
-                .contentType("text/plain")
-                .body(containsString("first"))
-                .statusCode(500);
-    }
-
-    @Test
-    public void testSecondResponseStatusHoldingException() {
-        RestAssured.when().get("/exception/second").then()
-                .contentType("text/plain")
-                .body(is(emptyString()))
-                .statusCode(503);
-    }
-
-    @Test
-    public void testExceptionHandlerVoidReturnType() {
-        RestAssured.when().get("/exception/void").then()
-                .contentType("text/plain")
-                .body(is(emptyString()))
-                .statusCode(400);
-    }
-
-    @Test
-    public void testExceptionHandlerWithoutResponseStatusOnExceptionOrMethod() {
-        RestAssured.when().get("/exception/unannotated").then()
-                .contentType("text/plain")
-                .body(is(emptyString()))
-                .statusCode(204);
-    }
-
-    @Test
-    public void testExceptionHandlerResponseEntityType() {
-        RestAssured.when().get("/exception/responseEntity").then()
-                .contentType("application/json")
-                .body(containsString("bad state"), containsString("responseEntity"))
-                .statusCode(402)
-                .header("custom-header", "custom-value");
-    }
-
-    @Test
-    public void testExceptionHandlerResponseEntityTypeFromVoidReturningMethod() {
-        RestAssured.when().get("/exception/responseEntityFromVoidReturningMethod").then()
-                .contentType("application/json")
-                .body(containsString("bad state"), containsString("responseEntity"))
-                .statusCode(402)
-                .header("custom-header", "custom-value");
-    }
-
-    @Test
-    public void testExceptionHandlerPojoEntityType() {
-        RestAssured.when().get("/exception/pojo").then()
-                .contentType("application/json")
-                .body(containsString("hello from error"))
-                .statusCode(417);
-    }
-
-    @Test
-    public void testControllerMethodWithVoidReturnTypeAndExceptionHandlerPojoEntityType() {
-        RestAssured.when().get("/exception/pojoWithVoidReturnType").then()
-                .contentType("application/json")
-                .body(containsString("hello from error"))
-                .statusCode(417);
-    }
-
     @Test
     public void testRestControllerWithoutRequestMapping() {
         RestAssured.when().get("/hello").then()
                 .body(containsString("hello"));
     }
 
-    @Test
-    public void testResponseEntityWithIllegalArgumentException() {
-        RestAssured.when().get("/exception/re").then()
-                .contentType("application/json")
-                .body(containsString("hello from error"))
-                .statusCode(402);
-    }
-
     @Test
     public void testMethodReturningXmlContent() {
         RestAssured.when().get("/book")

From 1b5ded4dd592ae8aad3358c0d95a219fa7e7d00d Mon Sep 17 00:00:00 2001
From: Justin Lee 
Date: Wed, 11 Dec 2019 12:35:51 -0500
Subject: [PATCH 350/602] only base64 encode binary content types

---
 bom/runtime/pom.xml                           | 14 +++++++++++++
 .../src/main/asciidoc/amazon-lambda-http.adoc |  6 ++++++
 extensions/amazon-lambda-http/runtime/pom.xml |  4 ++++
 .../amazon/lambda/http/LambdaHttpHandler.java | 21 +++++++++++++++++--
 4 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index 1607ba7c937cd..fb722d43982f1 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -2319,6 +2319,20 @@
                 com.amazonaws.serverless
                 aws-serverless-java-container-core
                 ${aws-lambda-serverless-java-container.version}
+                
+                    
+                        commons-logging
+                        commons-logging
+                    
+                    
+                        javax.servlet
+                        javax.servlet-api
+                    
+                    
+                        javax.ws.rs
+                        javax.ws.rs-api
+                    
+                
             
 
             
diff --git a/docs/src/main/asciidoc/amazon-lambda-http.adoc b/docs/src/main/asciidoc/amazon-lambda-http.adoc
index 32eebbcafd5d1..29bb535bffe24 100644
--- a/docs/src/main/asciidoc/amazon-lambda-http.adoc
+++ b/docs/src/main/asciidoc/amazon-lambda-http.adoc
@@ -185,6 +185,12 @@ It should give you something like the following output:
 
 The `OutputValue` attribute is the root URL for your lambda. Copy it to your browser and add `hello` at the end.
 
+[NOTE]
+Responses for binary types will be automatically encoded with base64.  This is different than the behavior using
+`quarkus:dev` which will return the raw bytes.  Amazon's API has additional restrictions requiring the base64 encoding.
+In general, client code will automatically handle this encoding but in certain custom situations, you should be aware
+you may need to manually manage that encoding.
+
 == Examine the POM
 
 If you want to adapt an existing Resteasy, Undertow, or Vert.x Web project to Amazon Lambda, there's a couple
diff --git a/extensions/amazon-lambda-http/runtime/pom.xml b/extensions/amazon-lambda-http/runtime/pom.xml
index 800c30c47bc1d..6cc3acf55e907 100644
--- a/extensions/amazon-lambda-http/runtime/pom.xml
+++ b/extensions/amazon-lambda-http/runtime/pom.xml
@@ -28,6 +28,10 @@
             io.quarkus
             quarkus-core
         
+        
+            com.amazonaws.serverless
+            aws-serverless-java-container-core
+        
         
             org.graalvm.nativeimage
             svm
diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java
index 2169542d9e1f9..7481a03e8572c 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java
+++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java
@@ -9,6 +9,7 @@
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
+import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
 import com.amazonaws.services.lambda.runtime.Context;
 import com.amazonaws.services.lambda.runtime.RequestHandler;
 
@@ -144,8 +145,12 @@ private AwsProxyResponse nettyDispatch(VirtualClientConnection connection, AwsPr
                 }
                 if (msg instanceof LastHttpContent) {
                     if (baos != null) {
-                        responseBuilder.setBase64Encoded(true);
-                        responseBuilder.setBody(Base64.getMimeEncoder().encodeToString(baos.toByteArray()));
+                        if (isBinary(responseBuilder.getMultiValueHeaders().getFirst("Content-Type"))) {
+                            responseBuilder.setBase64Encoded(true);
+                            responseBuilder.setBody(Base64.getMimeEncoder().encodeToString(baos.toByteArray()));
+                        } else {
+                            responseBuilder.setBody(new String(baos.toByteArray(), "UTF-8"));
+                        }
                     }
                     return responseBuilder;
                 }
@@ -156,4 +161,16 @@ private AwsProxyResponse nettyDispatch(VirtualClientConnection connection, AwsPr
         }
     }
 
+    private boolean isBinary(String contentType) {
+        if (contentType != null) {
+            int index = contentType.indexOf(';');
+            if (index >= 0) {
+                return LambdaContainerHandler.getContainerConfig().isBinaryContentType(contentType.substring(0, index));
+            } else {
+                return LambdaContainerHandler.getContainerConfig().isBinaryContentType(contentType);
+            }
+        }
+        return false;
+    }
+
 }

From f03ad3f8c56f03c6757d7004b2f4c67f4b5936cf Mon Sep 17 00:00:00 2001
From: Alexey Loubyansky 
Date: Thu, 12 Dec 2019 17:32:44 +0100
Subject: [PATCH 351/602] Maven plugin's devmode doesn't take into account
 versions overridden through system properties or profiles

---
 devtools/maven/pom.xml                        |  25 ----
 .../main/java/io/quarkus/maven/DevMojo.java   |  67 ++++-----
 .../resolver/maven/MavenRepoInitializer.java  |  78 +++++-----
 .../maven/options/BootstrapMavenOptions.java  | 141 ++++++++++++++++++
 .../options/BootstrapMavenOptionsParser.java  |  58 +++++++
 integration-tests/maven/pom.xml               |  10 +-
 6 files changed, 270 insertions(+), 109 deletions(-)
 create mode 100644 independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptions.java
 create mode 100644 independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java

diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml
index 557c564b843e2..876e6320b2cdd 100644
--- a/devtools/maven/pom.xml
+++ b/devtools/maven/pom.xml
@@ -21,12 +21,6 @@
         
             io.quarkus
             quarkus-bootstrap-core
-            
-                
-                    org.apache.maven.wagon
-                    wagon-provider-api
-                
-            
         
         
             io.quarkus
@@ -58,10 +52,6 @@
             jakarta.enterprise
             jakarta.enterprise.cdi-api
         
-        
-            org.apache.maven.shared
-            maven-invoker
-        
         
             org.apache.maven
             maven-core
@@ -72,21 +62,6 @@
                 
             
         
-        
-            org.apache.maven
-            maven-toolchain
-            
-                
-                    commons-logging
-                    commons-logging-api
-                
-                
-                    log4j
-                    log4j
-                
-            
-        
-
         
             org.apache.maven.plugin-tools
             maven-plugin-annotations
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
index 782afab8e0b52..f40b1b722d272 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
@@ -35,6 +35,7 @@
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Plugin;
 import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.BuildPluginManager;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Component;
@@ -43,24 +44,22 @@
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
 import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.invoker.DefaultInvocationRequest;
-import org.apache.maven.shared.invoker.InvocationRequest;
-import org.apache.maven.shared.invoker.Invoker;
-import org.apache.maven.shared.invoker.MavenInvocationException;
-import org.apache.maven.toolchain.ToolchainManager;
 import org.codehaus.plexus.util.xml.Xpp3Dom;
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.WorkspaceReader;
 import org.eclipse.aether.resolution.ArtifactRequest;
 import org.eclipse.aether.resolution.ArtifactResolutionException;
 import org.eclipse.aether.resolution.ArtifactResult;
+import org.twdata.maven.mojoexecutor.MojoExecutor;
 
 import io.quarkus.bootstrap.model.AppDependency;
 import io.quarkus.bootstrap.model.AppModel;
 import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
 import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.resolver.maven.MavenRepoInitializer;
 import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
 import io.quarkus.dev.DevModeContext;
 import io.quarkus.dev.DevModeMain;
@@ -100,6 +99,9 @@ public class DevMojo extends AbstractMojo {
             "install",
             "deploy"));
 
+    private static final String ORG_APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins";
+    private static final String MAVEN_COMPILER_PLUGIN = "maven-compiler-plugin";
+
     /**
      * The directory for compiled classes.
      */
@@ -185,9 +187,6 @@ public class DevMojo extends AbstractMojo {
     @Component
     private RepositorySystem repoSystem;
 
-    @Component
-    private Invoker invoker;
-
     @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
     private RepositorySystemSession repoSession;
 
@@ -228,18 +227,14 @@ public class DevMojo extends AbstractMojo {
     private String target;
 
     @Component
-    private ToolchainManager toolchainManager;
+    private WorkspaceReader wsReader;
 
-    public ToolchainManager getToolchainManager() {
-        return toolchainManager;
-    }
-
-    public MavenSession getSession() {
-        return session;
-    }
+    @Component
+    private BuildPluginManager pluginManager;
 
     @Override
     public void execute() throws MojoFailureException, MojoExecutionException {
+
         mavenVersionEnforcer.ensureMavenVersion(getLog(), session);
         boolean found = MojoUtils.checkProjectForMavenBuildPlugin(project);
 
@@ -267,15 +262,23 @@ public void execute() throws MojoFailureException, MojoExecutionException {
 
         //if the user did not compile we run it for them
         if (compileNeeded) {
-            try {
-                InvocationRequest request = new DefaultInvocationRequest();
-                request.setBatchMode(true);
-                request.setGoals(Collections.singletonList("compile"));
-
-                invoker.execute(request);
-            } catch (MavenInvocationException e) {
-                throw new MojoExecutionException(e.getMessage(), e);
+            // Compile the project
+            final String key = ORG_APACHE_MAVEN_PLUGINS + ":" + MAVEN_COMPILER_PLUGIN;
+            final Plugin plugin = project.getPlugin(key);
+            if (plugin == null) {
+                throw new MojoExecutionException("Failed to locate " + key + " among the project plugins");
             }
+            MojoExecutor.executeMojo(
+                    MojoExecutor.plugin(
+                            MojoExecutor.groupId(ORG_APACHE_MAVEN_PLUGINS),
+                            MojoExecutor.artifactId(MAVEN_COMPILER_PLUGIN),
+                            MojoExecutor.version(plugin.getVersion())),
+                    MojoExecutor.goal("compile"),
+                    MojoExecutor.configuration(),
+                    MojoExecutor.executionEnvironment(
+                            project,
+                            session,
+                            pluginManager));
         }
 
         try {
@@ -522,10 +525,12 @@ void prepare() throws Exception {
 
             final AppModel appModel;
             try {
+                RepositorySystem repoSystem = DevMojo.this.repoSystem;
                 final LocalProject localProject;
                 if (noDeps) {
                     localProject = LocalProject.load(outputDirectory.toPath());
                     addProject(devModeContext, localProject);
+                    pomFiles.add(localProject.getDir().resolve("pom.xml"));
                 } else {
                     localProject = LocalProject.loadWorkspace(outputDirectory.toPath());
                     for (LocalProject project : localProject.getSelfWithLocalDeps()) {
@@ -539,20 +544,10 @@ void prepare() throws Exception {
                             }
                         }
                         addProject(devModeContext, project);
+                        pomFiles.add(project.getDir().resolve("pom.xml"));
                     }
+                    repoSystem = MavenRepoInitializer.getRepositorySystem(repoSession.isOffline(), localProject.getWorkspace());
                 }
-                for (LocalProject i : localProject.getSelfWithLocalDeps()) {
-                    pomFiles.add(i.getDir().resolve("pom.xml"));
-                }
-
-                /*
-                 * TODO: support multiple resources dirs for config hot deployment
-                 * String resources = null;
-                 * for (Resource i : project.getBuild().getResources()) {
-                 * resources = i.getDirectory();
-                 * break;
-                 * }
-                 */
 
                 appModel = new BootstrapAppModelResolver(MavenArtifactResolver.builder()
                         .setRepositorySystem(repoSystem)
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenRepoInitializer.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenRepoInitializer.java
index f002c3d65f84c..99acb4e515855 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenRepoInitializer.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenRepoInitializer.java
@@ -11,9 +11,6 @@
 import java.util.Properties;
 import java.util.StringTokenizer;
 
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.ParseException;
-import org.apache.maven.cli.CLIManager;
 import org.apache.maven.model.building.ModelBuilder;
 import org.apache.maven.model.building.ModelProblemCollector;
 import org.apache.maven.model.building.ModelProblemCollectorRequest;
@@ -63,6 +60,7 @@
 import org.eclipse.aether.util.repository.DefaultProxySelector;
 import org.jboss.logging.Logger;
 import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptions;
 import io.quarkus.bootstrap.util.PropertyUtils;
 
 
@@ -87,24 +85,22 @@ public class MavenRepoInitializer {
     private static final File USER_SETTINGS_FILE;
     private static final File GLOBAL_SETTINGS_FILE;
 
-    private static final CommandLine mvnArgs;
+    private static final String ALTERNATE_USER_SETTINGS = "s";
+    private static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
+    private static final String OFFLINE = "o";
+    private static final String SUPRESS_SNAPSHOT_UPDATES = "nsu";
+    private static final String UPDATE_SNAPSHOTS = "U";
+    private static final String CHECKSUM_FAILURE_POLICY = "C";
+    private static final String CHECKSUM_WARNING_POLICY = "c";
+    private static final String ACTIVATE_PROFILES = "P";
+
+    private static final BootstrapMavenOptions mvnArgs;
 
     static {
         final String mvnCmd = System.getenv(MAVEN_CMD_LINE_ARGS);
-        String userSettings = null;
-        String globalSettings = null;
-        if(mvnCmd != null) {
-            final CLIManager mvnCli = new CLIManager();
-            try {
-                mvnArgs = mvnCli.parse(mvnCmd.split("\\s+"));
-            } catch (ParseException e) {
-                throw new IllegalStateException("Failed to parse Maven command line arguments", e);
-            }
-            userSettings = mvnArgs.getOptionValue(CLIManager.ALTERNATE_USER_SETTINGS);
-            globalSettings = mvnArgs.getOptionValue(CLIManager.ALTERNATE_GLOBAL_SETTINGS);
-        } else {
-            mvnArgs = null;
-        }
+        mvnArgs = BootstrapMavenOptions.newInstance(mvnCmd);
+        final String userSettings = mvnArgs.getOptionValue(ALTERNATE_USER_SETTINGS);
+        final String globalSettings = mvnArgs.getOptionValue(ALTERNATE_GLOBAL_SETTINGS);
 
         File f = userSettings != null ? resolveUserSettings(userSettings) : new File(userMavenConfigurationHome, SETTINGS_XML);
         USER_SETTINGS_FILE = f != null && f.exists() ? f : null;
@@ -185,18 +181,18 @@ public static DefaultRepositorySystemSession newSession(RepositorySystem system,
 
         session.setOffline(settings.isOffline());
 
-        if(mvnArgs != null) {
-            if(!session.isOffline() && mvnArgs.hasOption(CLIManager.OFFLINE)) {
+        if(!mvnArgs.isEmpty()) {
+            if(!session.isOffline() && mvnArgs.hasOption(OFFLINE)) {
                 session.setOffline(true);
             }
-            if(mvnArgs.hasOption(CLIManager.SUPRESS_SNAPSHOT_UPDATES)) {
+            if(mvnArgs.hasOption(SUPRESS_SNAPSHOT_UPDATES)) {
                 session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
-            } else if(mvnArgs.hasOption(CLIManager.UPDATE_SNAPSHOTS)) {
+            } else if(mvnArgs.hasOption(UPDATE_SNAPSHOTS)) {
                 session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
             }
-            if(mvnArgs.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) {
+            if(mvnArgs.hasOption(CHECKSUM_FAILURE_POLICY)) {
                 session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL);
-            } else if(mvnArgs.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) {
+            } else if(mvnArgs.hasOption(CHECKSUM_WARNING_POLICY)) {
                 session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_WARN);
             }
         }
@@ -271,24 +267,22 @@ public static List getRemoteRepos(Settings settings, Repositor
 
             final List activeProfiles = new ArrayList<>(0);
             final List inactiveProfiles = new ArrayList<>(0);
-            if(mvnArgs != null) {
-                final String[] profileOptionValues = mvnArgs.getOptionValues(CLIManager.ACTIVATE_PROFILES);
-                if (profileOptionValues != null && profileOptionValues.length > 0) {
-                    for (String profileOptionValue : profileOptionValues) {
-                        final StringTokenizer profileTokens = new StringTokenizer(profileOptionValue, ",");
-                        while (profileTokens.hasMoreTokens()) {
-                            final String profileAction = profileTokens.nextToken().trim();
-                            if(profileAction.isEmpty()) {
-                                continue;
-                            }
-                            final char c = profileAction.charAt(0);
-                            if (c == '-' || c == '!') {
-                                inactiveProfiles.add(profileAction.substring(1));
-                            } else if (c == '+') {
-                                activeProfiles.add(profileAction.substring(1));
-                            } else {
-                                activeProfiles.add(profileAction);
-                            }
+            final String[] profileOptionValues = mvnArgs.getOptionValues(ACTIVATE_PROFILES);
+            if (profileOptionValues != null && profileOptionValues.length > 0) {
+                for (String profileOptionValue : profileOptionValues) {
+                    final StringTokenizer profileTokens = new StringTokenizer(profileOptionValue, ",");
+                    while (profileTokens.hasMoreTokens()) {
+                        final String profileAction = profileTokens.nextToken().trim();
+                        if (profileAction.isEmpty()) {
+                            continue;
+                        }
+                        final char c = profileAction.charAt(0);
+                        if (c == '-' || c == '!') {
+                            inactiveProfiles.add(profileAction.substring(1));
+                        } else if (c == '+') {
+                            activeProfiles.add(profileAction.substring(1));
+                        } else {
+                            activeProfiles.add(profileAction);
                         }
                     }
                 }
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptions.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptions.java
new file mode 100644
index 0000000000000..8aeb6ff853661
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptions.java
@@ -0,0 +1,141 @@
+package io.quarkus.bootstrap.resolver.maven.options;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.quarkus.bootstrap.util.PropertyUtils;
+
+/**
+ * This class resolves relevant Maven command line options in case it's called
+ * from a Maven build process. Maven internally uses org.apache.commons.cli.* API
+ * besides Maven-specific API. This class locates the Maven's lib directory that
+ * was used to launch the build process and loads the necessary classes from that
+ * lib directory.
+ */
+public class BootstrapMavenOptions {
+
+    public static Map parse(String cmdLine) {
+        if(cmdLine == null) {
+            return Collections.emptyMap();
+        }
+        final String[] args = cmdLine.split("\\s+");
+        if(args.length == 0) {
+            return Collections.emptyMap();
+        }
+
+        final String mavenHome = PropertyUtils.getProperty("maven.home");
+        if(mavenHome == null) {
+            return invokeParser(Thread.currentThread().getContextClassLoader(), args);
+        }
+
+        final Path mvnLib = Paths.get(mavenHome).resolve("lib");
+        if (!Files.exists(mvnLib)) {
+            throw new IllegalStateException("Maven lib dir does not exist: " + mvnLib);
+        }
+        final URL[] urls;
+        try {
+            final List list = Files.list(mvnLib).map(p -> {
+                try {
+                    return p.toUri().toURL();
+                } catch (MalformedURLException e) {
+                    throw new IllegalStateException("Failed to translate " + p + " to URL", e);
+                }
+            }).collect(Collectors.toCollection(ArrayList::new));
+            list.add(getClassOrigin(BootstrapMavenOptions.class).toUri().toURL());
+            urls = list.toArray(new URL[list.size()]);
+        } catch (Exception e) {
+            throw new IllegalStateException("Failed to create a URL list out of " + mvnLib + " content", e);
+        }
+        final ClassLoader originalCl = Thread.currentThread().getContextClassLoader();
+        try (URLClassLoader ucl = new URLClassLoader(urls, null)) {
+            Thread.currentThread().setContextClassLoader(ucl);
+            return invokeParser(ucl, args);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to close URL classloader", e);
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalCl);
+        }
+    }
+
+    public static BootstrapMavenOptions newInstance(String cmdLine) {
+        return new BootstrapMavenOptions(parse(cmdLine));
+    }
+
+    private final Map options;
+
+    private BootstrapMavenOptions(Map options) {
+        this.options = options;
+    }
+
+    public boolean hasOption(String name) {
+        return options.containsKey(name);
+    }
+
+    public String getOptionValue(String name) {
+        final Object o = options.get(name);
+        return o == null ? null : o.toString();
+    }
+
+    public String[] getOptionValues(String name) {
+        final Object o = options.get(name);
+        return o == null ? null : (String[]) o;
+    }
+
+    public boolean isEmpty() {
+        return options.isEmpty();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map invokeParser(ClassLoader cl, String[] args) {
+        try {
+            final Class parserCls = cl.loadClass("io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptionsParser");
+            final Method parseMethod = parserCls.getMethod("parse", String[].class);
+            return (Map) parseMethod.invoke(null, (Object) args);
+        } catch (Exception e) {
+            throw new IllegalStateException("Failed to parse command line arguments " + Arrays.asList(args), e);
+        }
+    }
+
+    /**
+     * Returns the JAR or the root directory that contains the class file that is on the
+     * classpath of the context classloader
+     */
+    public static Path getClassOrigin(Class cls) throws IOException {
+        return getResourceOrigin(cls.getClassLoader(), cls.getName().replace('.', '/') + ".class");
+    }
+
+    public static Path getResourceOrigin(ClassLoader cl, final String name) throws IOException {
+        URL url = cl.getResource(name);
+        if (url == null) {
+            throw new IOException("Failed to locate the origin of " + name);
+        }
+        String classLocation = url.toExternalForm();
+        if (url.getProtocol().equals("jar")) {
+            classLocation = classLocation.substring(4, classLocation.length() - name.length() - 2);
+        } else {
+            classLocation = classLocation.substring(0, classLocation.length() - name.length());
+        }
+        return urlSpecToPath(classLocation);
+    }
+
+    private static Path urlSpecToPath(String urlSpec) throws IOException {
+        try {
+            return Paths.get(new URL(urlSpec).toURI());
+        } catch (Throwable e) {
+            throw new IOException(
+                    "Failed to create an instance of " + Path.class.getName() + " from " + urlSpec, e);
+        }
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java
new file mode 100644
index 0000000000000..9b145e2e9772b
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java
@@ -0,0 +1,58 @@
+package io.quarkus.bootstrap.resolver.maven.options;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.ParseException;
+import org.apache.maven.cli.CLIManager;
+
+public class BootstrapMavenOptionsParser {
+
+    public static Map parse(String[] args) {
+        final CommandLine cmdLine;
+        try {
+            cmdLine = new CLIManager().parse(args);
+        } catch (ParseException e) {
+            throw new IllegalStateException("Failed to parse Maven command line arguments", e);
+        }
+
+        final Map map = new HashMap<>();
+        put(cmdLine, map, CLIManager.ALTERNATE_USER_SETTINGS);
+        put(cmdLine, map, CLIManager.ALTERNATE_GLOBAL_SETTINGS);
+        put(cmdLine, map, CLIManager.ACTIVATE_PROFILES);
+
+        putBoolean(cmdLine, map, CLIManager.SUPRESS_SNAPSHOT_UPDATES);
+        putBoolean(cmdLine, map, CLIManager.UPDATE_SNAPSHOTS);
+        putBoolean(cmdLine, map, CLIManager.CHECKSUM_FAILURE_POLICY);
+        putBoolean(cmdLine, map, CLIManager.CHECKSUM_WARNING_POLICY);
+
+        return map;
+    }
+
+    private static void put(CommandLine cmdLine, Map map, char name) {
+        put(map, String.valueOf(name), cmdLine.getOptionValue(name));
+    }
+
+    private static void put(CommandLine cmdLine, Map map, String name) {
+        put(map, name, cmdLine.getOptionValue(name));
+    }
+
+    private static void putBoolean(CommandLine cmdLine, Map map, char name) {
+        if(cmdLine.hasOption(name)) {
+            map.put(String.valueOf(name), Boolean.TRUE.toString());
+        }
+    }
+
+    private static void putBoolean(CommandLine cmdLine, Map map, String name) {
+        if(cmdLine.hasOption(name)) {
+            map.put(name, Boolean.TRUE.toString());
+        }
+    }
+
+    private static void put(Map map, String name, final Object value) {
+        if(value != null) {
+            map.put(name, value);
+        }
+    }
+}
diff --git a/integration-tests/maven/pom.xml b/integration-tests/maven/pom.xml
index 2677dbefb200b..6925cffa4dd71 100644
--- a/integration-tests/maven/pom.xml
+++ b/integration-tests/maven/pom.xml
@@ -38,6 +38,10 @@
             ${project.version}
             test
         
+        
+            junit
+            junit
+        
     
 
     
@@ -99,12 +103,6 @@
                         io.quarkus
                         quarkus-platform-descriptor-json
                         ${project.version}
-                        
-                            
-                                org.apache.maven
-                                *
-                            
-                        
                     
                     
                         io.quarkus

From 647c895ce83a7a61b1e341c5cd5cf3cc404930f6 Mon Sep 17 00:00:00 2001
From: Gwenneg Lepage 
Date: Fri, 13 Dec 2019 21:58:22 +0100
Subject: [PATCH 352/602] Substitute BootLoader.loadClassOrNull(name) to fix a
 NPE with JDK 11

---
 .../graal/BootLoaderSubstitutions.java        | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java
index 46def1012a5a0..ab1a20a75257c 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java
@@ -7,17 +7,30 @@
 
 import com.oracle.svm.core.annotate.Substitute;
 import com.oracle.svm.core.annotate.TargetClass;
+import com.oracle.svm.core.hub.ClassForNameSupport;
 import com.oracle.svm.core.jdk.JDK11OrLater;
 
-/*
- * The `hasClassPath()` method substitution from this class is required to work around a NPE when `BootLoader.hasClassPath()`
- * is called. This was fixed in a GraalVM commit (https://github.com/oracle/graal/commit/68027de) which will be backported to
- * 19.3.1. We copied the entire GraalVM commit content into Quarkus for the sake of consistency.
- * See https://github.com/oracle/graal/issues/1966 for more details about the NPE.
- */
 @TargetClass(className = "jdk.internal.loader.BootLoader", onlyWith = JDK11OrLater.class)
 final class Target_jdk_internal_loader_BootLoader {
 
+    /*
+     * The following substitution is required to work around a NPE that happened when `BootLoader.loadClassOrNull(name)` was
+     * called during the Quarkus native integration tests execution. It will be directly fixed in a future GraalVM release
+     * which is undetermined for now. This substitution should be removed as soon as Quarkus depends on a GraalVM version that
+     * includes the fix.
+     */
+    @Substitute
+    private static Class loadClassOrNull(String name) {
+        return ClassForNameSupport.forNameOrNull(name, false);
+    }
+
+    /*
+     * The following substitution is required to work around a NPE that happened when `BootLoader.hasClassPath()` was called
+     * during the Quarkus native integration tests execution. It was fixed in a GraalVM commit which should be backported to
+     * 19.3.1 (https://github.com/oracle/graal/commit/68027de). This substitution should be removed as soon as Quarkus depends
+     * on a GraalVM version that includes that commit.
+     * See https://github.com/oracle/graal/issues/1966 for more details about the NPE.
+     */
     @Substitute
     private static boolean hasClassPath() {
         return true;

From 55eb4a4e0b1a9e99c22d46d6e241322f0d6edac5 Mon Sep 17 00:00:00 2001
From: Gwenneg Lepage 
Date: Sun, 15 Dec 2019 14:13:02 +0100
Subject: [PATCH 353/602] Initialize
 com.microsoft.sqlserver.jdbc.KerbAuthentication at run time

---
 .../io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java     | 5 +++++
 .../io/quarkus/kogito/deployment/KogitoAssetsProcessor.java  | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java
index d74bd95cbfe0a..87e0d44d486ac 100644
--- a/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java
+++ b/extensions/jdbc/jdbc-mssql/deployment/src/main/java/io/quarkus/jdbc/mssql/deployment/MsSQLProcessor.java
@@ -5,6 +5,7 @@
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem;
 import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
 
 public class MsSQLProcessor {
 
@@ -20,4 +21,8 @@ void nativeResources(BuildProducer resources
         nativeEnableAllCharsets.produce(new NativeImageEnableAllCharsetsBuildItem());
     }
 
+    @BuildStep
+    public RuntimeInitializedClassBuildItem runtimeInitializedClass() {
+        return new RuntimeInitializedClassBuildItem("com.microsoft.sqlserver.jdbc.KerbAuthentication");
+    }
 }
diff --git a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java
index e040ceaba9d44..4fd45d3ecf7cb 100644
--- a/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java
+++ b/extensions/kogito/deployment/src/main/java/io/quarkus/kogito/deployment/KogitoAssetsProcessor.java
@@ -137,7 +137,7 @@ public List reflectiveDMNREST() {
     }
 
     @BuildStep
-    public RuntimeInitializedClassBuildItem init() {
+    public RuntimeInitializedClassBuildItem runtimeInitializedClass() {
         return new RuntimeInitializedClassBuildItem(ClassFieldAccessorFactory.class.getName());
     }
 

From 92d772ea84eee4b929b8b0f934ecaa7cf4425b2a Mon Sep 17 00:00:00 2001
From: George Gastaldi 
Date: Sun, 15 Dec 2019 11:28:17 -0300
Subject: [PATCH 354/602] Add quarkus-kafka-streams-deployment to
 bom/deployment

---
 bom/deployment/pom.xml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index e5b0394e55ca2..730b858c93065 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -166,6 +166,11 @@
                 quarkus-kafka-client-deployment
                 ${project.version}
             
+            
+                io.quarkus
+                quarkus-kafka-streams-deployment
+                ${project.version}
+            
             
                 io.quarkus
                 quarkus-smallrye-reactive-streams-operators-deployment

From 298332cb4a285a655cce54233d8b322769fdaee0 Mon Sep 17 00:00:00 2001
From: Guillaume Smet 
Date: Sun, 15 Dec 2019 17:29:33 +0100
Subject: [PATCH 355/602] Raise a few native tests timeout

---
 ci-templates/stages.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml
index 2170d208a96a6..035df977c0bcd 100644
--- a/ci-templates/stages.yml
+++ b/ci-templates/stages.yml
@@ -200,7 +200,7 @@ stages:
         parameters:
           poolSettings: ${{parameters.poolSettings}}
           expectUseVMs: ${{parameters.expectUseVMs}}
-          timeoutInMinutes: 25
+          timeoutInMinutes: 30
           modules:
             - jpa-h2
             - jpa-mariadb
@@ -324,7 +324,7 @@ stages:
         parameters:
           poolSettings: ${{parameters.poolSettings}}
           expectUseVMs: ${{parameters.expectUseVMs}}
-          timeoutInMinutes: 25
+          timeoutInMinutes: 30
           modules:
             - jackson
             - jsonb

From 2eff3f4a7f5dba52f152c01258bcfd324a575f28 Mon Sep 17 00:00:00 2001
From: Alexey Loubyansky 
Date: Sun, 15 Dec 2019 21:03:52 +0100
Subject: [PATCH 356/602] Removed Quarkus bootstrap dependencies not used
 (directly) by the bootstrap core module

---
 independent-projects/bootstrap/core/pom.xml | 20 --------------------
 1 file changed, 20 deletions(-)

diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml
index 7d8b0a3605e51..5f8a5308cc630 100644
--- a/independent-projects/bootstrap/core/pom.xml
+++ b/independent-projects/bootstrap/core/pom.xml
@@ -14,30 +14,10 @@
     Quarkus - Bootstrap - Core
 
     
-        
-            com.google.guava
-            guava
-        
-        
-            org.apache.commons
-            commons-lang3
-        
         
             org.apache.maven
             maven-embedder
         
-        
-            jakarta.annotation
-            jakarta.annotation-api
-        
-        
-            jakarta.inject
-            jakarta.inject-api
-        
-        
-            jakarta.enterprise
-            jakarta.enterprise.cdi-api
-        
         
             org.apache.maven
             maven-settings-builder

From 4dd9749732cfe83dfcf42b80d6a92db08615269d Mon Sep 17 00:00:00 2001
From: Stuart Douglas 
Date: Mon, 16 Dec 2019 10:11:52 +1100
Subject: [PATCH 357/602] Improve error message if config properties conflict

---
 .../deployment/configuration/matching/PatternMapBuilder.java  | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java
index d19d0aa2b8e3e..d614f7a49caae 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java
@@ -67,7 +67,9 @@ private static void addMember(ConfigPatternMap patternMap, ClassDefin
             Container matched = patternMap.getMatched();
             if (matched != null) {
                 throw new IllegalArgumentException(
-                        "Multiple matching properties for name \"" + matched.getPropertyName() + "\"");
+                        "Multiple matching properties for name \"" + matched.getPropertyName()
+                                + "\" property was matched by both " + container.findField() + " and " + matched.findField()
+                                + ". This is likely because you have an incompatible combination of extensions that both define the same properties (e.g. including both reactive and blocking database extensions)");
             }
             patternMap.setMatched(container);
         } else if (member instanceof ClassDefinition.MapMember) {

From 08f79a18fb72e7a06ad92d711b7ec02a375dcb32 Mon Sep 17 00:00:00 2001
From: George Gastaldi 
Date: Sun, 15 Dec 2019 20:55:39 -0300
Subject: [PATCH 358/602] Move Launching JVM log to debug level

Fixes #6183
---
 .../src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java     | 4 +++-
 devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java    | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
index 2c58b65a06aab..c4314b90dcd58 100644
--- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
+++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java
@@ -351,7 +351,9 @@ public void startDev() {
                     .redirectErrorStream(true)
                     .redirectInput(ProcessBuilder.Redirect.INHERIT)
                     .directory(getWorkingDir());
-            System.out.printf("Launching JVM with command line: %s%n", pb.command().stream().collect(joining(" ")));
+            if (getLogger().isDebugEnabled()) {
+                getLogger().debug("Launching JVM with command line: {}", pb.command().stream().collect(joining(" ")));
+            }
             Process p = pb.start();
             Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                 @Override
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
index f40b1b722d272..4243d6c1e7315 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
@@ -691,7 +691,9 @@ public Set getPomFiles() {
 
         public void run() throws Exception {
             // Display the launch command line in dev mode
-            getLog().info("Launching JVM with command line: " + args.stream().collect(joining(" ")));
+            if (getLog().isDebugEnabled()) {
+                getLog().debug("Launching JVM with command line: " + args.stream().collect(joining(" ")));
+            }
             process = new ProcessBuilder(args)
                     .inheritIO()
                     .directory(workingDir)

From fc79112325a4bbfb63194e9cfa2309b884fac92b Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Mon, 16 Dec 2019 09:07:00 +0200
Subject: [PATCH 359/602] Fix erroneous Kubernetes file name in docs

Fixes: #6187
---
 docs/src/main/asciidoc/kubernetes.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/src/main/asciidoc/kubernetes.adoc b/docs/src/main/asciidoc/kubernetes.adoc
index 8fb228c114447..988b54df1912e 100644
--- a/docs/src/main/asciidoc/kubernetes.adoc
+++ b/docs/src/main/asciidoc/kubernetes.adoc
@@ -63,7 +63,7 @@ quarkus.application.name=test-quarkus-app # this is also optional and defaults t
 ----
 
 and following the execution of `./mvnw package` you will notice amongst the other files that are created, two files named
-`kubernetes.json` and `kubernetes.yaml` in the `target/kubernetes/` directory.
+`kubernetes.json` and `kubernetes.yml` in the `target/kubernetes/` directory.
 
 If you look at either file you will see that it contains both a Kubernetes `Deployment` and a `Service`.
 

From 2e2193f937f4b648397edfa3f48f42c638a31e50 Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Fri, 13 Dec 2019 16:22:42 +0200
Subject: [PATCH 360/602] Introduce handling of more advanced generics cases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Stéphane Épardaud 
---
 .../quarkus/deployment/util/JandexUtil.java   | 104 +++++++++++++-----
 .../deployment/util/JandexUtilTest.java       |  83 ++++++++++++++
 2 files changed, 159 insertions(+), 28 deletions(-)

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
index aa6192c89a905..ae4fd3fe1aa42 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/util/JandexUtil.java
@@ -6,6 +6,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import org.jboss.jandex.ArrayType;
 import org.jboss.jandex.ClassInfo;
 import org.jboss.jandex.ClassType;
 import org.jboss.jandex.DotName;
@@ -63,46 +64,80 @@ public static List resolveTypeParameters(DotName input, DotName target, In
         if (recursiveMatchResult == null) {
             return Collections.emptyList();
         }
+        final List result = resolveTypeParameters(recursiveMatchResult);
+
+        if (result.size() != recursiveMatchResult.argumentsOfMatch.size()) {
+            throw new IllegalStateException("Unable to properly match generic types");
+        }
+
+        return result;
+    }
+
+    private static List resolveTypeParameters(RecursiveMatchResult recursiveMatchResult) {
         final List result = new ArrayList<>();
         for (int i = 0; i < recursiveMatchResult.argumentsOfMatch.size(); i++) {
             final Type argument = recursiveMatchResult.argumentsOfMatch.get(i);
-            if (isDirectlyHandledType(argument)) {
+            if (argument instanceof ClassType) {
                 result.add(argument);
-            } else if (argument instanceof TypeVariable) {
-
-                String unmatchedParameter = argument.asTypeVariable().identifier();
-
-                for (RecursiveMatchLevel recursiveMatchLevel : recursiveMatchResult.recursiveMatchLevels) {
-                    Type matchingCapturedType = null;
-                    for (int j = 0; j < recursiveMatchLevel.definitions.size(); j++) {
-                        final Type definition = recursiveMatchLevel.definitions.get(j);
-                        if ((definition instanceof TypeVariable)
-                                && unmatchedParameter.equals(definition.asTypeVariable().identifier())) {
-                            matchingCapturedType = recursiveMatchLevel.captures.get(j);
-                            break; // out of the definitions loop
-                        }
-                    }
-                    // at this point their MUST be a match, if there isn't we have made some mistake in the implementation
-                    if (matchingCapturedType == null) {
-                        throw new IllegalStateException("Error retrieving generic types");
+            } else if (argument instanceof ParameterizedType) {
+                ParameterizedType argumentParameterizedType = argument.asParameterizedType();
+                List resolvedTypes = new ArrayList<>(argumentParameterizedType.arguments().size());
+                for (Type argType : argumentParameterizedType.arguments()) {
+                    if (argType instanceof TypeVariable) {
+                        resolvedTypes.add(findTypeFromTypeVariable(recursiveMatchResult, argType.asTypeVariable()));
+                    } else {
+                        resolvedTypes.add(argType);
                     }
-                    if (isDirectlyHandledType(matchingCapturedType)) {
-                        result.add(matchingCapturedType);
-                        break; // out of level loop
-                    }
-                    if (matchingCapturedType instanceof TypeVariable) {
-                        // continue the search in the lower levels using the new name
-                        unmatchedParameter = matchingCapturedType.asTypeVariable().identifier();
+                }
+                result.add(ParameterizedType.create(argumentParameterizedType.name(), resolvedTypes.toArray(new Type[0]),
+                        argumentParameterizedType.owner()));
+            } else if (argument instanceof TypeVariable) {
+                Type typeFromTypeVariable = findTypeFromTypeVariable(recursiveMatchResult, argument.asTypeVariable());
+                if (typeFromTypeVariable != null) {
+                    result.add(typeFromTypeVariable);
+                }
+            } else if (argument instanceof ArrayType) {
+                ArrayType argumentAsArrayType = argument.asArrayType();
+                Type componentType = argumentAsArrayType.component();
+                if (componentType instanceof TypeVariable) { // should always be the case
+                    Type typeFromTypeVariable = findTypeFromTypeVariable(recursiveMatchResult, componentType.asTypeVariable());
+                    if (typeFromTypeVariable != null) {
+                        result.add(ArrayType.create(typeFromTypeVariable, argumentAsArrayType.dimensions()));
                     }
                 }
             }
         }
+        return result;
+    }
 
-        if (result.size() != recursiveMatchResult.argumentsOfMatch.size()) {
-            throw new IllegalStateException("Unable to properly match generic types");
+    private static Type findTypeFromTypeVariable(RecursiveMatchResult recursiveMatchResult, TypeVariable typeVariable) {
+        String unmatchedParameter = typeVariable.identifier();
+
+        for (RecursiveMatchLevel recursiveMatchLevel : recursiveMatchResult.recursiveMatchLevels) {
+            Type matchingCapturedType = null;
+            for (int j = 0; j < recursiveMatchLevel.definitions.size(); j++) {
+                final Type definition = recursiveMatchLevel.definitions.get(j);
+                if ((definition instanceof TypeVariable)
+                        && unmatchedParameter.equals(definition.asTypeVariable().identifier())) {
+                    matchingCapturedType = recursiveMatchLevel.captures.get(j);
+                    break; // out of the definitions loop
+                }
+            }
+            // at this point their MUST be a match, if there isn't we have made some mistake in the implementation
+            if (matchingCapturedType == null) {
+                throw new IllegalStateException("Error retrieving generic types");
+            }
+            if (isDirectlyHandledType(matchingCapturedType)) {
+                // search is over
+                return matchingCapturedType;
+            }
+            if (matchingCapturedType instanceof TypeVariable) {
+                // continue the search in the lower levels using the new name
+                unmatchedParameter = matchingCapturedType.asTypeVariable().identifier();
+            }
         }
 
-        return result;
+        return null;
     }
 
     private static boolean isDirectlyHandledType(Type matchingCapturedType) {
@@ -172,6 +207,19 @@ private static List addArgumentIfNeeded(Type type, IndexVie
             final RecursiveMatchLevel recursiveMatchLevel = new RecursiveMatchLevel(classInfo.typeParameters(),
                     type.asParameterizedType().arguments());
             newVisitedTypes.add(0, recursiveMatchLevel);
+        } else if (type instanceof ClassType) {
+            // we need to check if the class contains any bounds
+            final ClassInfo classInfo = index.getClassByName(type.name());
+            if ((classInfo != null) && !classInfo.typeParameters().isEmpty()) {
+                // in this case we just use the first bound as the capture type
+                // TODO do we need something more sophisticated than this?
+                List captures = new ArrayList<>(classInfo.typeParameters().size());
+                for (TypeVariable typeParameter : classInfo.typeParameters()) {
+                    captures.add(typeParameter.bounds().get(0));
+                }
+                final RecursiveMatchLevel recursiveMatchLevel = new RecursiveMatchLevel(classInfo.typeParameters(), captures);
+                newVisitedTypes.add(0, recursiveMatchLevel);
+            }
         }
         return newVisitedTypes;
     }
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
index dd164739ffdec..4bb590cbbacb1 100644
--- a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
@@ -167,6 +167,35 @@ public void testExtendsAbstractClass() {
                 abstractSingle, index)).extracting("name").containsOnly(INTEGER);
     }
 
+    @Test
+    public void testArrayGenerics() {
+        final Index index = index(Repo.class, ArrayRepo.class, GenericArrayRepo.class);
+        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ArrayRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
+                index)).extracting("name").containsOnly(DotName.createSimple(Integer[].class.getName()));
+    }
+
+    @Test
+    public void testCompositeGenerics() {
+        final Index index = index(Repo.class, Repo2.class, CompositeRepo.class, CompositeRepo2.class, GenericCompositeRepo.class, GenericCompositeRepo2.class);
+        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
+                index)).hasOnlyOneElementSatisfying(t -> {
+            assertThat(t.toString()).isEqualTo(Repo.class.getName() + "");
+        });
+        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo2.class.getName()), DotName.createSimple(Repo2.class.getName()),
+                index)).hasOnlyOneElementSatisfying(t -> {
+            assertThat(t.toString()).isEqualTo(Repo.class.getName() + "");
+        });
+    }
+
+    @Test
+    public void testErasedGenerics() {
+        final Index index = index(Repo.class, BoundedRepo.class, ErasedRepo1.class, MultiBoundedRepo.class, ErasedRepo2.class, A.class);
+        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo1.class.getName()), DotName.createSimple(Repo.class.getName()),
+                index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
+        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo2.class.getName()), DotName.createSimple(Repo.class.getName()),
+                index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
+    }
+
     public interface Single {
     }
 
@@ -246,6 +275,60 @@ public static class ExtendsMultipleT1Impl implements MultipleT1 {
     public static class SingleFromInterfaceAndSuperClass extends SingleImpl implements Single {
     }
 
+    public interface Repo {
+    }
+
+    public interface Repo2 {
+    }
+
+    public static class DirectRepo implements Repo {
+    }
+
+    public static class IndirectRepo extends DirectRepo {
+    }
+
+    public static class GenericRepo implements Repo {
+    }
+
+    public static class IndirectGenericRepo extends GenericRepo {
+    }
+
+    public static class GenericArrayRepo implements Repo {
+    }
+
+    public static class ArrayRepo extends GenericArrayRepo {
+    }
+
+    public static class GenericCompositeRepo implements Repo> {
+    }
+
+    public static class GenericCompositeRepo2 implements Repo2> {
+    }
+
+    public static class CompositeRepo extends GenericCompositeRepo {
+    }
+
+    public static class CompositeRepo2 extends GenericCompositeRepo2 {
+    }
+
+    public static class BoundedRepo implements Repo {
+    }
+
+    public static class ErasedRepo1 extends BoundedRepo {
+    }
+
+    public interface A {
+    }
+
+    public interface B {
+    }
+
+    public static class MultiBoundedRepo implements Repo {
+    }
+
+    public static class ErasedRepo2 extends MultiBoundedRepo {
+    }
+
     private static Index index(Class... classes) {
         Indexer indexer = new Indexer();
         for (Class clazz : classes) {

From 16b12fd887d4eda11acf5f180909bc1fae8633f4 Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Fri, 13 Dec 2019 16:43:44 +0200
Subject: [PATCH 361/602] Make JandexUtilTest more readable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Stéphane Épardaud 
---
 .../deployment/util/JandexUtilTest.java       | 113 ++++++++----------
 1 file changed, 52 insertions(+), 61 deletions(-)

diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
index 4bb590cbbacb1..3a3294c097fae 100644
--- a/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/JandexUtilTest.java
@@ -50,150 +50,113 @@ public void testAbstractSingle() {
     @Test
     public void testSimplestImpl() {
         final Index index = index(Single.class, SingleImpl.class);
-        final DotName impl = DotName.createSimple(SingleImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(STRING);
+        checkRepoArg(index, SingleImpl.class, Single.class, String.class);
     }
 
     @Test
     public void testSimplestImplWithBound() {
         final Index index = index(SingleWithBound.class, SingleWithBoundImpl.class);
-        final DotName impl = DotName.createSimple(SingleWithBoundImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl,
-                DotName.createSimple(SingleWithBound.class.getName()), index);
-        assertThat(result).extracting("name").containsOnly(DotName.createSimple(List.class.getName()));
+        checkRepoArg(index, SingleWithBoundImpl.class, SingleWithBound.class, List.class);
     }
 
     @Test
     public void testSimpleImplMultipleParams() {
         final Index index = index(Multiple.class, MultipleImpl.class);
-        final DotName impl = DotName.createSimple(MultipleImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index);
-        assertThat(result).extracting("name").containsExactly(INTEGER, STRING);
+        checkRepoArg(index, MultipleImpl.class, Multiple.class, Integer.class, String.class);
     }
 
     @Test
     public void testInverseParameterNames() {
         final Index index = index(Multiple.class, InverseMultiple.class, InverseMultipleImpl.class);
-        final DotName impl = DotName.createSimple(InverseMultipleImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index);
-        assertThat(result).extracting("name").containsExactly(DOUBLE, INTEGER);
+        checkRepoArg(index, InverseMultipleImpl.class, Multiple.class, Double.class, Integer.class);
     }
 
     @Test
     public void testImplExtendsSimplestImplementation() {
         final Index index = index(Single.class, SingleImpl.class, SingleImplImpl.class);
-        final DotName impl = DotName.createSimple(SingleImplImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(STRING);
+        checkRepoArg(index, SingleImplImpl.class, Single.class, String.class);
     }
 
     @Test
     public void testImplementationOfInterfaceThatExtendsSimpleWithoutParam() {
         final Index index = index(Single.class, ExtendsSimpleNoParam.class, ExtendsSimpleNoParamImpl.class);
-        final DotName impl = DotName.createSimple(ExtendsSimpleNoParamImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(DOUBLE);
+        checkRepoArg(index, ExtendsSimpleNoParamImpl.class, Single.class, Double.class);
     }
 
     @Test
     public void testImplExtendsImplOfInterfaceThatExtendsSimpleWithoutParams() {
         final Index index = index(Single.class, ExtendsSimpleNoParam.class, ExtendsSimpleNoParamImpl.class,
                 ExtendsSimpleNoParamImplImpl.class);
-        final DotName impl = DotName.createSimple(ExtendsSimpleNoParamImplImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(DOUBLE);
+        checkRepoArg(index, ExtendsSimpleNoParamImplImpl.class, Single.class, Double.class);
     }
 
     @Test
     public void testImplOfInterfaceThatExtendsSimpleWithParam() {
         final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsSimpleWithParamImpl.class);
-        final DotName impl = DotName.createSimple(ExtendsSimpleWithParamImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(INTEGER);
+        checkRepoArg(index, ExtendsSimpleWithParamImpl.class, Single.class, Integer.class);
     }
 
     @Test
     public void testImplOfInterfaceThatExtendsSimpleWithParamInMultipleLevels() {
         final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsExtendsSimpleWithParam.class,
                 ExtendsExtendsSimpleWithParamImpl.class);
-        final DotName impl = DotName.createSimple(ExtendsExtendsSimpleWithParamImpl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(DOUBLE);
+        checkRepoArg(index, ExtendsExtendsSimpleWithParamImpl.class, Single.class, Double.class);
     }
 
     @Test
     public void testImplOfInterfaceThatExtendsSimpleWithGenericParamInMultipleLevels() {
         final Index index = index(Single.class, ExtendsSimpleWithParam.class, ExtendsExtendsSimpleWithParam.class,
                 ExtendsExtendsSimpleGenericParam.class);
-        final DotName impl = DotName.createSimple(ExtendsExtendsSimpleGenericParam.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(DotName.createSimple(Map.class.getName()));
+        checkRepoArg(index, ExtendsExtendsSimpleGenericParam.class, Single.class, Map.class);
     }
 
     @Test
     public void testImplOfMultipleWithParamsInDifferentLevels() {
         final Index index = index(Multiple.class, MultipleT1.class, ExtendsMultipleT1Impl.class);
-        final DotName impl = DotName.createSimple(ExtendsMultipleT1Impl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index);
-        assertThat(result).extracting("name").containsOnly(INTEGER, STRING);
+        checkRepoArg(index, ExtendsMultipleT1Impl.class, Multiple.class, Integer.class, String.class);
     }
 
     @Test
     public void testImplOfAbstractMultipleWithParamsInDifferentLevels() {
         final Index index = index(Multiple.class, MultipleT1.class, AbstractMultipleT1Impl.class,
                 ExtendsAbstractMultipleT1Impl.class);
-        final DotName impl = DotName.createSimple(ExtendsAbstractMultipleT1Impl.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, MULTIPLE, index);
-        assertThat(result).extracting("name").containsOnly(INTEGER, STRING);
+        checkRepoArg(index, ExtendsAbstractMultipleT1Impl.class, Multiple.class, Integer.class, String.class);
     }
 
     @Test
     public void testMultiplePathsToSingle() {
         final Index index = index(Single.class, SingleImpl.class, SingleFromInterfaceAndSuperClass.class);
-        final DotName impl = DotName.createSimple(SingleFromInterfaceAndSuperClass.class.getName());
-        final List result = JandexUtil.resolveTypeParameters(impl, SIMPLE, index);
-        assertThat(result).extracting("name").containsOnly(STRING);
+        checkRepoArg(index, SingleFromInterfaceAndSuperClass.class, Single.class, String.class);
     }
 
     @Test
     public void testExtendsAbstractClass() {
-        final DotName abstractSingle = DotName.createSimple(AbstractSingle.class.getName());
         final Index index = index(Single.class, AbstractSingle.class, AbstractSingleImpl.class,
                 ExtendsAbstractSingleImpl.class);
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(AbstractSingleImpl.class.getName()), abstractSingle,
-                index)).extracting("name").containsOnly(INTEGER);
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ExtendsAbstractSingleImpl.class.getName()),
-                abstractSingle, index)).extracting("name").containsOnly(INTEGER);
+        checkRepoArg(index, AbstractSingleImpl.class, AbstractSingle.class, Integer.class);
+        checkRepoArg(index, ExtendsAbstractSingleImpl.class, AbstractSingle.class, Integer.class);
     }
 
     @Test
     public void testArrayGenerics() {
         final Index index = index(Repo.class, ArrayRepo.class, GenericArrayRepo.class);
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ArrayRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
-                index)).extracting("name").containsOnly(DotName.createSimple(Integer[].class.getName()));
+        checkRepoArg(index, ArrayRepo.class, Repo.class, Integer[].class);
     }
 
     @Test
     public void testCompositeGenerics() {
-        final Index index = index(Repo.class, Repo2.class, CompositeRepo.class, CompositeRepo2.class, GenericCompositeRepo.class, GenericCompositeRepo2.class);
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo.class.getName()), DotName.createSimple(Repo.class.getName()),
-                index)).hasOnlyOneElementSatisfying(t -> {
-            assertThat(t.toString()).isEqualTo(Repo.class.getName() + "");
-        });
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(CompositeRepo2.class.getName()), DotName.createSimple(Repo2.class.getName()),
-                index)).hasOnlyOneElementSatisfying(t -> {
-            assertThat(t.toString()).isEqualTo(Repo.class.getName() + "");
-        });
+        final Index index = index(Repo.class, Repo2.class, CompositeRepo.class, CompositeRepo2.class,
+                GenericCompositeRepo.class, GenericCompositeRepo2.class);
+        checkRepoArg(index, CompositeRepo.class, Repo.class, Repo.class.getName() + "");
+        checkRepoArg(index, CompositeRepo2.class, Repo2.class, Repo.class.getName() + "");
     }
 
     @Test
     public void testErasedGenerics() {
-        final Index index = index(Repo.class, BoundedRepo.class, ErasedRepo1.class, MultiBoundedRepo.class, ErasedRepo2.class, A.class);
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo1.class.getName()), DotName.createSimple(Repo.class.getName()),
-                index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
-        assertThat(JandexUtil.resolveTypeParameters(DotName.createSimple(ErasedRepo2.class.getName()), DotName.createSimple(Repo.class.getName()),
-                index)).extracting("name").containsOnly(DotName.createSimple(A.class.getName()));
+        final Index index = index(Repo.class, BoundedRepo.class, ErasedRepo1.class, MultiBoundedRepo.class, ErasedRepo2.class,
+                A.class);
+        checkRepoArg(index, ErasedRepo1.class, Repo.class, A.class);
+        checkRepoArg(index, ErasedRepo2.class, Repo.class, A.class);
     }
 
     public interface Single {
@@ -344,4 +307,32 @@ private static Index index(Class... classes) {
         return indexer.complete();
     }
 
+    private void checkRepoArg(Index index, Class baseClass, Class soughtClass, Class expectedArg) {
+        List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass),
+                index);
+        assertThat(args).extracting("name").containsOnly(name(expectedArg));
+    }
+
+    private void checkRepoArg(Index index, Class baseClass, Class soughtClass, Class... expectedArgs) {
+        List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass),
+                index);
+        Object[] expectedArgNames = new Object[expectedArgs.length];
+        for (int i = 0; i < expectedArgs.length; i++) {
+            expectedArgNames[i] = name(expectedArgs[i]);
+        }
+        assertThat(args).extracting("name").containsOnly(expectedArgNames);
+    }
+
+    private void checkRepoArg(Index index, Class baseClass, Class soughtClass, String expectedArg) {
+        List args = JandexUtil.resolveTypeParameters(name(baseClass), name(soughtClass),
+                index);
+        assertThat(args).hasOnlyOneElementSatisfying(t -> {
+            assertThat(t.toString()).isEqualTo(expectedArg);
+        });
+    }
+
+    private static DotName name(Class klass) {
+        return DotName.createSimple(klass.getName());
+    }
+
 }

From 3c98dca9161f921a42c38e1a668b583467bc7757 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= 
Date: Mon, 16 Dec 2019 10:36:57 +0100
Subject: [PATCH 362/602] Fix #5885: generics break panache repo enhancer

Do not enhance generic/abstract repos
Use proper generics resolution when looking up the repository type
arguments
---
 .../deployment/PanacheResourceProcessor.java     | 13 +++++++++++++
 .../deployment/PanacheRepositoryEnhancer.java    | 16 +++++++---------
 .../it/panache/Bug5885AbstractRepository.java    |  7 +++++++
 .../it/panache/Bug5885EntityRepository.java      |  7 +++++++
 .../java/io/quarkus/it/panache/TestEndpoint.java | 11 +++++++++++
 .../it/panache/PanacheFunctionalityTest.java     |  5 +++++
 6 files changed, 50 insertions(+), 9 deletions(-)
 create mode 100644 integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885AbstractRepository.java
 create mode 100644 integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885EntityRepository.java

diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
index bbfb8146778fd..1e4a9c2844cfb 100644
--- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
+++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
@@ -1,5 +1,6 @@
 package io.quarkus.hibernate.orm.panache.deployment;
 
+import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -82,9 +83,13 @@ void build(CombinedIndexBuildItem index,
             // Skip PanacheRepository
             if (classInfo.name().equals(DOTNAME_PANACHE_REPOSITORY))
                 continue;
+            if (skipRepository(classInfo))
+                continue;
             daoClasses.add(classInfo.name().toString());
         }
         for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY)) {
+            if (skipRepository(classInfo))
+                continue;
             daoClasses.add(classInfo.name().toString());
         }
         for (String daoClass : daoClasses) {
@@ -95,6 +100,7 @@ void build(CombinedIndexBuildItem index,
         Set modelClasses = new HashSet<>();
         // Note that we do this in two passes because for some reason Jandex does not give us subtypes
         // of PanacheEntity if we ask for subtypes of PanacheEntityBase
+        // NOTE: we don't skip abstract/generic entities because they still need accessors
         for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(DOTNAME_PANACHE_ENTITY_BASE)) {
             // FIXME: should we really skip PanacheEntity or all MappedSuperClass?
             if (classInfo.name().equals(DOTNAME_PANACHE_ENTITY))
@@ -122,4 +128,11 @@ void build(CombinedIndexBuildItem index,
         }
     }
 
+    private boolean skipRepository(ClassInfo classInfo) {
+        // we don't want to add methods to abstract/generic entities/repositories: they get added to bottom types
+        // which can't be either
+        return Modifier.isAbstract(classInfo.flags())
+                || !classInfo.typeParameters().isEmpty();
+    }
+
 }
diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
index 4c8a54154a559..af1bc0dea32cd 100644
--- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
+++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
@@ -90,15 +90,13 @@ private String recursivelyFindEntityTypeFromClass(DotName clazz, DotName reposit
                 return null;
             }
 
-            final ClassInfo classByName = indexView.getClassByName(clazz);
-            for (org.jboss.jandex.Type type : classByName.interfaceTypes()) {
-                if (type.name().equals(repositoryDotName)) {
-                    org.jboss.jandex.Type entityType = type.asParameterizedType().arguments().get(0);
-                    return entityType.name().toString().replace('.', '/');
-                }
-            }
-
-            return recursivelyFindEntityTypeFromClass(classByName.superName(), repositoryDotName);
+            List typeParameters = io.quarkus.deployment.util.JandexUtil
+                    .resolveTypeParameters(clazz, repositoryDotName, indexView);
+            if (typeParameters.isEmpty())
+                throw new IllegalStateException(
+                        "Failed to find supertype " + repositoryDotName + " from entity class " + clazz);
+            org.jboss.jandex.Type entityType = typeParameters.get(0);
+            return entityType.name().toString().replace('.', '/');
         }
 
         @Override
diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885AbstractRepository.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885AbstractRepository.java
new file mode 100644
index 0000000000000..0bd118580bf55
--- /dev/null
+++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885AbstractRepository.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.panache;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepository;
+
+public abstract class Bug5885AbstractRepository implements PanacheRepository {
+
+}
diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885EntityRepository.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885EntityRepository.java
new file mode 100644
index 0000000000000..be9d686c6adc0
--- /dev/null
+++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Bug5885EntityRepository.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.panache;
+
+import javax.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class Bug5885EntityRepository extends Bug5885AbstractRepository {
+}
diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java
index 9aa806f7266f0..0a4e1d2bf3ac2 100644
--- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java
+++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java
@@ -987,4 +987,15 @@ public String testBug5274() {
         bug5274EntityRepository.count();
         return "OK";
     }
+
+    @Inject
+    Bug5885EntityRepository bug5885EntityRepository;
+
+    @GET
+    @Path("5885")
+    @Transactional
+    public String testBug5885() {
+        bug5885EntityRepository.findById(1L);
+        return "OK";
+    }
 }
diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java
index 4c5b29fdfadd7..8ee58859b2615 100644
--- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java
+++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java
@@ -54,6 +54,11 @@ public void testBug5274() {
         RestAssured.when().get("/test/5274").then().body(is("OK"));
     }
 
+    @Test
+    public void testBug5885() {
+        RestAssured.when().get("/test/5885").then().body(is("OK"));
+    }
+
     /**
      * This test is disabled in native mode as there is no interaction with the quarkus integration test endpoint.
      */

From e920186c9157e3898e2d24474a87153075fe4bee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= 
Date: Mon, 16 Dec 2019 12:22:22 +0100
Subject: [PATCH 363/602] Moved skipRepository up

---
 .../deployment/PanacheResourceProcessor.java       | 14 +++-----------
 .../deployment/PanacheResourceProcessor.java       |  5 +++++
 .../deployment/PanacheRepositoryEnhancer.java      |  8 ++++++++
 3 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
index 1e4a9c2844cfb..82ce06315c2b5 100644
--- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
+++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java
@@ -1,6 +1,5 @@
 package io.quarkus.hibernate.orm.panache.deployment;
 
-import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -31,6 +30,7 @@
 import io.quarkus.panache.common.deployment.EntityModel;
 import io.quarkus.panache.common.deployment.MetamodelInfo;
 import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer;
+import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer;
 
 public final class PanacheResourceProcessor {
 
@@ -83,12 +83,12 @@ void build(CombinedIndexBuildItem index,
             // Skip PanacheRepository
             if (classInfo.name().equals(DOTNAME_PANACHE_REPOSITORY))
                 continue;
-            if (skipRepository(classInfo))
+            if (PanacheRepositoryEnhancer.skipRepository(classInfo))
                 continue;
             daoClasses.add(classInfo.name().toString());
         }
         for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY)) {
-            if (skipRepository(classInfo))
+            if (PanacheRepositoryEnhancer.skipRepository(classInfo))
                 continue;
             daoClasses.add(classInfo.name().toString());
         }
@@ -127,12 +127,4 @@ void build(CombinedIndexBuildItem index,
             }
         }
     }
-
-    private boolean skipRepository(ClassInfo classInfo) {
-        // we don't want to add methods to abstract/generic entities/repositories: they get added to bottom types
-        // which can't be either
-        return Modifier.isAbstract(classInfo.flags())
-                || !classInfo.typeParameters().isEmpty();
-    }
-
 }
diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
index d7806dbfd25dc..616e81b9bc04e 100644
--- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
+++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
@@ -28,6 +28,7 @@
 import io.quarkus.mongodb.panache.PanacheMongoRepository;
 import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase;
 import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer;
+import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer;
 
 public class PanacheResourceProcessor {
     static final DotName DOTNAME_PANACHE_REPOSITORY_BASE = DotName.createSimple(PanacheMongoRepositoryBase.class.getName());
@@ -88,9 +89,13 @@ void build(CombinedIndexBuildItem index,
             // Skip PanacheRepository
             if (classInfo.name().equals(DOTNAME_PANACHE_REPOSITORY))
                 continue;
+            if (PanacheRepositoryEnhancer.skipRepository(classInfo))
+                continue;
             daoClasses.add(classInfo.name().toString());
         }
         for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY)) {
+            if (PanacheRepositoryEnhancer.skipRepository(classInfo))
+                continue;
             daoClasses.add(classInfo.name().toString());
         }
         for (String daoClass : daoClasses) {
diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
index af1bc0dea32cd..4a5fb5f99b54a 100644
--- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
+++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java
@@ -1,5 +1,6 @@
 package io.quarkus.panache.common.deployment;
 
+import java.lang.reflect.Modifier;
 import java.util.List;
 import java.util.function.BiFunction;
 
@@ -165,4 +166,11 @@ private void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeE
             mv.visitEnd();
         }
     }
+
+    public static boolean skipRepository(ClassInfo classInfo) {
+        // we don't want to add methods to abstract/generic entities/repositories: they get added to bottom types
+        // which can't be either
+        return Modifier.isAbstract(classInfo.flags())
+                || !classInfo.typeParameters().isEmpty();
+    }
 }

From ebded38a773a35fe17345223412b2365589133d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= 
Date: Mon, 16 Dec 2019 12:22:46 +0100
Subject: [PATCH 364/602] Test for mongo version of #5885 bug

---
 .../panache/bugs/Bug5885AbstractRepository.java        |  7 +++++++
 .../mongodb/panache/bugs/Bug5885EntityRepository.java  |  9 +++++++++
 .../quarkus/it/mongodb/panache/bugs/BugResource.java   | 10 ++++++++++
 .../it/mongodb/panache/MongodbPanacheResourceTest.java |  4 ++++
 4 files changed, 30 insertions(+)
 create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.java
 create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.java

diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.java
new file mode 100644
index 0000000000000..4b4549792bac3
--- /dev/null
+++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.mongodb.panache.bugs;
+
+import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase;
+
+public abstract class Bug5885AbstractRepository implements PanacheMongoRepositoryBase {
+
+}
diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.java
new file mode 100644
index 0000000000000..bb9642055fa40
--- /dev/null
+++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.java
@@ -0,0 +1,9 @@
+package io.quarkus.it.mongodb.panache.bugs;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import io.quarkus.it.mongodb.panache.person.PersonEntity;
+
+@ApplicationScoped
+public class Bug5885EntityRepository extends Bug5885AbstractRepository {
+}
diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/BugResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/BugResource.java
index 788c811915a7d..bc39e8c657cdb 100644
--- a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/BugResource.java
+++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/bugs/BugResource.java
@@ -21,4 +21,14 @@ public String testBug5274() {
         bug5274EntityRepository.count();
         return "OK";
     }
+
+    @Inject
+    Bug5885EntityRepository bug5885EntityRepository;
+
+    @GET
+    @Path("5885")
+    public String testBug5885() {
+        bug5885EntityRepository.findById(1L);
+        return "OK";
+    }
 }
diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java
index ff899be5d54a2..2c75db6d87471 100644
--- a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java
+++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java
@@ -334,4 +334,8 @@ public void testBug5274() {
         get("/bugs/5274").then().body(is("OK"));
     }
 
+    @Test
+    public void testBug5885() {
+        get("/bugs/5885").then().body(is("OK"));
+    }
 }

From 60882c447eafaff209d14e17aa55e98fb171bd22 Mon Sep 17 00:00:00 2001
From: "David M. Lloyd" 
Date: Fri, 13 Dec 2019 14:22:18 -0600
Subject: [PATCH 365/602] Let the delegate handlers decide to autoflush

---
 .../java/io/quarkus/runtime/logging/LoggingSetupRecorder.java   | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
index 77fae3133cab5..422ecc7c6590d 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java
@@ -117,6 +117,7 @@ public void initializeLogging(LogConfig config, final List
Date: Fri, 13 Dec 2019 15:06:06 +0100
Subject: [PATCH 366/602] Move Elytron Security substitutions to their own
 module

---
 bom/deployment/pom.xml                        |  5 ++
 bom/runtime/pom.xml                           | 10 ++++
 .../deployment/pom.xml                        | 39 +++++++++++++++
 extensions/elytron-security-common/pom.xml    | 20 ++++++++
 .../elytron-security-common/runtime/pom.xml   | 50 +++++++++++++++++++
 ...ty_password_interfaces_BCryptPassword.java |  0
 ...rd_interfaces_BSDUnixDESCryptPassword.java |  0
 ...ity_password_interfaces_ClearPassword.java |  0
 ..._password_interfaces_RawClearPassword.java |  0
 ..._security_x500_util_X500PrincipalUtil.java |  0
 .../resources/META-INF/quarkus-extension.yaml |  9 ++++
 .../elytron-security/deployment/pom.xml       |  4 ++
 extensions/elytron-security/runtime/pom.xml   |  4 ++
 .../infinispan-client/deployment/pom.xml      |  4 ++
 extensions/infinispan-client/runtime/pom.xml  |  4 ++
 extensions/pom.xml                            |  1 +
 16 files changed, 150 insertions(+)
 create mode 100644 extensions/elytron-security-common/deployment/pom.xml
 create mode 100644 extensions/elytron-security-common/pom.xml
 create mode 100644 extensions/elytron-security-common/runtime/pom.xml
 rename extensions/{elytron-security/runtime/src/main/java/io/quarkus/elytron/security => elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common}/runtime/graal/Target_org_wildfly_security_password_interfaces_BCryptPassword.java (100%)
 rename extensions/{elytron-security/runtime/src/main/java/io/quarkus/elytron/security => elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common}/runtime/graal/Target_org_wildfly_security_password_interfaces_BSDUnixDESCryptPassword.java (100%)
 rename extensions/{elytron-security/runtime/src/main/java/io/quarkus/elytron/security => elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common}/runtime/graal/Target_org_wildfly_security_password_interfaces_ClearPassword.java (100%)
 rename extensions/{elytron-security/runtime/src/main/java/io/quarkus/elytron/security => elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common}/runtime/graal/Target_org_wildfly_security_password_interfaces_RawClearPassword.java (100%)
 rename extensions/{elytron-security/runtime/src/main/java/io/quarkus/elytron/security => elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common}/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java (100%)
 create mode 100644 extensions/elytron-security-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml

diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index 730b858c93065..756c5db10608d 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -381,6 +381,11 @@
                 quarkus-swagger-ui-deployment
                 ${project.version}
             
+            
+                io.quarkus
+                quarkus-elytron-security-common-deployment
+                ${project.version}
+            
             
                 io.quarkus
                 quarkus-elytron-security-deployment
diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index fb722d43982f1..ff3fcbcf7766e 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -329,6 +329,11 @@
                 quarkus-security
                 ${project.version}
             
+            
+                io.quarkus
+                quarkus-elytron-security-common
+                ${project.version}
+            
             
                 io.quarkus
                 quarkus-elytron-security
@@ -1868,6 +1873,11 @@
                 wildfly-elytron-x500-cert
                 ${wildfly-elytron.version}
             
+            
+                org.wildfly.security
+                wildfly-elytron-credential
+                ${wildfly-elytron.version}
+            
 
             
                 org.ow2.asm
diff --git a/extensions/elytron-security-common/deployment/pom.xml b/extensions/elytron-security-common/deployment/pom.xml
new file mode 100644
index 0000000000000..9cb7588f3adf7
--- /dev/null
+++ b/extensions/elytron-security-common/deployment/pom.xml
@@ -0,0 +1,39 @@
+
+
+    
+        quarkus-elytron-security-common-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-elytron-security-common-deployment
+    Quarkus - Elytron Security - Common - Deployment
+
+    
+        
+            io.quarkus
+            quarkus-core-deployment
+        
+    
+
+    
+        
+            
+                maven-compiler-plugin
+                
+                    
+                        
+                            io.quarkus
+                            quarkus-extension-processor
+                            ${project.version}
+                        
+                    
+                
+            
+        
+    
+
diff --git a/extensions/elytron-security-common/pom.xml b/extensions/elytron-security-common/pom.xml
new file mode 100644
index 0000000000000..f8520149f08e6
--- /dev/null
+++ b/extensions/elytron-security-common/pom.xml
@@ -0,0 +1,20 @@
+
+
+    
+        quarkus-build-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../../build-parent/pom.xml
+    
+    4.0.0
+
+    quarkus-elytron-security-common-parent
+    Quarkus - Elytron Security - Common
+    pom
+    
+        deployment
+        runtime
+    
+
diff --git a/extensions/elytron-security-common/runtime/pom.xml b/extensions/elytron-security-common/runtime/pom.xml
new file mode 100644
index 0000000000000..6337720a62ac9
--- /dev/null
+++ b/extensions/elytron-security-common/runtime/pom.xml
@@ -0,0 +1,50 @@
+
+
+    
+        quarkus-elytron-security-common-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-elytron-security-common
+    Quarkus - Elytron Security - Common - Runtime
+    
+        
+            io.quarkus
+            quarkus-core
+        
+        
+            org.graalvm.nativeimage
+            svm
+        
+        
+            org.wildfly.security
+            wildfly-elytron-credential
+        
+    
+
+    
+        
+            
+                io.quarkus
+                quarkus-bootstrap-maven-plugin
+            
+            
+                maven-compiler-plugin
+                
+                    
+                        
+                            io.quarkus
+                            quarkus-extension-processor
+                            ${project.version}
+                        
+                    
+                
+            
+        
+    
+
diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_BCryptPassword.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_BCryptPassword.java
similarity index 100%
rename from extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_BCryptPassword.java
rename to extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_BCryptPassword.java
diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_BSDUnixDESCryptPassword.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_BSDUnixDESCryptPassword.java
similarity index 100%
rename from extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_BSDUnixDESCryptPassword.java
rename to extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_BSDUnixDESCryptPassword.java
diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_ClearPassword.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_ClearPassword.java
similarity index 100%
rename from extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_ClearPassword.java
rename to extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_ClearPassword.java
diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_RawClearPassword.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_RawClearPassword.java
similarity index 100%
rename from extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_password_interfaces_RawClearPassword.java
rename to extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_password_interfaces_RawClearPassword.java
diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java
similarity index 100%
rename from extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java
rename to extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java
diff --git a/extensions/elytron-security-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/elytron-security-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..97696455e3850
--- /dev/null
+++ b/extensions/elytron-security-common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,9 @@
+---
+name: "Elytron Security Common"
+metadata:
+  keywords:
+  - "security"
+  categories:
+  - "security"
+  stable: "true"
+  unlisted: "true"
diff --git a/extensions/elytron-security/deployment/pom.xml b/extensions/elytron-security/deployment/pom.xml
index d493cac344445..efb2db3824787 100644
--- a/extensions/elytron-security/deployment/pom.xml
+++ b/extensions/elytron-security/deployment/pom.xml
@@ -25,6 +25,10 @@
             quarkus-resteasy-deployment
         
         -->
+        
+            io.quarkus
+            quarkus-elytron-security-common-deployment
+        
         
             io.quarkus
             quarkus-vertx-http-deployment
diff --git a/extensions/elytron-security/runtime/pom.xml b/extensions/elytron-security/runtime/pom.xml
index a80fea5800a30..82908be7f47d0 100644
--- a/extensions/elytron-security/runtime/pom.xml
+++ b/extensions/elytron-security/runtime/pom.xml
@@ -18,6 +18,10 @@
             io.quarkus
             quarkus-core
         
+        
+            io.quarkus
+            quarkus-elytron-security-common
+        
         
             io.quarkus
             quarkus-vertx-http
diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml
index 40c9a1d095e9d..d6c1ec3ded876 100644
--- a/extensions/infinispan-client/deployment/pom.xml
+++ b/extensions/infinispan-client/deployment/pom.xml
@@ -21,6 +21,10 @@
             io.quarkus
             quarkus-arc-deployment
         
+        
+            io.quarkus
+            quarkus-elytron-security-common-deployment
+        
         
             io.quarkus
             quarkus-infinispan-client
diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml
index c1cebdb832fca..1b7b111397b06 100644
--- a/extensions/infinispan-client/runtime/pom.xml
+++ b/extensions/infinispan-client/runtime/pom.xml
@@ -29,6 +29,10 @@
             io.quarkus
             quarkus-netty
         
+        
+            io.quarkus
+            quarkus-elytron-security-common
+        
         
             org.wildfly.security
             wildfly-elytron-sasl-plain
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 0e1ab681ad6bd..02e982ef782c5 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -96,6 +96,7 @@
 
         
         security
+        elytron-security-common
         elytron-security
         elytron-security-jdbc
         elytron-security-properties-file

From a8c55e6c7d649377e29ea025eaf58f9dd5828f65 Mon Sep 17 00:00:00 2001
From: Guillaume Smet 
Date: Fri, 13 Dec 2019 15:21:46 +0100
Subject: [PATCH 367/602] Add the jsonp extension as it is required for OAuth2
 authentication

---
 extensions/infinispan-client/deployment/pom.xml | 4 ++++
 extensions/infinispan-client/runtime/pom.xml    | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml
index d6c1ec3ded876..053d981f0db70 100644
--- a/extensions/infinispan-client/deployment/pom.xml
+++ b/extensions/infinispan-client/deployment/pom.xml
@@ -37,6 +37,10 @@
             io.quarkus
             quarkus-netty-deployment
         
+        
+            io.quarkus
+            quarkus-jsonp-deployment
+        
     
 
     
diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml
index 1b7b111397b06..231597bd62bb7 100644
--- a/extensions/infinispan-client/runtime/pom.xml
+++ b/extensions/infinispan-client/runtime/pom.xml
@@ -29,6 +29,10 @@
             io.quarkus
             quarkus-netty
         
+        
+            io.quarkus
+            quarkus-jsonp
+        
         
             io.quarkus
             quarkus-elytron-security-common

From 276e4c8ee678b74330e9bf5b12618906d3205295 Mon Sep 17 00:00:00 2001
From: Stuart Douglas 
Date: Tue, 17 Dec 2019 08:11:14 +1100
Subject: [PATCH 368/602] Run standalone RESTeasy in main thread pool

This means both Servlet and standalone will both
use the main thread pool, and it will avoid Vert.x
blocked thread warnings. These warnings are useless
as blocking IO can potentially block a thread for
a long time if request or response is large and the
connection is slow.
---
 .../ResteasyStandaloneBuildStep.java           |  9 ++++++---
 .../standalone/ResteasyStandaloneRecorder.java | 10 ++++++----
 .../standalone/VertxRequestHandler.java        | 18 ++++++++++++------
 3 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java
index 9102d5b0bac0f..2247694712c33 100644
--- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java
+++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java
@@ -24,6 +24,7 @@
 import io.quarkus.deployment.annotations.BuildStep;
 import io.quarkus.deployment.annotations.Record;
 import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
+import io.quarkus.deployment.builditem.ExecutorBuildItem;
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
 import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
@@ -210,7 +211,8 @@ public void boot(ShutdownContextBuildItem shutdown,
             BeanContainerBuildItem beanContainer,
             ResteasyStandaloneBuildItem standalone,
             Optional requireVirtual,
-            HttpBuildTimeConfig httpConfig) throws Exception {
+            HttpBuildTimeConfig httpConfig,
+            ExecutorBuildItem executorBuildItem) throws Exception {
 
         if (standalone == null) {
             return;
@@ -221,7 +223,8 @@ public void boot(ShutdownContextBuildItem shutdown,
                 || standalone.deploymentRootPath.equals("/");
         if (!isDefaultOrNullDeploymentPath) {
             // We need to register a special handler for non-default deployment path (specified as application path or resteasyConfig.path)
-            Handler handler = recorder.vertxRequestHandler(vertx.getVertx(), beanContainer.getValue());
+            Handler handler = recorder.vertxRequestHandler(vertx.getVertx(), beanContainer.getValue(),
+                    executorBuildItem.getExecutorProxy());
             // Exact match for resources matched to the root path
             routes.produce(new RouteBuildItem(standalone.deploymentRootPath, handler, false));
             String matchPath = standalone.deploymentRootPath;
@@ -238,7 +241,7 @@ public void boot(ShutdownContextBuildItem shutdown,
         Consumer ut = recorder.start(vertx.getVertx(),
                 shutdown,
                 beanContainer.getValue(),
-                isVirtual, isDefaultOrNullDeploymentPath);
+                isVirtual, isDefaultOrNullDeploymentPath, executorBuildItem.getExecutorProxy());
 
         defaultRoutes.produce(new DefaultRouteBuildItem(ut));
     }
diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java
index 312a91fcc1224..1d7597f29d685 100644
--- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java
+++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java
@@ -4,6 +4,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -91,7 +92,8 @@ public void staticInit(ResteasyDeployment dep, String path, Set known) {
     public Consumer start(RuntimeValue vertx,
             ShutdownContext shutdown,
             BeanContainer beanContainer,
-            boolean isVirtual, boolean isDefaultResourcesPath) {
+            boolean isVirtual, boolean isDefaultResourcesPath,
+            Executor executor) {
 
         shutdown.addShutdownTask(new Runnable() {
             @Override
@@ -147,7 +149,7 @@ public Handler get() {
         }
 
         if (deployment != null && isDefaultResourcesPath) {
-            handlers.add(vertxRequestHandler(vertx, beanContainer));
+            handlers.add(vertxRequestHandler(vertx, beanContainer, executor));
         }
         return new Consumer() {
 
@@ -161,9 +163,9 @@ public void accept(Route route) {
     }
 
     public Handler vertxRequestHandler(RuntimeValue vertx,
-            BeanContainer beanContainer) {
+            BeanContainer beanContainer, Executor executor) {
         if (deployment != null) {
-            return new VertxRequestHandler(vertx.getValue(), beanContainer, deployment, contextPath, ALLOCATOR);
+            return new VertxRequestHandler(vertx.getValue(), beanContainer, deployment, contextPath, ALLOCATOR, executor);
         }
         return null;
     }
diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java
index ccd63565410d4..8b53df4331559 100644
--- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java
+++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java
@@ -3,6 +3,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.concurrent.Executor;
 
 import javax.enterprise.inject.Instance;
 import javax.enterprise.inject.spi.CDI;
@@ -42,18 +43,20 @@ public class VertxRequestHandler implements Handler {
     protected final BeanContainer beanContainer;
     protected final CurrentIdentityAssociation association;
     protected final CurrentVertxRequest currentVertxRequest;
+    protected final Executor executor;
 
     public VertxRequestHandler(Vertx vertx,
             BeanContainer beanContainer,
             ResteasyDeployment deployment,
             String rootPath,
-            BufferAllocator allocator) {
+            BufferAllocator allocator, Executor executor) {
         this.vertx = vertx;
         this.beanContainer = beanContainer;
         this.dispatcher = new RequestDispatcher((SynchronousDispatcher) deployment.getDispatcher(),
                 deployment.getProviderFactory(), null, Thread.currentThread().getContextClassLoader());
         this.rootPath = rootPath;
         this.allocator = allocator;
+        this.executor = executor;
         Instance association = CDI.current().select(CurrentIdentityAssociation.class);
         this.association = association.isResolvable() ? association.get() : null;
         currentVertxRequest = CDI.current().select(CurrentVertxRequest.class).get();
@@ -75,11 +78,14 @@ public void handle(RoutingContext request) {
             return;
         }
 
-        vertx.executeBlocking(event -> {
-            dispatch(request, is, new VertxBlockingOutput(request.request()));
-        }, false, event -> {
-            if (event.failed()) {
-                request.fail(event.cause());
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    dispatch(request, is, new VertxBlockingOutput(request.request()));
+                } catch (Throwable e) {
+                    request.fail(e);
+                }
             }
         });
     }

From e5c17a104d2e95e8d1519819747498d106b1a3be Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis 
Date: Mon, 16 Dec 2019 23:07:15 +0200
Subject: [PATCH 369/602] Move Spring Data JPA classes to panache to avoid
 dev-mode CL issue

Fixes #6214
---
 .../orm/panache/runtime/AdditionalJpaOperations.java          | 4 ++--
 .../orm/panache/runtime/CustomCountPanacheQuery.java          | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)
 rename extensions/{spring-data-jpa => panache/hibernate-orm-panache}/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java (90%)
 rename extensions/{spring-data-jpa => panache/hibernate-orm-panache}/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java (75%)

diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java
similarity index 90%
rename from extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java
rename to extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java
index 4ab7fe9800a84..cc267002b261e 100644
--- a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java
+++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/AdditionalJpaOperations.java
@@ -9,6 +9,8 @@
 import io.quarkus.panache.common.Parameters;
 import io.quarkus.panache.common.Sort;
 
+//TODO this class is only needed by the Spring Data JPA module and would be placed there it it weren't for a dev-mode classloader issue
+// see https://github.com/quarkusio/quarkus/issues/6214
 public class AdditionalJpaOperations {
 
     @SuppressWarnings("rawtypes")
@@ -19,7 +21,6 @@ public static PanacheQuery find(Class entityClass, String query, String co
         Query jpaQuery = em.createQuery(sort != null ? findQuery + JpaOperations.toOrderBy(sort) : findQuery);
         JpaOperations.bindParameters(jpaQuery, params);
         return new CustomCountPanacheQuery(em, jpaQuery, findQuery, countQuery, params);
-        //        return new PanacheQueryImpl(em, jpaQuery, findQuery, params);
     }
 
     @SuppressWarnings("rawtypes")
@@ -35,6 +36,5 @@ public static PanacheQuery find(Class entityClass, String query, String co
         Query jpaQuery = em.createQuery(sort != null ? findQuery + JpaOperations.toOrderBy(sort) : findQuery);
         JpaOperations.bindParameters(jpaQuery, params);
         return new CustomCountPanacheQuery(em, jpaQuery, findQuery, countQuery, params);
-        //        return new PanacheQueryImpl(em, jpaQuery, findQuery, params);
     }
 }
diff --git a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java
similarity index 75%
rename from extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java
rename to extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java
index 0694fef6e4075..dbe86a374716c 100644
--- a/extensions/spring-data-jpa/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java
+++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/runtime/CustomCountPanacheQuery.java
@@ -3,6 +3,8 @@
 import javax.persistence.EntityManager;
 import javax.persistence.Query;
 
+//TODO this class is only needed by the Spring Data JPA module and would be placed there it it weren't for a dev-mode classloader issue
+// see https://github.com/quarkusio/quarkus/issues/6214
 public class CustomCountPanacheQuery extends PanacheQueryImpl {
 
     private final String customCountQuery;

From e554b8a7ff073f3b0b3fb76942336b10d383c5f8 Mon Sep 17 00:00:00 2001
From: Martin Kouba 
Date: Sat, 14 Dec 2019 16:05:16 +0100
Subject: [PATCH 370/602] Qute - escape expressions in html by default

- resolves #6155
- API changes:
 - introduce TemplateLocator
 - add Template#getVariant()
---
 docs/src/main/asciidoc/qute-reference.adoc    |  18 ++++
 .../quarkus/qute/deployment/EscapingTest.java |  66 ++++++++++++
 .../qute/deployment/VariantTemplateTest.java  |   2 +-
 .../io/quarkus/qute/api/VariantTemplate.java  |   1 +
 .../quarkus/qute/runtime/EngineProducer.java  | 101 +++++++++++++++---
 .../qute/runtime/TemplateProducer.java        |   7 ++
 .../qute/runtime/VariantTemplateProducer.java |  20 ++--
 .../qute/runtime/TemplateResponseFilter.java  |   2 +-
 .../src/main/java/io/quarkus/qute/Engine.java |   6 +-
 .../java/io/quarkus/qute/EngineBuilder.java   |   5 +-
 .../main/java/io/quarkus/qute/EngineImpl.java |  22 ++--
 .../main/java/io/quarkus/qute/Escaper.java    |  85 +++++++++++++++
 .../src/main/java/io/quarkus/qute/Parser.java |  28 +++--
 .../main/java/io/quarkus/qute/RawString.java  |  23 ++++
 .../java/io/quarkus/qute/ResultMapper.java    |   5 +-
 .../io/quarkus/qute/SingleResultNode.java     |   2 +-
 .../main/java/io/quarkus/qute/Template.java   |   9 +-
 .../java/io/quarkus/qute/TemplateImpl.java    |  10 +-
 .../java/io/quarkus/qute/TemplateLocator.java |  37 +++++++
 .../java/io/quarkus/qute/TemplateNode.java    |  12 ++-
 .../java/io/quarkus/qute/ValueResolvers.java  |  15 +++
 .../main/java/io/quarkus/qute}/Variant.java   |   9 +-
 .../java/io/quarkus/qute/EscaperTest.java     |  48 +++++++++
 .../test/java/io/quarkus/qute/SimpleTest.java |   7 +-
 24 files changed, 484 insertions(+), 56 deletions(-)
 create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/EscapingTest.java
 create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/Escaper.java
 create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/RawString.java
 create mode 100644 independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java
 rename {extensions/qute/runtime/src/main/java/io/quarkus/qute/api => independent-projects/qute/core/src/main/java/io/quarkus/qute}/Variant.java (76%)
 create mode 100644 independent-projects/qute/core/src/test/java/io/quarkus/qute/EscaperTest.java

diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc
index 829638a71ca1c..36c3fa283f8ad 100644
--- a/docs/src/main/asciidoc/qute-reference.adoc
+++ b/docs/src/main/asciidoc/qute-reference.adoc
@@ -182,6 +182,24 @@ This could be useful to access data for which the key is overriden:
 
 ====
 
+===== Character Escapes
+
+For HTML and XML templates the `'`, `"`, `<`, `>`, `&` characters are escaped by default.
+If you need to render the unescaped value:
+
+1. Use the `raw` or `safe` properties implemented as extension methods of the `java.lang.Object`,
+2. Wrap the `String` value in a `io.quarkus.qute.RawString`.
+
+[source,html]
+----
+
+

{title}

<1> +{paragraph.raw} <2> + +---- +<1> `title` that resolves to `Expressions & Escapes` will be rendered as `Expressions &amp; Escapes` +<2> `paragraph` that resolves to `

My text!

` will be rendered as `

My text!

` + ==== Sections A section: diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/EscapingTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/EscapingTest.java new file mode 100644 index 0000000000000..40488586bde26 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/EscapingTest.java @@ -0,0 +1,66 @@ +package io.quarkus.qute.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.RawString; +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateData; +import io.quarkus.test.QuarkusUnitTest; + +public class EscapingTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(Item.class) + .addAsResource(new StringAsset("{text} {other} {text.raw} {text.safe} {item.foo}"), + "templates/foo.html") + .addAsResource(new StringAsset("{item} {item.raw}"), + "templates/item.html") + .addAsResource(new StringAsset("{text} {other} {text.raw} {text.safe} {item.foo}"), + "templates/bar.txt")); + + @Inject + Template foo; + + @Inject + Template bar; + + @Inject + Template item; + + @Test + public void testEscaper() { + assertEquals("<div> &"'
", + foo.data("text", "
").data("other", "&\"'").data("item", new Item()).render()); + // No escaping for txt templates + assertEquals("
&\"'
", + bar.data("text", "
").data("other", "&\"'").data("item", new Item()).render()); + // Item.toString() is escaped too + assertEquals("<h1>Item</h1>

Item

", + item.data("item", new Item()).render()); + } + + @TemplateData + public static class Item { + + public RawString getFoo() { + return new RawString(""); + } + + @Override + public String toString() { + return "

Item

"; + } + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java index fbaa9cc710330..a6eb5d35e4f0b 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/VariantTemplateTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.Variant; +import io.quarkus.qute.Variant; import io.quarkus.qute.api.VariantTemplate; import io.quarkus.test.QuarkusUnitTest; diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/VariantTemplate.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/VariantTemplate.java index 7894127d2b0bf..77b4f3e74f1c3 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/VariantTemplate.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/VariantTemplate.java @@ -1,6 +1,7 @@ package io.quarkus.qute.api; import io.quarkus.qute.Template; +import io.quarkus.qute.Variant; /** * diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java index 2beda61a1de49..9cafeb77e2912 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java @@ -1,8 +1,9 @@ package io.quarkus.qute.runtime; -import java.io.InputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URL; import java.nio.charset.Charset; import java.util.List; import java.util.Optional; @@ -19,12 +20,19 @@ import io.quarkus.arc.InstanceHandle; import io.quarkus.qute.Engine; import io.quarkus.qute.EngineBuilder; +import io.quarkus.qute.Escaper; +import io.quarkus.qute.Expression; import io.quarkus.qute.NamespaceResolver; +import io.quarkus.qute.RawString; import io.quarkus.qute.ReflectionValueResolver; +import io.quarkus.qute.ResultMapper; import io.quarkus.qute.Results.Result; +import io.quarkus.qute.TemplateLocator.TemplateLocation; +import io.quarkus.qute.TemplateNode.Origin; import io.quarkus.qute.UserTagSectionHelper; import io.quarkus.qute.ValueResolver; import io.quarkus.qute.ValueResolvers; +import io.quarkus.qute.Variant; @Singleton public class EngineProducer { @@ -59,8 +67,31 @@ void init(QuteConfig config, List resolverClasses, List template // We don't register the map resolver because of param declaration validation // See DefaultTemplateExtensions - builder.addValueResolvers(ValueResolvers.thisResolver(), ValueResolvers.orResolver(), ValueResolvers.trueResolver(), - ValueResolvers.collectionResolver(), ValueResolvers.mapperResolver(), ValueResolvers.mapEntryResolver()); + builder.addValueResolver(ValueResolvers.thisResolver()); + builder.addValueResolver(ValueResolvers.orResolver()); + builder.addValueResolver(ValueResolvers.trueResolver()); + builder.addValueResolver(ValueResolvers.collectionResolver()); + builder.addValueResolver(ValueResolvers.mapperResolver()); + builder.addValueResolver(ValueResolvers.mapEntryResolver()); + // foo.string.raw returns a RawString which is never escaped + builder.addValueResolver(ValueResolvers.rawResolver()); + + // Escape some characters for HTML templates + Escaper htmlEscaper = Escaper.builder().add('"', """).add('\'', "'") + .add('&', "&").add('<', "<").add('>', ">").build(); + builder.addResultMapper(new ResultMapper() { + + @Override + public boolean appliesTo(Origin origin, Object result) { + return !(result instanceof RawString) + && origin.getVariant().filter(EngineProducer::requiresDefaultEscaping).isPresent(); + } + + @Override + public String map(Object result, Expression expression) { + return htmlEscaper.escape(result.toString()); + } + }); // Fallback reflection resolver builder.addValueResolver(new ReflectionValueResolver()); @@ -132,37 +163,79 @@ private ValueResolver createResolver(String resolverClassName) { * @param path * @return the optional reader */ - private Optional locate(String path) { - InputStream in = null; + private Optional locate(String path) { + URL resource = null; // First try to locate a tag template if (tags.stream().anyMatch(tag -> tag.startsWith(path))) { LOGGER.debugf("Locate tag for %s", path); - in = locatePath(tagPath + path); + resource = locatePath(tagPath + path); // Try path with suffixes for (String suffix : suffixes) { - in = locatePath(tagPath + path + "." + suffix); - if (in != null) { + resource = locatePath(tagPath + path + "." + suffix); + if (resource != null) { break; } } } - if (in == null) { + if (resource == null) { String templatePath = basePath + path; LOGGER.debugf("Locate template for %s", templatePath); - in = locatePath(templatePath); + resource = locatePath(templatePath); } - if (in != null) { - return Optional.of(new InputStreamReader(in, Charset.forName("utf-8"))); + if (resource != null) { + return Optional.of(new ResourceTemplateLocation(resource, guessVariant(path))); } return Optional.empty(); } - private InputStream locatePath(String path) { + private URL locatePath(String path) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = EngineProducer.class.getClassLoader(); } - return cl.getResourceAsStream(path); + return cl.getResource(path); + } + + static Variant guessVariant(String path) { + // TODO we need a proper way to detect the variant + int suffixIdx = path.lastIndexOf('.'); + if (suffixIdx != -1) { + String suffix = path.substring(suffixIdx); + return new Variant(null, VariantTemplateProducer.parseMediaType(suffix), null); + } + return null; + } + + static boolean requiresDefaultEscaping(Variant variant) { + return variant.mediaType != null + ? (Variant.TEXT_HTML.equals(variant.mediaType) || Variant.TEXT_XML.equals(variant.mediaType)) + : false; + } + + static class ResourceTemplateLocation implements TemplateLocation { + + private final URL resource; + private final Optional variant; + + public ResourceTemplateLocation(URL resource, Variant variant) { + this.resource = resource; + this.variant = Optional.ofNullable(variant); + } + + @Override + public Reader read() { + try { + return new InputStreamReader(resource.openStream(), Charset.forName("utf-8")); + } catch (IOException e) { + return null; + } + } + + @Override + public Optional getVariant() { + return variant; + } + } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java index d4b6ba3fe8ef3..b599e7bd5d907 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -16,6 +17,7 @@ import io.quarkus.qute.Expression; import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Variant; import io.quarkus.qute.api.ResourcePath; @Singleton @@ -104,6 +106,11 @@ public String getGeneratedId() { return template.get().getGeneratedId(); } + @Override + public Optional getVariant() { + return template.get().getVariant(); + } + } } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java index 412f2b3f0c7db..190d339bb0158 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; @@ -27,8 +28,8 @@ import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; import io.quarkus.qute.TemplateInstanceBase; +import io.quarkus.qute.Variant; import io.quarkus.qute.api.ResourcePath; -import io.quarkus.qute.api.Variant; import io.quarkus.qute.api.VariantTemplate; @Singleton @@ -114,6 +115,11 @@ public String getGeneratedId() { throw new UnsupportedOperationException(); } + @Override + public Optional getVariant() { + throw new UnsupportedOperationException(); + } + } class VariantTemplateInstanceImpl extends TemplateInstanceBase { @@ -166,15 +172,15 @@ public TemplateVariants(Map variants, String defaultTemplate) { } static String parseMediaType(String suffix) { - // TODO support more media types... - if (suffix.equalsIgnoreCase(".html")) { - return "text/html"; + // TODO we need a proper way to parse the media type + if (suffix.equalsIgnoreCase(".html") || suffix.equalsIgnoreCase(".htm")) { + return Variant.TEXT_HTML; } else if (suffix.equalsIgnoreCase(".xml")) { - return "text/xml"; + return Variant.TEXT_XML; } else if (suffix.equalsIgnoreCase(".txt")) { - return "text/plain"; + return Variant.TEXT_PLAIN; } else if (suffix.equalsIgnoreCase(".json")) { - return "application/json"; + return Variant.APPLICATION_JSON; } LOGGER.warn("Unknown media type for suffix: " + suffix); return "application/octet-stream"; diff --git a/extensions/resteasy-qute/runtime/src/main/java/io/quarkus/resteasy/qute/runtime/TemplateResponseFilter.java b/extensions/resteasy-qute/runtime/src/main/java/io/quarkus/resteasy/qute/runtime/TemplateResponseFilter.java index e04a208fc7396..2e408fb0a358e 100644 --- a/extensions/resteasy-qute/runtime/src/main/java/io/quarkus/resteasy/qute/runtime/TemplateResponseFilter.java +++ b/extensions/resteasy-qute/runtime/src/main/java/io/quarkus/resteasy/qute/runtime/TemplateResponseFilter.java @@ -17,7 +17,7 @@ import org.jboss.resteasy.core.interception.jaxrs.SuspendableContainerResponseContext; import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.Variant; +import io.quarkus.qute.Variant; import io.quarkus.qute.api.VariantTemplate; @Provider diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java index af538c4de2168..a1442828cf813 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java @@ -13,7 +13,11 @@ static EngineBuilder builder() { return new EngineBuilder(); } - public Template parse(String content); + default Template parse(String content) { + return parse(content, null); + } + + public Template parse(String content, Variant variant); public SectionHelperFactory getSectionHelperFactory(String name); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java index 04446fa3d07a9..b5cfe59baacc8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java @@ -13,7 +13,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -25,7 +24,7 @@ public final class EngineBuilder { private final Map> sectionHelperFactories; private final List valueResolvers; private final List namespaceResolvers; - private final List>> locators; + private final List locators; private final List resultMappers; private Function> sectionHelperFunc; @@ -105,7 +104,7 @@ public EngineBuilder addNamespaceResolver(NamespaceResolver resolver) { * @return self * @see Engine#getTemplate(String) */ - public EngineBuilder addLocator(Function> locator) { + public EngineBuilder addLocator(TemplateLocator locator) { this.locators.add(locator); return this; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java index 845840b9fe946..191e474939bec 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java @@ -1,5 +1,6 @@ package io.quarkus.qute; +import io.quarkus.qute.TemplateLocator.TemplateLocation; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; @@ -35,20 +36,20 @@ class EngineImpl implements Engine { private final List namespaceResolvers; private final Evaluator evaluator; private final Map templates; - private final List>> locators; + private final List locators; private final List resultMappers; private final PublisherFactory publisherFactory; private final AtomicLong idGenerator = new AtomicLong(0); EngineImpl(Map> sectionHelperFactories, List valueResolvers, - List namespaceResolvers, List>> locators, + List namespaceResolvers, List locators, List resultMappers, Function> sectionHelperFunc) { this.sectionHelperFactories = new HashMap<>(sectionHelperFactories); this.valueResolvers = sort(valueResolvers); this.namespaceResolvers = ImmutableList.copyOf(namespaceResolvers); this.evaluator = new EvaluatorImpl(this.valueResolvers); this.templates = new ConcurrentHashMap<>(); - this.locators = ImmutableList.copyOf(locators); + this.locators = sort(locators); ServiceLoader loader = ServiceLoader.load(PublisherFactory.class); Iterator iterator = loader.iterator(); if (iterator.hasNext()) { @@ -65,8 +66,9 @@ class EngineImpl implements Engine { this.sectionHelperFunc = sectionHelperFunc; } - public Template parse(String content) { - return new Parser(this).parse(new StringReader(content)); + @Override + public Template parse(String content, Variant variant) { + return new Parser(this).parse(new StringReader(content), Optional.ofNullable(variant)); } @Override @@ -125,11 +127,11 @@ String generateId() { } private Template load(String id) { - for (Function> locator : locators) { - Optional reader = locator.apply(id); - if (reader.isPresent()) { - try (Reader r = reader.get()) { - return new Parser(this).parse(ensureBufferedReader(reader.get())); + for (TemplateLocator locator : locators) { + Optional location = locator.locate(id); + if (location.isPresent()) { + try (Reader r = location.get().read()) { + return new Parser(this).parse(ensureBufferedReader(r), location.get().getVariant()); } catch (IOException e) { LOGGER.warn("Unable to close the reader for " + id, e); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Escaper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Escaper.java new file mode 100644 index 0000000000000..0d9e88dad530d --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Escaper.java @@ -0,0 +1,85 @@ +package io.quarkus.qute; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Escapes a characted sequence using a map of replacements. + */ +public final class Escaper { + + private final Map replacements; + + /** + * + * @param replacements + */ + private Escaper(Map replacements) { + this.replacements = replacements.isEmpty() ? Collections.emptyMap() + : new HashMap<>(replacements); + } + + /** + * + * @param value + * @return an escaped value + */ + public String escape(CharSequence value) { + Objects.requireNonNull(value); + if (value.length() == 0) { + return value.toString(); + } + for (int i = 0; i < value.length(); i++) { + String replacement = replacements.get(value.charAt(i)); + if (replacement != null) { + // In most cases we will not need to escape the value at all + return doEscape(value, i, new StringBuilder(value.subSequence(0, i)).append(replacement)); + } + } + return value.toString(); + } + + private String doEscape(CharSequence value, int index, StringBuilder builder) { + int length = value.length(); + while (++index < length) { + char c = value.charAt(index); + String replacement = replacements.get(c); + if (replacement != null) { + builder.append(replacement); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final Map replacements; + + private Builder() { + this.replacements = new HashMap<>(); + } + + public Builder add(char c, String replacement) { + replacements.put(c, replacement); + return this; + } + + public Escaper build() { + return new Escaper(replacements); + } + + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 92ec49a916b75..311e3aaa6243f 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.stream.Collectors; @@ -47,6 +48,7 @@ class Parser implements Function { private int sectionBlockIdx; private boolean ignoreContent; private String templateId; + private Optional variant; public Parser(EngineImpl engine) { this.engine = engine; @@ -54,7 +56,7 @@ public Parser(EngineImpl engine) { this.buffer = new StringBuilder(); this.sectionStack = new ArrayDeque<>(); this.sectionStack - .addFirst(SectionNode.builder(ROOT_HELPER_NAME, new OriginImpl(line, templateId)).setEngine(engine) + .addFirst(SectionNode.builder(ROOT_HELPER_NAME, new OriginImpl(line, templateId, variant)).setEngine(engine) .setHelperFactory(new SectionHelperFactory() { @Override public SectionHelper initialize(SectionInitContext context) { @@ -78,9 +80,10 @@ public CompletionStage resolve(SectionResolutionContext context) { this.line = 1; } - Template parse(Reader reader) { + Template parse(Reader reader, Optional variant) { long start = System.currentTimeMillis(); - templateId = engine.generateId(); + this.templateId = engine.generateId(); + this.variant = variant; try { int val; while ((val = reader.read()) != -1) { @@ -110,7 +113,7 @@ Template parse(Reader reader) { throw new IllegalStateException("No root section part found!"); } root.addBlock(part.build()); - Template template = new TemplateImpl(engine, root.build(), templateId); + Template template = new TemplateImpl(engine, root.build(), templateId, variant); LOGGER.tracef("Parsing finished in %s ms", System.currentTimeMillis() - start); return template; @@ -194,7 +197,7 @@ private boolean isLineSeparator(char character) { private void flushText() { if (buffer.length() > 0 && !ignoreContent) { SectionBlock.Builder block = sectionBlockStack.peek(); - block.addNode(new TextNode(buffer.toString(), new OriginImpl(line, templateId))); + block.addNode(new TextNode(buffer.toString(), new OriginImpl(line, templateId, variant))); } this.buffer = new StringBuilder(); } @@ -263,7 +266,7 @@ private void flushTag() { // Init section block Map typeInfos = typeInfoStack.peek(); Map result = factory.initializeBlock(typeInfos, mainBlock); - SectionNode.Builder sectionNode = SectionNode.builder(sectionName, new OriginImpl(-1, templateId)) + SectionNode.Builder sectionNode = SectionNode.builder(sectionName, new OriginImpl(-1, templateId, variant)) .setEngine(engine) .setHelperFactory(factory); @@ -328,7 +331,8 @@ private void flushTag() { typeInfos.put(key, "[" + value + "]"); } else { - sectionBlockStack.peek().addNode(new ExpressionNode(apply(content), engine, new OriginImpl(line, templateId))); + sectionBlockStack.peek() + .addNode(new ExpressionNode(apply(content), engine, new OriginImpl(line, templateId, variant))); } this.buffer = new StringBuilder(); } @@ -564,17 +568,19 @@ static boolean isBracket(char character) { @Override public Expression apply(String value) { - return parseExpression(value, typeInfoStack.peek(), new OriginImpl(line, templateId)); + return parseExpression(value, typeInfoStack.peek(), new OriginImpl(line, templateId, variant)); } static class OriginImpl implements Origin { private final int line; private final String templateId; + private final Optional variant; - OriginImpl(int line, String templateId) { + OriginImpl(int line, String templateId, Optional variant) { this.line = line; this.templateId = templateId; + this.variant = variant; } @Override @@ -587,6 +593,10 @@ public String getTemplateId() { return templateId; } + public Optional getVariant() { + return variant; + } + @Override public int hashCode() { return Objects.hash(line, templateId); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/RawString.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/RawString.java new file mode 100644 index 0000000000000..4c6803c875484 --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/RawString.java @@ -0,0 +1,23 @@ +package io.quarkus.qute; + +/** + * Raw string is never escaped. + */ +public final class RawString { + + private final String value; + + public RawString(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultMapper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultMapper.java index 56920b72a005d..9cb7db0ae5c4f 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultMapper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ResultMapper.java @@ -1,5 +1,7 @@ package io.quarkus.qute; +import io.quarkus.qute.TemplateNode.Origin; + /** * The first result mapper that applies to the result object is used to map the result to the string value. The mapper with * higher priority wins. @@ -9,10 +11,11 @@ public interface ResultMapper extends WithPriority { /** * + * @param origin * @param result * @return {@code true} if this mapper applies to the given result */ - default boolean appliesTo(Object result) { + default boolean appliesTo(Origin origin, Object result) { return true; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java index 7ad741026d70a..70d0a538e8628 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SingleResultNode.java @@ -25,7 +25,7 @@ public void process(Consumer consumer) { String result = null; if (mappers != null) { for (ResultMapper mapper : mappers) { - if (mapper.appliesTo(value)) { + if (mapper.appliesTo(expression.origin, value)) { result = mapper.map(value, expression); break; } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java index 86e1660c98d89..35b56ef0c8718 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java @@ -1,5 +1,6 @@ package io.quarkus.qute; +import java.util.Optional; import java.util.Set; /** @@ -43,7 +44,7 @@ default String render() { /** * - * @return an immutable set of expressions + * @return an immutable set of expressions used in the template */ Set getExpressions(); @@ -54,4 +55,10 @@ default String render() { */ String getGeneratedId(); + /** + * + * @return the template variant + */ + Optional getVariant(); + } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java index 03926d595f29b..5f609590bc509 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java @@ -1,6 +1,7 @@ package io.quarkus.qute; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -14,12 +15,14 @@ class TemplateImpl implements Template { private final String generatedId; private final EngineImpl engine; + private final Optional variant; final SectionNode root; - TemplateImpl(EngineImpl engine, SectionNode root, String generatedId) { + TemplateImpl(EngineImpl engine, SectionNode root, String generatedId, Optional variant) { this.engine = engine; this.root = root; this.generatedId = generatedId; + this.variant = variant; } @Override @@ -37,6 +40,11 @@ public String getGeneratedId() { return generatedId; } + @Override + public Optional getVariant() { + return variant; + } + private class TemplateInstanceImpl extends TemplateInstanceBase { @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java new file mode 100644 index 0000000000000..2fd5ed4dfb78f --- /dev/null +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateLocator.java @@ -0,0 +1,37 @@ +package io.quarkus.qute; + +import java.io.Reader; +import java.util.Optional; + +/** + * Locates template sources. + * + * @see Engine#getTemplate(String) + */ +public interface TemplateLocator extends WithPriority { + + /** + * + * @param id + * @return the template location for the given id + */ + Optional locate(String id); + + interface TemplateLocation { + + /** + * A {@link Reader} instance produced by a locator is immediately closed right after the template content is parsed. + * + * @return + */ + Reader read(); + + /** + * + * @return the template variant + */ + Optional getVariant(); + + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java index a135710c6e6a6..07efdced7c616 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateNode.java @@ -1,6 +1,7 @@ package io.quarkus.qute; import java.util.Collections; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; @@ -24,14 +25,23 @@ default Set getExpressions() { return Collections.emptySet(); } + /** + * + * @return the origin of the node + */ Origin getOrigin(); - interface Origin { + /** + * Represents an origin of a template node. + */ + public interface Origin { int getLine(); String getTemplateId(); + Optional getVariant(); + } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java index 462ed4951c4d7..a4b30207dc4d8 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java @@ -14,6 +14,21 @@ public final class ValueResolvers { static final String THIS = "this"; + public static ValueResolver rawResolver() { + return new ValueResolver() { + + public boolean appliesTo(EvalContext context) { + return context.getBase() != null + && (context.getName().equals("raw") || context.getName().equals("safe")); + } + + @Override + public CompletionStage resolve(EvalContext context) { + return CompletableFuture.completedFuture(new RawString(context.getBase().toString())); + } + }; + } + public static ValueResolver collectionResolver() { return new ValueResolver() { diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/Variant.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Variant.java similarity index 76% rename from extensions/qute/runtime/src/main/java/io/quarkus/qute/api/Variant.java rename to independent-projects/qute/core/src/main/java/io/quarkus/qute/Variant.java index a8640cccae266..a457d8a1bd5f9 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/Variant.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Variant.java @@ -1,4 +1,4 @@ -package io.quarkus.qute.api; +package io.quarkus.qute; import java.util.Locale; import java.util.Objects; @@ -6,7 +6,12 @@ /** * Media type, locale and encoding. */ -public class Variant { +public final class Variant { + + public final static String TEXT_HTML = "text/html"; + public final static String TEXT_PLAIN = "text/plain"; + public final static String TEXT_XML = "text/xml"; + public final static String APPLICATION_JSON = "application/json"; public final Locale locale; public final String mediaType; diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/EscaperTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EscaperTest.java new file mode 100644 index 0000000000000..01ac09aa86e31 --- /dev/null +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/EscaperTest.java @@ -0,0 +1,48 @@ +package io.quarkus.qute; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.qute.TemplateNode.Origin; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +/** + * + */ +public class EscaperTest { + + @Test + public void testEscaping() throws IOException { + Escaper escaper = Escaper.builder().add('a', "aaa").build(); + assertEquals("aaa", escaper.escape("a")); + assertEquals("b", escaper.escape("b")); + + Escaper html = Escaper.builder().add('"', """).add('\'', "'") + .add('&', "&").add('<', "<").add('>', ">").build(); + assertEquals("<strong>Čolek</strong>", html.escape("Čolek")); + assertEquals("<a>&link"'</a>", html.escape("&link\"'")); + } + + @Test + public void testRawStringRevolver() { + + Escaper escaper = Escaper.builder().add('a', "A").build(); + Engine engine = Engine.builder().addValueResolver(ValueResolvers.mapResolver()) + .addValueResolver(ValueResolvers.rawResolver()).addResultMapper(new ResultMapper() { + + @Override + public boolean appliesTo(Origin origin, Object result) { + return result instanceof String; + } + + @Override + public String map(Object result, Expression expression) { + return escaper.escape(result.toString()); + } + }).build(); + + assertEquals("HAM HaM", engine.parse("{foo} {foo.raw}").data("foo", "HaM").render()); + + } + +} diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java index aa18925e48560..831d2a755de25 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/SimpleTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.quarkus.qute.Results.Result; +import io.quarkus.qute.TemplateNode.Origin; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -146,7 +147,7 @@ public int getPriority() { return 10; } - public boolean appliesTo(Object val) { + public boolean appliesTo(Origin origin, Object val) { return val.equals(Result.NOT_FOUND); } @@ -160,7 +161,7 @@ public int getPriority() { return 1; } - public boolean appliesTo(Object val) { + public boolean appliesTo(Origin origin, Object val) { return val.equals(Result.NOT_FOUND); } @@ -174,7 +175,7 @@ public int getPriority() { return 1; } - public boolean appliesTo(Object val) { + public boolean appliesTo(Origin origin, Object val) { return val instanceof Collection; } From cd32b75504858c295f64f750aa0b3ecdcfbf3326 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 16 Dec 2019 16:34:04 +0100 Subject: [PATCH 371/602] Use the bean index to scan subresources, check parameters and look for @Context In the case of generated resource beans such as in Kogito, this was causing failures. It should fix the quickstart issue we have right now. --- .../common/deployment/ResteasyServerCommonProcessor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index 4b003c27900f0..31cee2d8639bc 100755 --- a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -232,7 +232,7 @@ public void build( } } - Set subresources = findSubresources(index, scannedResources); + Set subresources = findSubresources(beanArchiveIndexBuildItem.getIndex(), scannedResources); if (!subresources.isEmpty()) { for (DotName locator : subresources) { reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, locator.toString())); @@ -247,9 +247,9 @@ public void build( // see https://issues.jboss.org/browse/RESTEASY-2183 generateDefaultConstructors(transformers, withoutDefaultCtor, additionalJaxRsResourceDefiningAnnotations); - checkParameterNames(index, additionalJaxRsResourceMethodParamAnnotations); + checkParameterNames(beanArchiveIndexBuildItem.getIndex(), additionalJaxRsResourceMethodParamAnnotations); - registerContextProxyDefinitions(index, proxyDefinition); + registerContextProxyDefinitions(beanArchiveIndexBuildItem.getIndex(), proxyDefinition); registerReflectionForSerialization(reflectiveClass, reflectiveHierarchy, combinedIndexBuildItem, beanArchiveIndexBuildItem, additionalJaxRsResourceMethodAnnotations); From 5cc12283dedee673b51f77d652b1ddc94b57ab34 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 17 Dec 2019 11:41:15 +1100 Subject: [PATCH 372/602] Allow multiple headers with same name Manually adding multiple JAX-RS Set-Cookie headers would result in only a single one being sent to the client. --- .../quarkus/resteasy/runtime/standalone/VertxHttpResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxHttpResponse.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxHttpResponse.java index d3d9396347719..d1d84f03b9a83 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxHttpResponse.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxHttpResponse.java @@ -121,7 +121,7 @@ public static void transformHeaders(VertxHttpResponse vertxResponse, HttpServerR if (delegate != null) { response.headers().add(key, delegate.toString(value)); } else { - response.headers().set(key, value.toString()); + response.headers().add(key, value.toString()); } } } From 45912eef1c08499536e40d47e2bd05b3a9e74d89 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Mon, 16 Dec 2019 22:00:15 -0300 Subject: [PATCH 373/602] cookie test --- .../io/quarkus/resteasy/test/CookieTest.java | 30 +++++++++++++++++++ .../resteasy/test/SetCookieResource.java | 19 ++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CookieTest.java create mode 100644 extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/SetCookieResource.java diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CookieTest.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CookieTest.java new file mode 100644 index 0000000000000..824849fa6ba57 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/CookieTest.java @@ -0,0 +1,30 @@ +package io.quarkus.resteasy.test; + +import java.util.HashMap; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class CookieTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SetCookieResource.class)); + + @Test + public void testSetMultipleCookieAsString() { + HashMap cookies = new HashMap<>(); + + cookies.put("c1", "c1"); + cookies.put("c2", "c2"); + cookies.put("c3", "c3"); + + RestAssured.when().get("/set-cookies").then().cookies(cookies); + } +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/SetCookieResource.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/SetCookieResource.java new file mode 100644 index 0000000000000..47c28230551c3 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/SetCookieResource.java @@ -0,0 +1,19 @@ +package io.quarkus.resteasy.test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; + +import org.jboss.resteasy.spi.HttpResponse; + +@Path("/set-cookies") +public class SetCookieResource { + + @GET + public void setCookies(@Context HttpResponse response) { + response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, "c1=c1"); + response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, "c2=c2"); + response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, "c3=c3"); + } +} From 81dc599de4a93725d9a1e7f3987374a39619d68a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2019 20:39:42 +0000 Subject: [PATCH 374/602] Bump flyway-core from 6.1.1 to 6.1.2 Bumps [flyway-core](https://github.com/flyway/flyway) from 6.1.1 to 6.1.2. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-6.1.1...flyway-6.1.2) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index ff3fcbcf7766e..830a135c7dce4 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -147,7 +147,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.1.1 + 6.1.2 1.0.5 4.0.0 3.10.2 From c2cfab3a1c3f9931c550136028a53e5ab8f7f69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 13 Dec 2019 17:47:54 +0100 Subject: [PATCH 375/602] Fix a small documentation error --- docs/src/main/asciidoc/mongodb-panache.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 94ad05f159664..6591e298dc8da 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -312,7 +312,7 @@ MongoDB with Panache will then map it to a MongoDB native query. If your query does not start with `{`, we will consider it a PanacheQL query: -- `` (and single parameter) which will expand to `{'singleColumnName': '?'}` +- `` (and single parameter) which will expand to `{'singleColumnName': '?'}` - `` will expand to `{}` where we will map the PanacheQL query to MongoDB native query form. We support the following operators that will be mapped to the corresponding MongoDB operators: 'and', 'or' ( mixing 'and' and 'or' is not currently supported), '=', '>', '>=', '<', '<=', '!=', 'is null', 'is not null', and 'like' that is mapped to the MongoDB `$regex` operator. Here are some query examples: From 4ce618ea654ca139cc3f1a7119e7594132fe5473 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 17 Dec 2019 09:50:50 +0100 Subject: [PATCH 376/602] ArC - activate request context for any observer notification - resolves #6221 --- docs/src/main/asciidoc/cdi-reference.adoc | 12 +++ .../java/io/quarkus/arc/impl/EventImpl.java | 63 ++++++------- .../beans/BuiltInBeansAreResolvableTest.java | 35 ++++--- .../RequestInObserverNotificationTest.java | 91 +++++++++++++++++++ 4 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/request/RequestInObserverNotificationTest.java diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 7016ad06a7c0d..69c5b4a256f31 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -251,6 +251,18 @@ class MyBeanStarter { NOTE: Quarkus users are encouraged to always prefer the `@Observes StartupEvent` to `@Initialized(ApplicationScoped.class)` as explained in the link:lifecycle[Application Initialization and Termination] guide. +=== Request Context Lifecycle + +The request context is also active: + +* during notification of a synchronous observer method. + +The request context is destroyed: + +* after the observer notification completes for an event, if it was not already active when the notification started. + +NOTE: An event with qualifier `@Initialized(RequestScoped.class)` is fired when the request context is initialized for an observer notification. Moreover, the events with qualifiers `@BeforeDestroyed(RequestScoped.class)` and `@Destroyed(RequestScoped.class)` are fired when the request context is destroyed. + === Qualified Injected Fields In CDI, if you declare a field injection point you need to use `@Inject` and optionally a set of qualifiers: diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java index d3b112f13daa8..211e908129ca2 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java @@ -1,7 +1,6 @@ package io.quarkus.arc.impl; import io.quarkus.arc.Arc; -import io.quarkus.arc.ArcContainer; import io.quarkus.arc.ManagedContext; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; @@ -20,7 +19,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.function.Supplier; -import javax.enterprise.context.RequestScoped; import javax.enterprise.event.Event; import javax.enterprise.event.NotificationOptions; import javax.enterprise.event.ObserverException; @@ -86,28 +84,17 @@ public CompletionStage fireAsync(U event, NotificationOptions o return AsyncEventDeliveryStage.completed(event, executor); } - Supplier notifyLogic = () -> { - ObserverExceptionHandler exceptionHandler = new CollectingExceptionHandler(); - notifier.notify(event, exceptionHandler, true); - handleExceptions(exceptionHandler); - return event; - }; - - Supplier withinRequest = () -> { - ArcContainer container = Arc.container(); - if (container.getActiveContext(RequestScoped.class) != null) { - return notifyLogic.get(); - } else { - ManagedContext requestContext = container.requestContext(); - try { - requestContext.activate(); - return notifyLogic.get(); - } finally { - requestContext.terminate(); - } + Supplier notifyLogic = new Supplier() { + @Override + public U get() { + ObserverExceptionHandler exceptionHandler = new CollectingExceptionHandler(); + notifier.notify(event, exceptionHandler, true); + handleExceptions(exceptionHandler); + return event; } }; - CompletableFuture completableFuture = CompletableFuture.supplyAsync(withinRequest, executor); + + CompletableFuture completableFuture = CompletableFuture.supplyAsync(notifyLogic, executor); return new AsyncEventDeliveryStage<>(completableFuture, executor); } @@ -214,17 +201,31 @@ void notify(T event) { notify(event, ObserverExceptionHandler.IMMEDIATE_HANDLER, false); } - @SuppressWarnings({ "rawtypes", "unchecked" }) void notify(T event, ObserverExceptionHandler exceptionHandler, boolean async) { if (!isEmpty()) { - EventContext eventContext = new EventContextImpl<>(event, eventMetadata); - for (ObserverMethod observerMethod : observerMethods) { - if (observerMethod.isAsync() == async) { - try { - observerMethod.notify(eventContext); - } catch (Throwable e) { - exceptionHandler.handle(e); - } + ManagedContext requestContext = Arc.container().requestContext(); + if (requestContext.isActive()) { + notifyObservers(event, exceptionHandler, async); + } else { + try { + requestContext.activate(); + notifyObservers(event, exceptionHandler, async); + } finally { + requestContext.terminate(); + } + } + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void notifyObservers(T event, ObserverExceptionHandler exceptionHandler, boolean async) { + EventContext eventContext = new EventContextImpl<>(event, eventMetadata); + for (ObserverMethod observerMethod : observerMethods) { + if (observerMethod.isAsync() == async) { + try { + observerMethod.notify(eventContext); + } catch (Throwable e) { + exceptionHandler.handle(e); } } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/builtin/beans/BuiltInBeansAreResolvableTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/builtin/beans/BuiltInBeansAreResolvableTest.java index f0d79c20627d4..ee47e83bb8fd2 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/builtin/beans/BuiltInBeansAreResolvableTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/builtin/beans/BuiltInBeansAreResolvableTest.java @@ -49,6 +49,7 @@ public void testBeanManagerBean() { assertFalse(instance.select(BeanManager.class, new DummyQualifier.Literal()).isResolvable()); } + @SuppressWarnings({ "serial", "unchecked", "rawtypes" }) @Test public void testEventBean() { // use dynamic resolution to test it @@ -57,33 +58,34 @@ public void testEventBean() { Instance rawNoQualifier = instance.select(Event.class); assertTrue(rawNoQualifier.isResolvable()); DummyBean.resetCounters(); - rawNoQualifier.get().fire(new Object()); - assertEquals(0, DummyBean.noQualifiers); + rawNoQualifier.get().fire(new Payload()); + assertEquals(1, DummyBean.noQualifiers); assertEquals(0, DummyBean.qualifierTimesTriggered); - assertEquals(1, DummyBean.objectTimesTriggered); + assertEquals(1, DummyBean.timesTriggered); // event with type and no qualifier - Instance> typedEventNoQualifier = instance.select(new TypeLiteral>() { + Instance> typedEventNoQualifier = instance.select(new TypeLiteral>() { }); assertTrue(typedEventNoQualifier.isResolvable()); DummyBean.resetCounters(); - typedEventNoQualifier.get().fire("foo"); + typedEventNoQualifier.get().fire(new Payload()); assertEquals(1, DummyBean.noQualifiers); assertEquals(0, DummyBean.qualifierTimesTriggered); - assertEquals(1, DummyBean.objectTimesTriggered); + assertEquals(1, DummyBean.timesTriggered); // event with type and qualifier - Instance> typedEventWithQualifier = instance.select(new TypeLiteral>() { + Instance> typedEventWithQualifier = instance.select(new TypeLiteral>() { }, new DummyQualifier.Literal()); assertTrue(typedEventWithQualifier.isResolvable()); DummyBean.resetCounters(); - typedEventWithQualifier.get().fire("foo"); + typedEventWithQualifier.get().fire(new Payload()); assertEquals(1, DummyBean.noQualifiers); assertEquals(1, DummyBean.qualifierTimesTriggered); - assertEquals(1, DummyBean.objectTimesTriggered); + assertEquals(1, DummyBean.timesTriggered); } + @SuppressWarnings({ "serial", "unchecked" }) @Test public void testInstanceBean() { BeanManager bm = Arc.container().beanManager(); @@ -202,24 +204,24 @@ static class DummyBean { public static int noQualifiers = 0; public static int qualifierTimesTriggered = 0; - public static int objectTimesTriggered = 0; + public static int timesTriggered = 0; - public void observeEvent(@Observes String payload) { + public void observeEvent(@Observes Payload payload) { noQualifiers++; } - public void observeQualifiedEvent(@Observes @DummyQualifier String payload) { + public void observeQualifiedEvent(@Observes @DummyQualifier Payload payload) { qualifierTimesTriggered++; } - public void observeBasicEvent(@Observes @Any Object objPayload) { - objectTimesTriggered++; + public void observeBasicEvent(@Observes @Any Payload payload) { + timesTriggered++; } public static void resetCounters() { noQualifiers = 0; qualifierTimesTriggered = 0; - objectTimesTriggered = 0; + timesTriggered = 0; } public void ping() { @@ -227,4 +229,7 @@ public void ping() { ; } + + static class Payload { + } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/request/RequestInObserverNotificationTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/request/RequestInObserverNotificationTest.java new file mode 100644 index 0000000000000..4151e4bed7b00 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/request/RequestInObserverNotificationTest.java @@ -0,0 +1,91 @@ +package io.quarkus.arc.test.observers.request; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.ManagedContext; +import io.quarkus.arc.test.ArcTestContainer; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.event.Observes; +import javax.inject.Singleton; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class RequestInObserverNotificationTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(RequestFoo.class, MyObserver.class); + + @Test + public void testObserverNotification() { + ArcContainer container = Arc.container(); + AtomicReference msg = new AtomicReference(); + RequestFoo.DESTROYED.set(false); + + // Request context should be activated automatically + container.beanManager().getEvent().select(AtomicReference.class).fire(msg); + String fooId1 = msg.get(); + assertNotNull(fooId1); + assertTrue(RequestFoo.DESTROYED.get()); + + RequestFoo.DESTROYED.set(false); + msg.set(null); + + ManagedContext requestContext = container.requestContext(); + assertFalse(requestContext.isActive()); + try { + requestContext.activate(); + String fooId2 = container.instance(RequestFoo.class).get().getId(); + assertNotEquals(fooId1, fooId2); + container.beanManager().getEvent().select(AtomicReference.class).fire(msg); + assertEquals(fooId2, msg.get()); + } finally { + requestContext.terminate(); + } + assertTrue(RequestFoo.DESTROYED.get()); + } + + @Singleton + static class MyObserver { + + @SuppressWarnings({ "rawtypes", "unchecked" }) + void observeString(@Observes AtomicReference value, RequestFoo foo) { + // does not trigger ContextNotActiveException + value.set(foo.getId()); + } + + } + + @RequestScoped + static class RequestFoo { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(); + + private String id; + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + } + + public String getId() { + return id; + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + } + +} From 5929dbdc17ca1dac38c59159f0f462255b092d16 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 17 Dec 2019 15:47:59 +0200 Subject: [PATCH 377/602] Add a dev-mode test for the Spring Data JPA module This probably should have been done as part of #6216 but time was of the essence for that one --- extensions/spring-data-jpa/deployment/pom.xml | 15 +++++++ .../io/quarkus/spring/data/devmode/Book.java | 28 +++++++++++++ .../spring/data/devmode/BookRepository.java | 12 ++++++ .../spring/data/devmode/BookResource.java | 27 ++++++++++++ .../data/devmode/RepositoryReloadTest.java | 41 +++++++++++++++++++ .../src/test/resources/application.properties | 6 +++ .../deployment/src/test/resources/import.sql | 3 ++ 7 files changed, 132 insertions(+) create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/Book.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookRepository.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookResource.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadTest.java create mode 100644 extensions/spring-data-jpa/deployment/src/test/resources/application.properties create mode 100644 extensions/spring-data-jpa/deployment/src/test/resources/import.sql diff --git a/extensions/spring-data-jpa/deployment/pom.xml b/extensions/spring-data-jpa/deployment/pom.xml index 704f6f327293a..8b712b77862a8 100644 --- a/extensions/spring-data-jpa/deployment/pom.xml +++ b/extensions/spring-data-jpa/deployment/pom.xml @@ -33,6 +33,21 @@ quarkus-junit5-internal test + + io.quarkus + quarkus-resteasy-jackson-deployment + test + + + io.quarkus + quarkus-jdbc-h2 + test + + + io.rest-assured + rest-assured + test + diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/Book.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/Book.java new file mode 100644 index 0000000000000..d89580f3cf651 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/Book.java @@ -0,0 +1,28 @@ +package io.quarkus.spring.data.devmode; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class Book { + + @Id + private Integer bid; + private String name; + + public Integer getBid() { + return bid; + } + + public void setBid(Integer bid) { + this.bid = bid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookRepository.java new file mode 100644 index 0000000000000..6d0fcb9a034cd --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookRepository.java @@ -0,0 +1,12 @@ +package io.quarkus.spring.data.devmode; + +import java.util.List; + +import org.springframework.data.repository.Repository; + +public interface BookRepository extends Repository { + + List findAll(); + + // +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookResource.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookResource.java new file mode 100644 index 0000000000000..d1ca42ec255d3 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/BookResource.java @@ -0,0 +1,27 @@ +package io.quarkus.spring.data.devmode; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/book") +public class BookResource { + + private final BookRepository bookRepository; + + public BookResource(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List findAll() { + return bookRepository.findAll(); + } + + // + +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadTest.java new file mode 100644 index 0000000000000..883cd5812e762 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadTest.java @@ -0,0 +1,41 @@ +package io.quarkus.spring.data.devmode; + +import static org.hamcrest.CoreMatchers.containsString; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class RepositoryReloadTest { + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application.properties") + .addAsResource("import.sql") + .addClasses(Book.class, BookRepository.class, BookResource.class)); + + @Test + public void testRepositoryIsReloaded() { + RestAssured.get("/book").then() + .statusCode(200) + .body(containsString("Strangers"), containsString("Ascent"), containsString("Everything")); + + TEST.modifySourceFile("BookRepository.java", s -> s.replace("// ", + "java.util.Optional findById(Integer id);")); + + TEST.modifySourceFile("BookResource.java", s -> s.replace("// ", + "@GET @Path(\"/{id}\") @Produces(MediaType.APPLICATION_JSON)\n" + + " public java.util.Optional findById(@javax.ws.rs.PathParam(\"id\") Integer id) {\n" + + " return bookRepository.findById(id);\n" + + " }")); + + RestAssured.get("/book/1").then() + .statusCode(200) + .body(containsString("Strangers")); + } +} diff --git a/extensions/spring-data-jpa/deployment/src/test/resources/application.properties b/extensions/spring-data-jpa/deployment/src/test/resources/application.properties new file mode 100644 index 0000000000000..5fa748b04401f --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/resources/application.properties @@ -0,0 +1,6 @@ +quarkus.datasource.url=jdbc:h2:mem:test +quarkus.datasource.driver=org.h2.Driver + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.database.generation=drop-and-create \ No newline at end of file diff --git a/extensions/spring-data-jpa/deployment/src/test/resources/import.sql b/extensions/spring-data-jpa/deployment/src/test/resources/import.sql new file mode 100644 index 0000000000000..f837f5ee00f98 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/resources/import.sql @@ -0,0 +1,3 @@ +INSERT INTO book(bid, name) VALUES (1, 'Talking to Strangers'); +INSERT INTO book(bid, name) VALUES (2, 'The Ascent of Money'); +INSERT INTO book(bid, name) VALUES (3, 'A Short History of Everything'); \ No newline at end of file From 71796f7a2f1220e8a02efca1cd65d8723fb252a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 16 Dec 2019 17:12:58 +0100 Subject: [PATCH 378/602] Upgrade to Hibernate Search 6.0.0.Beta3 --- bom/runtime/pom.xml | 4 +- build-parent/pom.xml | 4 +- .../hibernate-search-elasticsearch.adoc | 2 +- .../elasticsearch/HibernateSearchClasses.java | 33 +------- ...HibernateSearchElasticsearchProcessor.java | 81 +++++++------------ .../HibernateSearchElasticsearchRecorder.java | 4 +- ...rnateSearchElasticsearchRuntimeConfig.java | 51 ++++++++++-- 7 files changed, 87 insertions(+), 92 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 830a135c7dce4..5baf62928319d 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -83,12 +83,12 @@ 1.3.4 6.1.0.Final 5.4.10.Final - 6.0.0.Beta2 + 6.0.0.Beta3 5.10.0.Final 1.1.1.Final 1.7 7.6.0.Final - 7.4.0 + 7.5.0 1.3.8 2.2.15 1.0.6.Final diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 73892e02b9cbe..02b51b4882b32 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -16,8 +16,8 @@ - 6.14 - 7.4.0 + 6.15 + 7.5.0 4.1.1 diff --git a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc index a93e1ffdea5be..fc08f451b6e66 100644 --- a/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc +++ b/docs/src/main/asciidoc/hibernate-search-elasticsearch.adoc @@ -594,7 +594,7 @@ Let's use Docker to start one of each: [source, shell] ---- -docker run -it --rm=true --name elasticsearch_quarkus_test -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.4.0 +docker run -it --rm=true --name elasticsearch_quarkus_test -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.5.0 ---- [source, shell] diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchClasses.java b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchClasses.java index 4d152d6cba025..dac09fea84658 100644 --- a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchClasses.java +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchClasses.java @@ -25,42 +25,17 @@ import org.hibernate.search.backend.elasticsearch.document.model.esnative.impl.RoutingType; import org.hibernate.search.backend.elasticsearch.index.settings.esnative.impl.Analysis; import org.hibernate.search.backend.elasticsearch.index.settings.esnative.impl.IndexSettings; -import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.declaration.MarkerBinding; -import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.declaration.PropertyBinding; -import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.declaration.RoutingKeyBinding; -import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.declaration.TypeBinding; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AssociationInverseSide; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; -import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.processing.TypeMapping; import org.jboss.jandex.DotName; class HibernateSearchClasses { static final DotName INDEXED = DotName.createSimple(Indexed.class.getName()); - static final List FIELD_ANNOTATIONS = Arrays.asList( - DotName.createSimple(DocumentId.class.getName()), - DotName.createSimple(GenericField.class.getName()), - DotName.createSimple(FullTextField.class.getName()), - DotName.createSimple(KeywordField.class.getName()), - DotName.createSimple(ScaledNumberField.class.getName()), - DotName.createSimple(IndexedEmbedded.class.getName()), - DotName.createSimple(AssociationInverseSide.class.getName()), - DotName.createSimple(IndexingDependency.class.getName())); - - static final List BINDING_DECLARATION_ANNOTATIONS_ON_PROPERTIES = Arrays.asList( - DotName.createSimple(PropertyBinding.class.getName()), - DotName.createSimple(MarkerBinding.class.getName())); - - static final List BINDING_DECLARATION_ANNOTATIONS_ON_TYPES = Arrays.asList( - DotName.createSimple(TypeBinding.class.getName()), - DotName.createSimple(RoutingKeyBinding.class.getName())); + static final DotName PROPERTY_MAPPING_META_ANNOTATION = DotName.createSimple( + org.hibernate.search.mapper.pojo.mapping.definition.annotation.processing.PropertyMapping.class.getName()); + static final DotName TYPE_MAPPING_META_ANNOTATION = DotName.createSimple(TypeMapping.class.getName()); static final List SCHEMA_MAPPING_CLASSES = Arrays.asList( DotName.createSimple(AbstractTypeMapping.class.getName()), diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java index 3a47a962aae21..f14be39c53e95 100644 --- a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java @@ -1,10 +1,9 @@ package io.quarkus.hibernate.search.elasticsearch; -import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.BINDING_DECLARATION_ANNOTATIONS_ON_PROPERTIES; -import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.BINDING_DECLARATION_ANNOTATIONS_ON_TYPES; -import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.FIELD_ANNOTATIONS; import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.INDEXED; +import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.PROPERTY_MAPPING_META_ANNOTATION; import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.SCHEMA_MAPPING_CLASSES; +import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.TYPE_MAPPING_META_ANNOTATION; import java.util.ArrayList; import java.util.HashSet; @@ -130,7 +129,6 @@ private static void checkConfig(HibernateSearchElasticsearchBuildTimeConfig buil private void registerReflection(IndexView index, BuildProducer reflectiveClass, BuildProducer reflectiveHierarchy) { Set reflectiveClassCollector = new HashSet<>(); - Set reflectiveTypeCollector = new HashSet<>(); if (buildTimeConfig.defaultBackend.analysis.configurer.isPresent()) { reflectiveClass.produce( @@ -142,53 +140,38 @@ private void registerReflection(IndexView index, BuildProducer reflectiveHierarchyCollector = new HashSet<>(); + + for (AnnotationInstance propertyMappingMetaAnnotationInstance : index + .getAnnotations(PROPERTY_MAPPING_META_ANNOTATION)) { + for (AnnotationInstance propertyMappingAnnotationInstance : index + .getAnnotations(propertyMappingMetaAnnotationInstance.name())) { + AnnotationTarget annotationTarget = propertyMappingAnnotationInstance.target(); if (annotationTarget.kind() == Kind.FIELD) { FieldInfo fieldInfo = annotationTarget.asField(); - addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, fieldInfo.declaringClass()); - addReflectiveType(index, reflectiveTypeCollector, fieldInfo.type()); + addReflectiveClass(index, reflectiveClassCollector, reflectiveHierarchyCollector, + fieldInfo.declaringClass()); + addReflectiveType(index, reflectiveClassCollector, reflectiveHierarchyCollector, + fieldInfo.type()); } else if (annotationTarget.kind() == Kind.METHOD) { MethodInfo methodInfo = annotationTarget.asMethod(); - addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, methodInfo.declaringClass()); - addReflectiveType(index, reflectiveTypeCollector, methodInfo.returnType()); + addReflectiveClass(index, reflectiveClassCollector, reflectiveHierarchyCollector, + methodInfo.declaringClass()); + addReflectiveType(index, reflectiveClassCollector, reflectiveHierarchyCollector, + methodInfo.returnType()); } } } - Set reflectiveHierarchyCollector = new HashSet<>(); - - for (DotName bindingDeclarationOnProperties : BINDING_DECLARATION_ANNOTATIONS_ON_PROPERTIES) { - for (AnnotationInstance propertyBridgeMappingInstance : index.getAnnotations(bindingDeclarationOnProperties)) { - for (AnnotationInstance propertyBridgeInstance : index.getAnnotations(propertyBridgeMappingInstance.name())) { - AnnotationTarget annotationTarget = propertyBridgeInstance.target(); - if (annotationTarget.kind() == Kind.FIELD) { - FieldInfo fieldInfo = annotationTarget.asField(); - addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, - fieldInfo.declaringClass()); - reflectiveHierarchyCollector.add(fieldInfo.type()); - } else if (annotationTarget.kind() == Kind.METHOD) { - MethodInfo methodInfo = annotationTarget.asMethod(); - addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, - methodInfo.declaringClass()); - reflectiveHierarchyCollector.add(methodInfo.returnType()); - } - } - } - } - - for (DotName bindingDeclarationOnTypes : BINDING_DECLARATION_ANNOTATIONS_ON_TYPES) { - for (AnnotationInstance typeBridgeMappingInstance : index.getAnnotations(bindingDeclarationOnTypes)) { - for (AnnotationInstance typeBridgeInstance : index.getAnnotations(typeBridgeMappingInstance.name())) { - addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, - typeBridgeInstance.target().asClass()); - } + for (AnnotationInstance typeBridgeMappingInstance : index.getAnnotations(TYPE_MAPPING_META_ANNOTATION)) { + for (AnnotationInstance typeBridgeInstance : index.getAnnotations(typeBridgeMappingInstance.name())) { + addReflectiveClass(index, reflectiveClassCollector, reflectiveHierarchyCollector, + typeBridgeInstance.target().asClass()); } } String[] reflectiveClasses = Stream - .of(reflectiveClassCollector.stream(), reflectiveTypeCollector.stream(), SCHEMA_MAPPING_CLASSES.stream()) + .of(reflectiveClassCollector.stream(), SCHEMA_MAPPING_CLASSES.stream()) .flatMap(Function.identity()).map(c -> c.toString()).toArray(String[]::new); reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, reflectiveClasses)); @@ -198,7 +181,7 @@ private void registerReflection(IndexView index, BuildProducer reflectiveClassCollector, - Set reflectiveTypeCollector, ClassInfo classInfo) { + Set reflectiveTypeCollector, ClassInfo classInfo) { if (skipClass(classInfo.name(), reflectiveClassCollector)) { return; } @@ -219,29 +202,27 @@ private static void addReflectiveClass(IndexView index, Set reflectiveC } else if (superClassType instanceof ParameterizedType) { ParameterizedType parameterizedType = superClassType.asParameterizedType(); for (Type typeArgument : parameterizedType.arguments()) { - addReflectiveType(index, reflectiveTypeCollector, typeArgument); + addReflectiveType(index, reflectiveClassCollector, reflectiveTypeCollector, typeArgument); } superClassType = parameterizedType.owner(); } } } - private static void addReflectiveType(IndexView index, Set reflectiveTypeCollector, Type type) { + private static void addReflectiveType(IndexView index, Set reflectiveClassCollector, + Set reflectiveTypeCollector, Type type) { if (type instanceof VoidType || type instanceof PrimitiveType || type instanceof UnresolvedTypeVariable) { return; } else if (type instanceof ClassType) { - if (skipClass(type.name(), reflectiveTypeCollector)) { - return; - } - - reflectiveTypeCollector.add(type.name()); + ClassInfo classInfo = index.getClassByName(type.name()); + addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, classInfo); } else if (type instanceof ArrayType) { - addReflectiveType(index, reflectiveTypeCollector, type.asArrayType().component()); + addReflectiveType(index, reflectiveClassCollector, reflectiveTypeCollector, type.asArrayType().component()); } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = type.asParameterizedType(); - addReflectiveType(index, reflectiveTypeCollector, parameterizedType.owner()); + addReflectiveType(index, reflectiveClassCollector, reflectiveTypeCollector, parameterizedType.owner()); for (Type typeArgument : parameterizedType.arguments()) { - addReflectiveType(index, reflectiveTypeCollector, typeArgument); + addReflectiveType(index, reflectiveClassCollector, reflectiveTypeCollector, typeArgument); } } } diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java index f8aeabead98bf..a50b41de6d1b2 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRecorder.java @@ -124,6 +124,8 @@ private void contributeBackendRuntimeProperties(BiConsumer prope ElasticsearchBackendRuntimeConfig elasticsearchBackendConfig) { addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.HOSTS, elasticsearchBackendConfig.hosts); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PROTOCOL, + elasticsearchBackendConfig.protocol.getHibernateSearchString()); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.USERNAME, elasticsearchBackendConfig.username); addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PASSWORD, @@ -140,8 +142,6 @@ private void contributeBackendRuntimeProperties(BiConsumer prope if (elasticsearchBackendConfig.discovery.enabled) { addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_REFRESH_INTERVAL, elasticsearchBackendConfig.discovery.refreshInterval.getSeconds()); - addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_SCHEME, - elasticsearchBackendConfig.discovery.defaultScheme); } addBackendDefaultIndexConfig(propertyCollector, backendName, ElasticsearchIndexSettings.LIFECYCLE_STRATEGY, diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java index 04aee3fccd968..89e067def4547 100644 --- a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -9,6 +10,8 @@ import org.hibernate.search.backend.elasticsearch.index.IndexStatus; import org.hibernate.search.mapper.orm.automaticindexing.AutomaticIndexingSynchronizationStrategyName; import org.hibernate.search.mapper.orm.search.loading.EntityLoadingCacheLookupStrategy; +import org.hibernate.search.util.common.SearchException; +import org.hibernate.search.util.common.impl.StringHelper; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; @@ -62,9 +65,16 @@ public static class ElasticsearchBackendRuntimeConfig { /** * The list of hosts of the Elasticsearch servers. */ - @ConfigItem(defaultValue = "http://localhost:9200") + @ConfigItem(defaultValue = "localhost:9200") List hosts; + /** + * The protocol to use when contacting Elasticsearch servers. + * Set to "https" to enable SSL/TLS. + */ + @ConfigItem(defaultValue = "http") + ElasticsearchClientProtocol protocol; + /** * The username used for authentication. */ @@ -115,6 +125,40 @@ public static class ElasticsearchBackendRuntimeConfig { Map indexes; } + public enum ElasticsearchClientProtocol { + /** + * Use clear-text HTTP, with SSL/TLS disabled. + */ + HTTP("http"), + /** + * Use HTTPS, with SSL/TLS enabled. + */ + HTTPS("https"); + + public static ElasticsearchClientProtocol of(String value) { + return StringHelper.parseDiscreteValues( + values(), + ElasticsearchClientProtocol::getHibernateSearchString, + (invalidValue, validValues) -> new SearchException( + String.format( + Locale.ROOT, + "Invalid protocol: '%1$s'. Valid protocols are: %2$s.", + invalidValue, + validValues)), + value); + } + + private final String hibernateSearchString; + + ElasticsearchClientProtocol(String hibernateSearchString) { + this.hibernateSearchString = hibernateSearchString; + } + + public String getHibernateSearchString() { + return hibernateSearchString; + } + } + @ConfigGroup public static class ElasticsearchIndexConfig { /** @@ -139,11 +183,6 @@ public static class DiscoveryConfig { @ConfigItem(defaultValue = "10S") Duration refreshInterval; - /** - * The scheme that should be used for the new nodes discovered. - */ - @ConfigItem(defaultValue = "http") - String defaultScheme; } @ConfigGroup From 681ca053401c0d38b9c45d84b9e7c13f8145a46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 16 Dec 2019 17:35:47 +0100 Subject: [PATCH 379/602] Filter out the new "version" log from Hibernate Search on startup --- .../HibernateSearchElasticsearchProcessor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java index f14be39c53e95..7598343d7f25f 100644 --- a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java @@ -38,6 +38,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationBuildItem; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfig; @@ -51,6 +52,11 @@ class HibernateSearchElasticsearchProcessor { HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig; + @BuildStep + void setupLogFilters(BuildProducer filters) { + filters.produce(new LogCleanupFilterBuildItem("org.hibernate.search.engine.Version", "HSEARCH000034")); + } + @BuildStep @Record(ExecutionTime.STATIC_INIT) public void build(HibernateSearchElasticsearchRecorder recorder, From 4fa53afe0e7be14ee213b81040399f2c0401a774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 17 Dec 2019 10:43:28 +0100 Subject: [PATCH 380/602] Add more assertions for mass indexing in Hibernate Search --- .../search/HibernateSearchTestResource.java | 37 +++++++++++++++++++ .../elasticsearch/HibernateSearchTest.java | 15 ++++++++ 2 files changed, 52 insertions(+) diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/it/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/it/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java index 120355f19e14f..084b7bf4bae77 100644 --- a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/it/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/it/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java @@ -59,6 +59,43 @@ public String testSearch() { return "OK"; } + @PUT + @Path("/purge") + @Produces(MediaType.TEXT_PLAIN) + public String testPurge() { + SearchSession searchSession = Search.session(entityManager); + + searchSession.workspace().purge(); + + return "OK"; + } + + @PUT + @Path("/flush") + @Produces(MediaType.TEXT_PLAIN) + public String testFlush() { + SearchSession searchSession = Search.session(entityManager); + + searchSession.workspace().flush(); + + return "OK"; + } + + @GET + @Path("/search-empty") + @Produces(MediaType.TEXT_PLAIN) + public String testSearchEmpty() { + SearchSession searchSession = Search.session(entityManager); + + List person = searchSession.search(Person.class) + .predicate(f -> f.matchAll()) + .fetchHits(20); + + assertEquals(0, person.size()); + + return "OK"; + } + @PUT @Path("/mass-indexer") @Produces(MediaType.TEXT_PLAIN) diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/it/hibernate/search/elasticsearch/HibernateSearchTest.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/it/hibernate/search/elasticsearch/HibernateSearchTest.java index fa8437989badd..503ca7805ddb6 100644 --- a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/it/hibernate/search/elasticsearch/HibernateSearchTest.java +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/it/hibernate/search/elasticsearch/HibernateSearchTest.java @@ -19,8 +19,23 @@ public void testSearch() throws Exception { .statusCode(200) .body(is("OK")); + RestAssured.when().put("/test/hibernate-search/purge").then() + .statusCode(200) + .body(is("OK")); + + RestAssured.when().put("/test/hibernate-search/flush").then() + .statusCode(200) + .body(is("OK")); + + RestAssured.when().get("/test/hibernate-search/search-empty").then() + .statusCode(200); + RestAssured.when().put("/test/hibernate-search/mass-indexer").then() .statusCode(200) .body(is("OK")); + + RestAssured.when().get("/test/hibernate-search/search").then() + .statusCode(200) + .body(is("OK")); } } From 73a8b71ebd48b6bc8fe56c120614ececa017d6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 17 Dec 2019 15:53:24 +0100 Subject: [PATCH 381/602] Work around a problem with Version.logVersion in Hibernate Search 6.0.0.Beta3 --- .../elasticsearch/HibernateSearchElasticsearchProcessor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java index 7598343d7f25f..fc826d3d807ce 100644 --- a/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java @@ -136,6 +136,10 @@ private void registerReflection(IndexView index, BuildProducer reflectiveHierarchy) { Set reflectiveClassCollector = new HashSet<>(); + // TODO remove this when upgrading to Beta4 (when https://hibernate.atlassian.net/browse/HSEARCH-3795 is solved) + reflectiveClass.produce( + new ReflectiveClassBuildItem(false, false, org.hibernate.search.engine.logging.impl.Log_$logger.class)); + if (buildTimeConfig.defaultBackend.analysis.configurer.isPresent()) { reflectiveClass.produce( new ReflectiveClassBuildItem(true, false, buildTimeConfig.defaultBackend.analysis.configurer.get())); From a44bdc47d81328a8cb6ec8e15de022c0b4b3babb Mon Sep 17 00:00:00 2001 From: Andreas Paschwitz <842659+paschi@users.noreply.github.com> Date: Tue, 17 Dec 2019 19:20:12 +0100 Subject: [PATCH 382/602] Fix file system path for overriding application.properties when using dev-mode with Gradle --- docs/src/main/asciidoc/config.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 51f54b34dedd0..dc517ade7abfb 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -396,7 +396,7 @@ _will also_ be taken into account. NOTE: Environment variables names are following the conversion rules of link:https://github.com/eclipse/microprofile-config/blob/master/spec/src/main/asciidoc/configsources.asciidoc#default-configsources[Eclipse MicroProfile] -NOTE: The `config/application.properties` features is available in development mode as well. To make use of it, `config/application.properties` needs to be placed inside the build tool's output directory (`target` for Maven and `build` for Gradle). +NOTE: The `config/application.properties` features is available in development mode as well. To make use of it, `config/application.properties` needs to be placed inside the build tool's output directory (`target` for Maven and `build/classes/java/main` for Gradle). Keep in mind however that any cleaning operation from the build tool like `mvn clean` or `gradle clean` will remove the `config` directory as well. === Configuration Profiles From d819a83d2922f97eb60894f8fb4a3e237e6f598b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 17 Dec 2019 18:57:54 +0100 Subject: [PATCH 383/602] Some housekeeping on our pom files --- bom/runtime/pom.xml | 5 +++++ extensions/amazon-lambda-http/maven-archetype/pom.xml | 4 ++-- extensions/amazon-lambda/maven-archetype/pom.xml | 4 ++-- extensions/azure-functions-http/maven-archetype/pom.xml | 4 ++-- integration-tests/amazon-lambda-http/pom.xml | 2 +- integration-tests/vault-agroal/pom.xml | 3 +-- integration-tests/vault-app/pom.xml | 3 +-- integration-tests/vault/pom.xml | 3 +-- test-framework/amazon-lambda/pom.xml | 2 +- test-framework/arquillian/pom.xml | 2 +- test-framework/{artemis-test => artemis}/pom.xml | 2 +- .../java/io/quarkus/artemis/test/ArtemisTestResource.java | 0 test-framework/common/pom.xml | 2 +- test-framework/derby/pom.xml | 2 +- test-framework/h2/pom.xml | 2 +- test-framework/junit5-internal/pom.xml | 2 +- test-framework/junit5/pom.xml | 2 +- test-framework/kubernetes-client/pom.xml | 2 +- test-framework/maven/pom.xml | 2 +- test-framework/pom.xml | 2 +- test-framework/vault/pom.xml | 4 ++-- 21 files changed, 28 insertions(+), 26 deletions(-) rename test-framework/{artemis-test => artemis}/pom.xml (97%) rename test-framework/{artemis-test => artemis}/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java (100%) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 5baf62928319d..723111254db5e 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -730,6 +730,11 @@ quarkus-vault-spi ${project.version} + + io.quarkus + quarkus-test-vault + ${project.version} + io.quarkus quarkus-logging-json diff --git a/extensions/amazon-lambda-http/maven-archetype/pom.xml b/extensions/amazon-lambda-http/maven-archetype/pom.xml index 674bd13dd27e9..93afd0c19456f 100644 --- a/extensions/amazon-lambda-http/maven-archetype/pom.xml +++ b/extensions/amazon-lambda-http/maven-archetype/pom.xml @@ -11,7 +11,7 @@ 4.0.0 quarkus-amazon-lambda-http-archetype - Quarkus - HTTP Amazon Lambda Archetype + Quarkus - Amazon Lambda HTTP - Archetype maven-archetype @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/extensions/amazon-lambda/maven-archetype/pom.xml b/extensions/amazon-lambda/maven-archetype/pom.xml index 2f9d454bdf47a..632b6f3d6f762 100644 --- a/extensions/amazon-lambda/maven-archetype/pom.xml +++ b/extensions/amazon-lambda/maven-archetype/pom.xml @@ -11,7 +11,7 @@ 4.0.0 quarkus-amazon-lambda-archetype - Quarkus - Amazon Lambda Archetype + Quarkus - Amazon Lambda - Archetype maven-archetype @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/extensions/azure-functions-http/maven-archetype/pom.xml b/extensions/azure-functions-http/maven-archetype/pom.xml index b2f009ed69dd7..29634eb650eb5 100644 --- a/extensions/azure-functions-http/maven-archetype/pom.xml +++ b/extensions/azure-functions-http/maven-archetype/pom.xml @@ -11,7 +11,7 @@ 4.0.0 quarkus-azure-functions-http-archetype - Quarkus - HTTP Azure Functions Archetype + Quarkus - HTTP Azure Functions - Archetype maven-archetype @@ -23,4 +23,4 @@ - \ No newline at end of file + diff --git a/integration-tests/amazon-lambda-http/pom.xml b/integration-tests/amazon-lambda-http/pom.xml index 7cb5aca960cbb..87128b9250dc6 100644 --- a/integration-tests/amazon-lambda-http/pom.xml +++ b/integration-tests/amazon-lambda-http/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-integration-test-amazon-lambda-http-it + quarkus-integration-test-amazon-lambda-http Quarkus - Integration Tests - Amazon Lambda HTTP Module that contains Amazon Lambda related tests for Resteasy diff --git a/integration-tests/vault-agroal/pom.xml b/integration-tests/vault-agroal/pom.xml index fc03ff62c9813..9e820b67041ee 100644 --- a/integration-tests/vault-agroal/pom.xml +++ b/integration-tests/vault-agroal/pom.xml @@ -22,8 +22,7 @@ io.quarkus - quarkus-vault-test - ${project.version} + quarkus-test-vault test diff --git a/integration-tests/vault-app/pom.xml b/integration-tests/vault-app/pom.xml index 95d6b6503c56e..70462be04f5b4 100644 --- a/integration-tests/vault-app/pom.xml +++ b/integration-tests/vault-app/pom.xml @@ -45,8 +45,7 @@ io.quarkus - quarkus-vault-test - ${project.version} + quarkus-test-vault test diff --git a/integration-tests/vault/pom.xml b/integration-tests/vault/pom.xml index 72ce157012712..fa16a90ed366b 100644 --- a/integration-tests/vault/pom.xml +++ b/integration-tests/vault/pom.xml @@ -22,8 +22,7 @@ io.quarkus - quarkus-vault-test - ${project.version} + quarkus-test-vault test diff --git a/test-framework/amazon-lambda/pom.xml b/test-framework/amazon-lambda/pom.xml index 1bf9abf414820..4b2dcdd0e4a49 100644 --- a/test-framework/amazon-lambda/pom.xml +++ b/test-framework/amazon-lambda/pom.xml @@ -12,7 +12,7 @@ quarkus-test-amazon-lambda - Quarkus - Amazon Lambda - Test Framework + Quarkus - Test Framework - Amazon Lambda diff --git a/test-framework/arquillian/pom.xml b/test-framework/arquillian/pom.xml index d55ebf86c2b1b..d606c917214b2 100644 --- a/test-framework/arquillian/pom.xml +++ b/test-framework/arquillian/pom.xml @@ -12,7 +12,7 @@ quarkus-arquillian - Quarkus - Arquillian Adapter + Quarkus - Test Framework - Arquillian Adapter diff --git a/test-framework/artemis-test/pom.xml b/test-framework/artemis/pom.xml similarity index 97% rename from test-framework/artemis-test/pom.xml rename to test-framework/artemis/pom.xml index 73dc2f11492d6..3a6fd4a9dcd25 100644 --- a/test-framework/artemis-test/pom.xml +++ b/test-framework/artemis/pom.xml @@ -11,7 +11,7 @@ 4.0.0 quarkus-test-artemis - Quarkus - Artemis - Test + Quarkus - Test Framework - Artemis Module that will hold all tests resources that can will be usable by Quarkus developers and users of the framework diff --git a/test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java b/test-framework/artemis/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java similarity index 100% rename from test-framework/artemis-test/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java rename to test-framework/artemis/src/main/java/io/quarkus/artemis/test/ArtemisTestResource.java diff --git a/test-framework/common/pom.xml b/test-framework/common/pom.xml index 61da99ff3c822..faa9a4d85a115 100644 --- a/test-framework/common/pom.xml +++ b/test-framework/common/pom.xml @@ -12,7 +12,7 @@ quarkus-test-common - Quarkus - Test framework - Common + Quarkus - Test Framework - Common diff --git a/test-framework/derby/pom.xml b/test-framework/derby/pom.xml index 952f01927d5d0..30391f7d32f10 100644 --- a/test-framework/derby/pom.xml +++ b/test-framework/derby/pom.xml @@ -12,7 +12,7 @@ quarkus-test-derby - Quarkus - Test framework - Derby Database Support + Quarkus - Test Framework - Derby Database Support io.quarkus diff --git a/test-framework/h2/pom.xml b/test-framework/h2/pom.xml index c50619a433f3a..f72de43f72d44 100644 --- a/test-framework/h2/pom.xml +++ b/test-framework/h2/pom.xml @@ -12,7 +12,7 @@ quarkus-test-h2 - Quarkus - Test framework - H2 Database Support + Quarkus - Test Framework - H2 Database Support io.quarkus diff --git a/test-framework/junit5-internal/pom.xml b/test-framework/junit5-internal/pom.xml index d11f7665c4af1..89028d57970e6 100644 --- a/test-framework/junit5-internal/pom.xml +++ b/test-framework/junit5-internal/pom.xml @@ -12,7 +12,7 @@ quarkus-junit5-internal - Quarkus - Test framework - JUnit 5 Internal Test Framework + Quarkus - Test Framework - JUnit 5 Internal Test Framework A runner for unit tests, intended for testing Quarkus rather than diff --git a/test-framework/junit5/pom.xml b/test-framework/junit5/pom.xml index 3f4e04e909802..d94e0b09dc8f5 100644 --- a/test-framework/junit5/pom.xml +++ b/test-framework/junit5/pom.xml @@ -12,7 +12,7 @@ quarkus-junit5 - Quarkus - Test framework - JUnit 5 + Quarkus - Test Framework - JUnit 5 diff --git a/test-framework/kubernetes-client/pom.xml b/test-framework/kubernetes-client/pom.xml index 2b90c1c78a7a1..d02fe6112316c 100644 --- a/test-framework/kubernetes-client/pom.xml +++ b/test-framework/kubernetes-client/pom.xml @@ -12,7 +12,7 @@ quarkus-test-kubernetes-client - Quarkus - Test framework - Kubernetes Client Mock Server support + Quarkus - Test Framework - Kubernetes Client Mock Server support io.quarkus diff --git a/test-framework/maven/pom.xml b/test-framework/maven/pom.xml index fae4cf29b56e6..7eec73c465171 100644 --- a/test-framework/maven/pom.xml +++ b/test-framework/maven/pom.xml @@ -12,7 +12,7 @@ quarkus-test-maven jar - Quarkus - Maven Integration Test Framework + Quarkus - Test Framework - Maven Integration diff --git a/test-framework/pom.xml b/test-framework/pom.xml index 8198712d1e6b5..1ae6b977c8e8d 100644 --- a/test-framework/pom.xml +++ b/test-framework/pom.xml @@ -14,7 +14,7 @@ Quarkus - Test Framework pom - artemis-test + artemis common h2 derby diff --git a/test-framework/vault/pom.xml b/test-framework/vault/pom.xml index c15a3fd4ad139..d55f134e668aa 100644 --- a/test-framework/vault/pom.xml +++ b/test-framework/vault/pom.xml @@ -11,8 +11,8 @@ ../ - quarkus-vault-test - Quarkus - Vault - Test + quarkus-test-vault + Quarkus - Test Framework - Vault From 5e44ebc0cbcfd8cb78ac45701a66cea7f13a6378 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 18 Dec 2019 06:57:11 +1100 Subject: [PATCH 384/602] Agroal dev mode test --- extensions/agroal/deployment/pom.xml | 10 +++++ .../agroal/test/AgroalDevModeTestCase.java | 43 +++++++++++++++++++ .../quarkus/agroal/test/DevModeResource.java | 31 +++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/AgroalDevModeTestCase.java create mode 100644 extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DevModeResource.java diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 92d20fa9b228a..6529345d5f716 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -40,6 +40,16 @@ + + io.quarkus + quarkus-resteasy-deployment + test + + + io.rest-assured + rest-assured + test + io.quarkus quarkus-junit5-internal diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/AgroalDevModeTestCase.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/AgroalDevModeTestCase.java new file mode 100644 index 0000000000000..56c4352b29548 --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/AgroalDevModeTestCase.java @@ -0,0 +1,43 @@ +package io.quarkus.agroal.test; + +import java.util.function.Supplier; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class AgroalDevModeTestCase { + + @RegisterExtension + public static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClass(DevModeResource.class) + .add(new StringAsset("quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:testing\n" + + "quarkus.datasource.driver=org.h2.Driver\n" + + "quarkus.datasource.username=USERNAME-NAMED\n"), "application.properties"); + } + }); + + @Test + public void testAgroalHotReplacement() { + RestAssured + .get("/dev/user") + .then() + .body(Matchers.equalTo("USERNAME-NAMED")); + test.modifyResourceFile("application.properties", s -> s.replace("USERNAME-NAMED", "OTHER-USER")); + + RestAssured + .get("/dev/user") + .then() + .body(Matchers.equalTo("OTHER-USER")); + } +} diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DevModeResource.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DevModeResource.java new file mode 100644 index 0000000000000..f9aa6c7f1ba61 --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DevModeResource.java @@ -0,0 +1,31 @@ +package io.quarkus.agroal.test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.inject.Inject; +import javax.sql.DataSource; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/dev") +public class DevModeResource { + + @Inject + DataSource dataSource; + + @GET + @Path("/user") + public String getUser() throws SQLException { + try (Connection c = dataSource.getConnection()) { + try (Statement s = c.createStatement()) { + try (ResultSet rs = s.executeQuery("SELECT USER()")) { + rs.next(); + return rs.getString(1); + } + } + } + } +} From 718fb4b715ff803bccd33d1c4d54b79ea19e484a Mon Sep 17 00:00:00 2001 From: Vincent Sevel Date: Mon, 25 Nov 2019 23:03:08 +0100 Subject: [PATCH 385/602] Add Support to Multiple Vault KV Paths - Fixes Issue 5638 #6174 --- .../runtime/config/VaultConfigSource.java | 85 ++++++++++++++++--- .../runtime/config/VaultRuntimeConfig.java | 30 ++++++- .../vault/VaultMultiPathConfigITCase.java | 42 +++++++++ .../application-vault-multi-path.properties | 7 ++ .../vault/test/VaultTestExtension.java | 6 ++ .../vault/src/main/resources/vault.policy | 5 ++ 6 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 integration-tests/vault/src/test/java/io/quarkus/vault/VaultMultiPathConfigITCase.java create mode 100644 integration-tests/vault/src/test/resources/application-vault-multi-path.properties diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java index 7af2a3196e066..68d2382a3ea45 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultConfigSource.java @@ -14,14 +14,18 @@ import static io.quarkus.vault.runtime.config.VaultRuntimeConfig.KV_SECRET_ENGINE_VERSION_V1; import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; +import static java.util.Collections.emptyMap; +import static java.util.Optional.empty; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; import java.util.AbstractMap.SimpleEntry; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -48,6 +52,8 @@ public class VaultConfigSource implements ConfigSource { private static final String PROPERTY_PREFIX = "quarkus.vault."; public static final Pattern CREDENTIAL_PATTERN = Pattern.compile("^quarkus\\.vault\\.credentials-provider\\.([^.]+)\\."); + public static final Pattern SECRET_CONFIG_KV_PATH_PATTERN = Pattern + .compile("^quarkus\\.vault\\.secret-config-kv-path\\.([^.]+)$"); private AtomicReference>> cache = new AtomicReference<>(null); private AtomicReference serverConfig = new AtomicReference<>(null); @@ -76,7 +82,7 @@ public int getOrdinal() { */ @Override public Map getProperties() { - return Collections.emptyMap(); + return emptyMap(); } @Override @@ -102,13 +108,19 @@ private Map getSecretConfig() { Map properties = new HashMap<>(); - if (serverConfig.secretConfigKvPath.isPresent()) { - try { - properties.putAll(fetchSecrets(serverConfig)); - log.debug("loaded " + properties.size() + " properties from vault"); - } catch (RuntimeException e) { - return tryReturnLastKnownValue(e, cacheEntry); + try { + // default kv paths + if (serverConfig.secretConfigKvPath.isPresent()) { + fetchSecrets(serverConfig.secretConfigKvPath.get(), null, properties); } + + // prefixed kv paths + serverConfig.secretConfigKvPrefixPath.entrySet() + .forEach(entry -> fetchSecrets(entry.getValue(), entry.getKey(), properties)); + + log.debug("loaded " + properties.size() + " properties from vault"); + } catch (RuntimeException e) { + return tryReturnLastKnownValue(e, cacheEntry); } cache.set(new VaultCacheEntry(properties)); @@ -116,11 +128,19 @@ private Map getSecretConfig() { } - private Map fetchSecrets(VaultRuntimeConfig serverConfig) { + private void fetchSecrets(List paths, String prefix, Map properties) { + paths.forEach(path -> properties.putAll(fetchSecrets(path, prefix))); + } + + private Map fetchSecrets(String path, String prefix) { VaultManager instance = getVaultManager(); - return instance == null - ? Collections.emptyMap() - : instance.getVaultKvManager().readSecret(serverConfig.secretConfigKvPath.get()); + return instance == null ? emptyMap() : prefixMap(instance.getVaultKvManager().readSecret(path), prefix); + } + + private Map prefixMap(Map map, String prefix) { + return prefix == null + ? map + : map.entrySet().stream().collect(toMap(entry -> prefix + "." + entry.getKey(), Map.Entry::getValue)); } // --- @@ -176,7 +196,7 @@ private VaultRuntimeConfig loadConfig() { getVaultProperty("kv-secret-engine-version", KV_SECRET_ENGINE_VERSION_V1)); serverConfig.kvSecretEngineMountPath = getVaultProperty("kv-secret-engine-mount-path", DEFAULT_KV_SECRET_ENGINE_MOUNT_PATH); - serverConfig.secretConfigKvPath = getOptionalVaultProperty("secret-config-kv-path"); + serverConfig.secretConfigKvPath = getOptionalListProperty("secret-config-kv-path"); serverConfig.tls.skipVerify = parseBoolean(getVaultProperty("tls.skip-verify", DEFAULT_TLS_SKIP_VERIFY)); serverConfig.tls.useKubernetesCaCert = parseBoolean( getVaultProperty("tls.use-kubernetes-ca-cert", DEFAULT_TLS_USE_KUBERNETES_CACERT)); @@ -185,10 +205,25 @@ private VaultRuntimeConfig loadConfig() { serverConfig.readTimeout = getVaultDuration("read-timeout", DEFAULT_READ_TIMEOUT); serverConfig.credentialsProvider = getCredentialsProviders(); + serverConfig.secretConfigKvPrefixPath = getSecretConfigKvPrefixPaths(); return serverConfig; } + private Optional> getOptionalListProperty(String name) { + + Optional optionalVaultProperty = getOptionalVaultProperty(name); + if (!optionalVaultProperty.isPresent()) { + return empty(); + } + + String[] split = optionalVaultProperty.get().split(","); + return Optional.of(Arrays.stream(split) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(toList())); + } + private Optional newURL(Optional url) { try { return Optional.ofNullable(url.isPresent() ? new URL(url.get()) : null); @@ -232,6 +267,17 @@ private Map getCredentialsProviders() { .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue)); } + private Map> getSecretConfigKvPrefixPaths() { + + return getConfigSourceStream() + .flatMap(configSource -> configSource.getPropertyNames().stream()) + .map(this::getSecretConfigKvPrefixPathName) + .filter(Objects::nonNull) + .distinct() + .map(this::createNameSecretConfigKvPrefixPathPair) + .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue)); + } + private Stream getConfigSourceStream() { Config config = ConfigProviderResolver.instance().getConfig(); return StreamSupport.stream(config.getConfigSources().spliterator(), false).filter(this::retain); @@ -252,11 +298,20 @@ private SimpleEntry createNameCredentialsProv return new SimpleEntry<>(name, getCredentialsProviderConfig(name)); } + private SimpleEntry> createNameSecretConfigKvPrefixPathPair(String name) { + return new SimpleEntry<>(name, getSecretConfigKvPrefixPath(name)); + } + private String getCredentialsProviderName(String propertyName) { Matcher matcher = CREDENTIAL_PATTERN.matcher(propertyName); return matcher.find() ? matcher.group(1) : null; } + private String getSecretConfigKvPrefixPathName(String propertyName) { + Matcher matcher = SECRET_CONFIG_KV_PATH_PATTERN.matcher(propertyName); + return matcher.find() ? matcher.group(1) : null; + } + private CredentialsProviderConfig getCredentialsProviderConfig(String name) { String prefix = "credentials-provider." + name; CredentialsProviderConfig config = new CredentialsProviderConfig(); @@ -266,4 +321,8 @@ private CredentialsProviderConfig getCredentialsProviderConfig(String name) { return config; } + private List getSecretConfigKvPrefixPath(String prefixName) { + return getOptionalListProperty("secret-config-kv-path." + prefixName).get(); + } + } diff --git a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java index e02292a46ec33..f3b37e0c6b302 100644 --- a/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java +++ b/extensions/vault/runtime/src/main/java/io/quarkus/vault/runtime/config/VaultRuntimeConfig.java @@ -8,6 +8,7 @@ import java.net.URL; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -82,10 +83,35 @@ public class VaultRuntimeConfig { public Duration secretConfigCachePeriod; /** - * Vault path in kv store, where all properties will be available as MP config. + * List of comma separated vault paths in kv store, + * where all properties will be available as MP config properties as-is, with no prefix. + * For instance, if vault contains property {@code foo}, it will be made available to the + * quarkus application as + * {@code @ConfigProperty(name = "foo") String foo;} + *

+ * If 2 paths contain the same property, the last path will win. For instance if + * {@code secret/base-config} contains {@code foo='bar'} and + * {@code secret/myapp/config} contains {@code foo='myappbar'}, then + * {@code @ConfigProperty(name = "foo") String foo;} will have for value {@code "bar"} + * with application properties + * {@code quarkus.vault.secret-config-kv-path=base-config,myapp/config} */ @ConfigItem - public Optional secretConfigKvPath; + public Optional> secretConfigKvPath; + + /** + * List of comma separated vault paths in kv store, + * where all properties will be available as prefixed MP config properties. + * For instance if the application properties contains + * {@code quarkus.vault.secret-config-kv-path-prefix.myprefix=config}, all properties located + * in vault path {@code secret/config} contains {@code foo='bar'}, then {@code myprefix.foo} + * will be available in the MP config. + *

+ * If the same property is available in 2 different paths for the same prefix, the last one + * will win. + */ + @ConfigItem(name = "secret-config-kv-path") + public Map> secretConfigKvPrefixPath; /** * Used to hide confidential infos, for logging in particular. diff --git a/integration-tests/vault/src/test/java/io/quarkus/vault/VaultMultiPathConfigITCase.java b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultMultiPathConfigITCase.java new file mode 100644 index 0000000000000..7625340c706ff --- /dev/null +++ b/integration-tests/vault/src/test/java/io/quarkus/vault/VaultMultiPathConfigITCase.java @@ -0,0 +1,42 @@ +package io.quarkus.vault; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.vault.test.VaultTestLifecycleManager; + +@DisabledOnOs(OS.WINDOWS) +@QuarkusTestResource(VaultTestLifecycleManager.class) +public class VaultMultiPathConfigITCase { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-vault-multi-path.properties", "application.properties")); + + @Test + public void defaultPath() { + Config config = ConfigProviderResolver.instance().getConfig(); + assertEquals("red", config.getValue("color", String.class)); + assertEquals("XL", config.getValue("size", String.class)); + assertEquals("3", config.getValue("weight", String.class)); + } + + @Test + public void prefixPath() { + Config config = ConfigProviderResolver.instance().getConfig(); + assertEquals("green", config.getValue("singer.color", String.class)); + assertEquals("paul", config.getValue("singer.firstname", String.class)); + assertEquals("simon", config.getValue("singer.lastname", String.class)); + assertEquals("78", config.getValue("singer.age", String.class)); + } +} diff --git a/integration-tests/vault/src/test/resources/application-vault-multi-path.properties b/integration-tests/vault/src/test/resources/application-vault-multi-path.properties new file mode 100644 index 0000000000000..cf921eb746c47 --- /dev/null +++ b/integration-tests/vault/src/test/resources/application-vault-multi-path.properties @@ -0,0 +1,7 @@ +quarkus.vault.url=https://localhost:8200 +quarkus.vault.authentication.userpass.username=bob +quarkus.vault.authentication.userpass.password=sinclair +quarkus.vault.secret-config-kv-path=multi/default1,multi/default2 +quarkus.vault.secret-config-kv-path.singer=multi/singer1,multi/singer2 + +quarkus.vault.tls.skip-verify=true diff --git a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java index 80f39fec85bca..55c4da4546927 100644 --- a/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java +++ b/test-framework/vault/src/main/java/io/quarkus/vault/test/VaultTestExtension.java @@ -204,6 +204,12 @@ private void initVault() throws InterruptedException, IOException { execVault(format("vault kv put %s/%s %s=%s", SECRET_PATH_V1, APP_SECRET_PATH, SECRET_KEY, SECRET_VALUE)); execVault(format("vault kv put %s/%s %s=%s", SECRET_PATH_V1, APP_CONFIG_PATH, PASSWORD_PROPERTY_NAME, DB_PASSWORD)); + // multi config + execVault(format("vault kv put %s/multi/default1 color=blue size=XL", SECRET_PATH_V1)); + execVault(format("vault kv put %s/multi/default2 color=red weight=3", SECRET_PATH_V1)); + execVault(format("vault kv put %s/multi/singer1 firstname=paul lastname=shaffer", SECRET_PATH_V1)); + execVault(format("vault kv put %s/multi/singer2 lastname=simon age=78 color=green", SECRET_PATH_V1)); + // static secrets kv v2 execVault(format("vault secrets enable -path=%s -version=2 kv", SECRET_PATH_V2)); execVault(format("vault kv put %s/%s %s=%s", SECRET_PATH_V2, APP_SECRET_PATH, SECRET_KEY, SECRET_VALUE)); diff --git a/test-framework/vault/src/main/resources/vault.policy b/test-framework/vault/src/main/resources/vault.policy index ec024fbac1618..8519bf96443e7 100644 --- a/test-framework/vault/src/main/resources/vault.policy +++ b/test-framework/vault/src/main/resources/vault.policy @@ -8,6 +8,11 @@ path "secret/config" { capabilities = ["read"] } +# vault config source kv engine v1 with multi paths +path "secret/multi/*" { + capabilities = ["read"] +} + # kv engine v2 path "secret-v2/data/foo" { capabilities = ["read"] From a2e7d06541aa293d72ed2b07d73f7cd1fce4c994 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 07:43:11 -0600 Subject: [PATCH 386/602] remove whitespace remove whitespace --- ci-templates/native-build-steps.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 5e686718167a1..08259b5eb3904 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -14,8 +14,8 @@ jobs: - job: ${{ parameters.name }} condition: and(eq(variables.LINUX_USE_VMS, ${{parameters.expectUseVMs}}),succeeded()) displayName: ${{ join(', ', parameters.modules) }} - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - pool: ${{ parameters.poolSettings }} + timeoutInMinutes: ${{parameters.timeoutInMinutes}} + pool: ${{parameters.poolSettings}} workspace: clean: all From 24b2fceb0688576805881f97444f0ef9f343164c Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 08:03:40 -0600 Subject: [PATCH 387/602] Workaround CI failure Workaround CI failure --- ci-templates/native-build-steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 08259b5eb3904..90b7369f597aa 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -25,7 +25,7 @@ jobs: steps: - script: docker rm -f $(docker ps -qa) || true displayName: 'Docker Cleanup' - - task: DownloadPipelineArtifact@2 + - task: DownloadPipelineArtifact@1 inputs: artifact: $(Build.SourceVersion)-BuiltMavenRepo path: $(Pipeline.Workspace)/.m2/repository/ From ee0ae71905c1430eece7bc0bbf9d6d4a1dfe00a2 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 08:05:59 -0600 Subject: [PATCH 388/602] Workaround CI Workaround CI --- ci-templates/native-build-steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 90b7369f597aa..81894d857044d 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -28,7 +28,7 @@ jobs: - task: DownloadPipelineArtifact@1 inputs: artifact: $(Build.SourceVersion)-BuiltMavenRepo - path: $(Pipeline.Workspace)/.m2/repository/ + targetPath: $(Pipeline.Workspace)/.m2/repository/ - ${{ if eq(parameters.postgres, 'true') }}: - script: docker run --rm --publish 5432:5432 --name build-postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -d postgres:10.5 displayName: 'start postgres' From dc75dc56e297e6579eafd62ffc308ffce5a385e8 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 09:32:40 -0600 Subject: [PATCH 389/602] Update workaround Update workaround --- ci-templates/native-build-steps.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 81894d857044d..b90868d6896d7 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -29,6 +29,7 @@ jobs: inputs: artifact: $(Build.SourceVersion)-BuiltMavenRepo targetPath: $(Pipeline.Workspace)/.m2/repository/ + - bash: ls $(Pipeline.Workspace)/.m2/repository/ - ${{ if eq(parameters.postgres, 'true') }}: - script: docker run --rm --publish 5432:5432 --name build-postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -d postgres:10.5 displayName: 'start postgres' @@ -49,4 +50,4 @@ jobs: inputs: goals: 'install' mavenOptions: $(MAVEN_OPTS) - options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' + options: $(MAVEN_OPTS) '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' From b8803260bbc5fbd185b8c5cd1a0e242646b4a541 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 09:35:42 -0600 Subject: [PATCH 390/602] Update native-build-steps.yaml --- ci-templates/native-build-steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index b90868d6896d7..eeaa42cb07783 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -50,4 +50,4 @@ jobs: inputs: goals: 'install' mavenOptions: $(MAVEN_OPTS) - options: $(MAVEN_OPTS) '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' + options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' From b039359e4bbd1f93ddd5d7620f0bae62d07242f7 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 17 Dec 2019 21:30:27 -0300 Subject: [PATCH 391/602] Minor cleanup in Gradle plugin Using constants and avoiding casts --- .../gradle/AppModelGradleResolver.java | 22 +++++++++++-------- .../java/io/quarkus/gradle/QuarkusPlugin.java | 3 ++- .../gradle/QuarkusPluginExtension.java | 11 +++++----- .../gradle/tasks/GradleMessageWriter.java | 16 +++++++------- .../io/quarkus/gradle/tasks/QuarkusDev.java | 3 ++- .../io/quarkus/gradle/tasks/QuarkusTask.java | 5 +++-- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java index 5a2acc712ff99..16862ef104244 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java @@ -27,6 +27,7 @@ import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact; import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Provider; import org.gradle.jvm.tasks.Jar; @@ -40,6 +41,7 @@ public class AppModelGradleResolver implements AppModelResolver { private AppModel appModel; + private final Project project; public AppModelGradleResolver(Project project) { @@ -47,7 +49,8 @@ public AppModelGradleResolver(Project project) { } @Override - public String getLatestVersion(AppArtifact arg0, String arg1, boolean arg2) throws AppModelResolverException { + public String getLatestVersion(AppArtifact appArtifact, String upToVersion, boolean inclusive) + throws AppModelResolverException { throw new UnsupportedOperationException(); } @@ -57,18 +60,20 @@ public String getLatestVersionFromRange(AppArtifact appArtifact, String range) t } @Override - public String getNextVersion(AppArtifact arg0, String fromVersion, boolean fromVersionIncluded, String arg1, boolean arg2) + public String getNextVersion(AppArtifact appArtifact, String fromVersion, boolean fromVersionIncluded, String upToVersion, + boolean upToVersionIncluded) throws AppModelResolverException { throw new UnsupportedOperationException(); } @Override - public List listLaterVersions(AppArtifact arg0, String arg1, boolean arg2) throws AppModelResolverException { + public List listLaterVersions(AppArtifact appArtifact, String upToVersion, boolean inclusive) + throws AppModelResolverException { throw new UnsupportedOperationException(); } @Override - public void relink(AppArtifact arg0, Path arg1) throws AppModelResolverException { + public void relink(AppArtifact appArtifact, Path localPath) throws AppModelResolverException { } @@ -105,8 +110,7 @@ public Path resolve(AppArtifact appArtifact) throws AppModelResolverException { } @Override - public List resolveUserDependencies(AppArtifact appArtifact, List directDeps) - throws AppModelResolverException { + public List resolveUserDependencies(AppArtifact appArtifact, List directDeps) { return Collections.emptyList(); } @@ -115,7 +119,7 @@ public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverExc if (appModel != null && appModel.getAppArtifact().equals(appArtifact)) { return appModel; } - final Configuration compileCp = project.getConfigurations().getByName("compileClasspath"); + final Configuration compileCp = project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME); final List extensionDeps = new ArrayList<>(); final List userDeps = new ArrayList<>(); Map userModules = new HashMap<>(); @@ -160,7 +164,7 @@ public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverExc // In the case of quarkusBuild (which is the primary user of this), // it's not necessary to actually resolve the original application JAR if (!appArtifact.isResolved()) { - final Jar jarTask = (Jar) project.getTasks().findByName("jar"); + final Jar jarTask = (Jar) project.getTasks().findByName(JavaPlugin.JAR_TASK_NAME); if (jarTask == null) { throw new AppModelResolverException("Failed to locate task 'jar' in the project."); } @@ -176,7 +180,7 @@ public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverExc } @Override - public AppModel resolveModel(AppArtifact arg0, List arg1) throws AppModelResolverException { + public AppModel resolveModel(AppArtifact root, List deps) throws AppModelResolverException { throw new UnsupportedOperationException(); } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 938af2eb4b916..29fce93e67077 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -27,6 +27,7 @@ public class QuarkusPlugin implements Plugin { + public static final String EXTENSION_NAME = "quarkus"; public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions"; public static final String ADD_EXTENSION_TASK_NAME = "addExtension"; public static final String QUARKUS_BUILD_TASK_NAME = "quarkusBuild"; @@ -46,7 +47,7 @@ public class QuarkusPlugin implements Plugin { public void apply(Project project) { verifyGradleVersion(); // register extension - project.getExtensions().create("quarkus", QuarkusPluginExtension.class, project); + project.getExtensions().create(EXTENSION_NAME, QuarkusPluginExtension.class, project); registerTasks(project); } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 9224d943e6127..6141059a686d4 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -5,6 +5,7 @@ import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.resolver.AppModelResolver; @@ -30,7 +31,7 @@ public QuarkusPluginExtension(Project project) { public File outputDirectory() { if (outputDirectory == null) outputDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getOutput().getClassesDirs().getAsPath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs().getAsPath(); return new File(outputDirectory); } @@ -38,7 +39,7 @@ public File outputDirectory() { public File outputConfigDirectory() { if (outputConfigDirectory == null) { outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getOutput().getResourcesDir().getAbsolutePath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir().getAbsolutePath(); } return new File(outputConfigDirectory); } @@ -46,7 +47,7 @@ public File outputConfigDirectory() { public File sourceDir() { if (sourceDir == null) { sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getAllJava().getSourceDirectories().getAsPath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava().getSourceDirectories().getAsPath(); } return new File(sourceDir); } @@ -61,14 +62,14 @@ public File workingDir() { public String finalName() { if (finalName == null || finalName.length() == 0) { - this.finalName = project.getName() + "-" + project.getVersion(); + this.finalName = String.format("%s-%s", project.getName(), project.getVersion()); } return finalName; } public Set resourcesDir() { return project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName("main").getResources().getSrcDirs(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getResources().getSrcDirs(); } public AppArtifact getAppArtifact() { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleMessageWriter.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleMessageWriter.java index a91641293e540..00deba251f42e 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleMessageWriter.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/GradleMessageWriter.java @@ -13,18 +13,18 @@ public GradleMessageWriter(Logger logger) { } @Override - public void debug(String arg0) { - logger.debug(arg0); + public void debug(String msg) { + logger.debug(msg); } @Override - public void error(String arg0) { - logger.error(arg0); + public void error(String msg) { + logger.error(msg); } @Override - public void info(String arg0) { - logger.info(arg0); + public void info(String msg) { + logger.info(msg); } @Override @@ -33,7 +33,7 @@ public boolean isDebugEnabled() { } @Override - public void warn(String arg0) { - logger.warn(arg0); + public void warn(String msg) { + logger.warn(msg); } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index c4314b90dcd58..d5984ab07fc6a 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -271,7 +271,8 @@ public void startDev() { res = file.getAbsolutePath(); } - final Configuration compileCp = project.getConfigurations().getByName("compileClasspath"); + final Configuration compileCp = project.getConfigurations() + .getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME); final DependencySet compileCpDependencies = compileCp.getAllDependencies(); for (Dependency dependency : compileCpDependencies) { diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java index 684e2bee882fe..1242768ed016c 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java @@ -16,8 +16,9 @@ public abstract class QuarkusTask extends DefaultTask { } QuarkusPluginExtension extension() { - if (extension == null) - extension = (QuarkusPluginExtension) getProject().getExtensions().findByName("quarkus"); + if (extension == null) { + extension = getProject().getExtensions().findByType(QuarkusPluginExtension.class); + } return extension; } } From b0890ab5ed1bb9333f8defec0528dda08554f022 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 18 Dec 2019 09:33:35 +0200 Subject: [PATCH 392/602] Add a dev-mode test for the Spring Web module --- .../spring/web/test/ControllerReloadTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java b/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java new file mode 100644 index 0000000000000..265b8a77e6a18 --- /dev/null +++ b/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java @@ -0,0 +1,28 @@ +package io.quarkus.spring.web.test; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +public class ControllerReloadTest { + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleSpringController.class)); + + @Test + public void testRepositoryIsReloaded() { + when().get("/simple").then().body(is("hello")); + + TEST.modifySourceFile("SimpleSpringController.java", s -> s.replace("hello", "hi")); + + when().get("/simple").then().body(is("hi")); + } +} From a37e7bc8f1b42de10e79ed797d1a05c347a3a7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferraz=20Campos=20Florentino?= Date: Wed, 18 Dec 2019 13:46:44 -0300 Subject: [PATCH 393/602] auth-mechanism property does not exist auth-mechanism property does not exist --- docs/src/main/asciidoc/security-jwt.adoc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/security-jwt.adoc b/docs/src/main/asciidoc/security-jwt.adoc index 0d3cebce3bd04..7484bf64fdc26 100644 --- a/docs/src/main/asciidoc/security-jwt.adoc +++ b/docs/src/main/asciidoc/security-jwt.adoc @@ -265,13 +265,11 @@ For part A of step 1, create a `security-jwt-quickstart/src/main/resources/appli mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem #<1> mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac #<2> -quarkus.smallrye-jwt.auth-mechanism=MP-JWT # <3> quarkus.smallrye-jwt.enabled=true # <4> ---- <1> We are setting public key location to point to a classpath publicKey.pem resource location. We will add this key in part B, <>. <2> We are setting the issuer to the URL string `https://quarkus.io/using-jwt-rbac`. -<3> We are setting the authentication mechanism name to MP-JWT. This is not strictly required to allow our quickstart to work, but it is the {mp-jwt} specification standard name for the token based authentication mechanism. -<4> We are enabling the {extension-name}. Also not required since this is the default, +<3> We are enabling the {extension-name}. Also not required since this is the default, but we are making it explicit. === Adding a Public Key From 9b1b3d184a64efe23daa130091b9a2ad11fead6c Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 18 Dec 2019 12:18:56 -0600 Subject: [PATCH 394/602] Complete work-around for CI (#6256) Complete work-around for CI --- ci-templates/native-build-steps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index eeaa42cb07783..45773ff6e7e99 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -29,7 +29,7 @@ jobs: inputs: artifact: $(Build.SourceVersion)-BuiltMavenRepo targetPath: $(Pipeline.Workspace)/.m2/repository/ - - bash: ls $(Pipeline.Workspace)/.m2/repository/ + - bash: (cd $(Pipeline.Workspace)/.m2/repository/$(Build.SourceVersion)-BuiltMavenRepo/; mv * ..) - ${{ if eq(parameters.postgres, 'true') }}: - script: docker run --rm --publish 5432:5432 --name build-postgres -e POSTGRES_USER=hibernate_orm_test -e POSTGRES_PASSWORD=hibernate_orm_test -e POSTGRES_DB=hibernate_orm_test -d postgres:10.5 displayName: 'start postgres' From ffde6ee3f9d4e5901c11fb3dad1211ea6b73372b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 18 Dec 2019 09:21:34 +0200 Subject: [PATCH 395/602] Add a dev-mode test for the Spring Security module --- extensions/spring-security/deployment/pom.xml | 15 +++++++ .../AnnotationChangeReloadTest.java | 44 +++++++++++++++++++ .../springapp/SpringController.java | 21 +++++++++ .../src/test/resources/application.properties | 5 +++ .../src/test/resources/test-roles.properties | 2 + .../src/test/resources/test-users.properties | 2 + 6 files changed, 89 insertions(+) create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/AnnotationChangeReloadTest.java create mode 100644 extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringController.java create mode 100644 extensions/spring-security/deployment/src/test/resources/application.properties create mode 100644 extensions/spring-security/deployment/src/test/resources/test-roles.properties create mode 100644 extensions/spring-security/deployment/src/test/resources/test-users.properties diff --git a/extensions/spring-security/deployment/pom.xml b/extensions/spring-security/deployment/pom.xml index 6eb0d967b2882..181da38f00940 100644 --- a/extensions/spring-security/deployment/pom.xml +++ b/extensions/spring-security/deployment/pom.xml @@ -50,6 +50,21 @@ quarkus-security-test-utils test + + io.quarkus + quarkus-spring-web-deployment + test + + + io.quarkus + quarkus-elytron-security-properties-file-deployment + test + + + io.rest-assured + rest-assured + test + diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/AnnotationChangeReloadTest.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/AnnotationChangeReloadTest.java new file mode 100644 index 0000000000000..c4e449f5e28a8 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/AnnotationChangeReloadTest.java @@ -0,0 +1,44 @@ +package io.quarkus.spring.security.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.spring.security.deployment.springapp.Person; +import io.quarkus.spring.security.deployment.springapp.Roles; +import io.quarkus.spring.security.deployment.springapp.SpringComponent; +import io.quarkus.spring.security.deployment.springapp.SpringController; +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class AnnotationChangeReloadTest { + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Roles.class, + Person.class, + SpringComponent.class, + SpringController.class)); + + @Test() + public void testUpdatedAnnotationWorks() { + RestAssured.given().auth().preemptive().basic("bob", "bob") + .when().get("/secure/admin").then() + .statusCode(403); + RestAssured.given().auth().preemptive().basic("alice", "alice") + .when().get("/secure/admin").then() + .statusCode(200); + + TEST.modifySourceFile("SpringComponent.java", s -> s.replace("@PreAuthorize(\"hasRole(@roles.ADMIN)\")", + "@PreAuthorize(\"hasRole(@roles.USER)\")")); + + RestAssured.given().auth().preemptive().basic("bob", "bob") + .when().get("/secure/admin").then() + .statusCode(200); + RestAssured.given().auth().preemptive().basic("alice", "alice") + .when().get("/secure/admin").then() + .statusCode(403); + } +} diff --git a/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringController.java b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringController.java new file mode 100644 index 0000000000000..42de077d049c2 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/java/io/quarkus/spring/security/deployment/springapp/SpringController.java @@ -0,0 +1,21 @@ +package io.quarkus.spring.security.deployment.springapp; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/secure") +public class SpringController { + + private final SpringComponent springComponent; + + public SpringController(SpringComponent springComponent) { + this.springComponent = springComponent; + } + + @GetMapping("/admin") + public String accessibleForAdminOnly() { + return springComponent.accessibleForAdminOnly(); + } +} diff --git a/extensions/spring-security/deployment/src/test/resources/application.properties b/extensions/spring-security/deployment/src/test/resources/application.properties new file mode 100644 index 0000000000000..9bd854dc31119 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/resources/application.properties @@ -0,0 +1,5 @@ +quarkus.security.users.file.enabled=true +quarkus.security.users.file.users=test-users.properties +quarkus.security.users.file.roles=test-roles.properties +#quarkus.security.users.file.auth-mechanism=BASIC +quarkus.security.users.file.plain-text=true diff --git a/extensions/spring-security/deployment/src/test/resources/test-roles.properties b/extensions/spring-security/deployment/src/test/resources/test-roles.properties new file mode 100644 index 0000000000000..452e1223fb380 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/resources/test-roles.properties @@ -0,0 +1,2 @@ +bob=user +alice=admin \ No newline at end of file diff --git a/extensions/spring-security/deployment/src/test/resources/test-users.properties b/extensions/spring-security/deployment/src/test/resources/test-users.properties new file mode 100644 index 0000000000000..fefc2ae47afe0 --- /dev/null +++ b/extensions/spring-security/deployment/src/test/resources/test-users.properties @@ -0,0 +1,2 @@ +bob=bob +alice=alice \ No newline at end of file From 19fb669bd0fe74a8f9cc7ec5f10161ac87401d50 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 18 Dec 2019 21:47:21 +0200 Subject: [PATCH 396/602] Fix broken call-out --- docs/src/main/asciidoc/security-jwt.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-jwt.adoc b/docs/src/main/asciidoc/security-jwt.adoc index 7484bf64fdc26..ada1eb2ff4328 100644 --- a/docs/src/main/asciidoc/security-jwt.adoc +++ b/docs/src/main/asciidoc/security-jwt.adoc @@ -264,8 +264,7 @@ For part A of step 1, create a `security-jwt-quickstart/src/main/resources/appli ---- mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem #<1> mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac #<2> - -quarkus.smallrye-jwt.enabled=true # <4> +quarkus.smallrye-jwt.enabled=true #<3> ---- <1> We are setting public key location to point to a classpath publicKey.pem resource location. We will add this key in part B, <>. <2> We are setting the issuer to the URL string `https://quarkus.io/using-jwt-rbac`. From a0d66b5c6e13847ab911f042d60529a7bbe3b6ce Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 13 Dec 2019 15:27:04 +0100 Subject: [PATCH 397/602] Mailer - support Qute templates - resolves #6158 - also collect all possible template variants and fix #6159 --- extensions/mailer/deployment/pom.xml | 4 + .../mailer/deployment/MailerProcessor.java | 67 +++++++- .../java/io/quarkus/mailer/InjectionTest.java | 38 ++++- .../mailer/MailTemplateValidationTest.java | 46 +++++ extensions/mailer/runtime/pom.xml | 5 +- .../java/io/quarkus/mailer/MailTemplate.java | 57 +++++++ .../runtime/MailTemplateInstanceImpl.java | 160 ++++++++++++++++++ .../mailer/runtime/MailTemplateProducer.java | 79 +++++++++ .../qute/deployment/QuteProcessor.java | 97 +++++------ .../deployment/TemplatePathBuildItem.java | 18 +- .../io/quarkus/qute/api/ResourcePath.java | 21 +++ .../io/quarkus/qute/runtime/QuteConfig.java | 3 +- .../qute/runtime/VariantTemplateProducer.java | 3 +- 13 files changed, 531 insertions(+), 67 deletions(-) create mode 100644 extensions/mailer/deployment/src/test/java/io/quarkus/mailer/MailTemplateValidationTest.java create mode 100644 extensions/mailer/runtime/src/main/java/io/quarkus/mailer/MailTemplate.java create mode 100644 extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateInstanceImpl.java create mode 100644 extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateProducer.java diff --git a/extensions/mailer/deployment/pom.xml b/extensions/mailer/deployment/pom.xml index e96876f17fd84..aec7797c61584 100644 --- a/extensions/mailer/deployment/pom.xml +++ b/extensions/mailer/deployment/pom.xml @@ -20,6 +20,10 @@ io.quarkus quarkus-vertx-deployment + + io.quarkus + quarkus-qute-deployment + io.quarkus quarkus-mailer diff --git a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java index 11e508dc0bcfd..1ec90f0f3fd3e 100644 --- a/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java +++ b/extensions/mailer/deployment/src/main/java/io/quarkus/mailer/deployment/MailerProcessor.java @@ -1,7 +1,19 @@ package io.quarkus.mailer.deployment; +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; + import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.ValidationPhaseBuildItem; +import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; +import io.quarkus.arc.processor.BuildExtension; +import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -11,18 +23,24 @@ import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.mailer.MailTemplate; import io.quarkus.mailer.runtime.BlockingMailerImpl; import io.quarkus.mailer.runtime.MailClientProducer; import io.quarkus.mailer.runtime.MailConfig; import io.quarkus.mailer.runtime.MailConfigRecorder; +import io.quarkus.mailer.runtime.MailTemplateProducer; import io.quarkus.mailer.runtime.MockMailboxImpl; import io.quarkus.mailer.runtime.ReactiveMailerImpl; +import io.quarkus.qute.deployment.QuteProcessor; +import io.quarkus.qute.deployment.TemplatePathBuildItem; import io.quarkus.runtime.RuntimeValue; import io.quarkus.vertx.deployment.VertxBuildItem; import io.vertx.ext.mail.MailClient; public class MailerProcessor { + private static final DotName MAIL_TEMPLATE = DotName.createSimple(MailTemplate.class.getName()); + @BuildStep AdditionalBeanBuildItem registerClients() { return AdditionalBeanBuildItem.unremovableOf(MailClientProducer.class); @@ -31,7 +49,8 @@ AdditionalBeanBuildItem registerClients() { @BuildStep AdditionalBeanBuildItem registerMailers() { return AdditionalBeanBuildItem.builder() - .addBeanClasses(ReactiveMailerImpl.class, BlockingMailerImpl.class, MockMailboxImpl.class) + .addBeanClasses(ReactiveMailerImpl.class, BlockingMailerImpl.class, MockMailboxImpl.class, + MailTemplateProducer.class) .build(); } @@ -68,4 +87,50 @@ MailerBuildItem build(BuildProducer feature, MailConfigRecorde return new MailerBuildItem(client); } + + @BuildStep + void validateMailTemplates( + List templatePaths, ValidationPhaseBuildItem validationPhase, + BuildProducer validationErrors) { + + Set filePaths = new HashSet(); + for (TemplatePathBuildItem templatePath : templatePaths) { + String filePath = templatePath.getPath(); + if (File.separatorChar != '/') { + filePath = filePath.replace(File.separatorChar, '/'); + } + if (filePath.endsWith("html") || filePath.endsWith("htm") || filePath.endsWith("txt")) { + // For e-mails we only consider html and txt templates + filePaths.add(filePath); + int idx = filePath.lastIndexOf('.'); + if (idx != -1) { + // Also add version without suffix from the path + // For example for "items.html" also add "items" + filePaths.add(filePath.substring(0, idx)); + } + } + } + + for (InjectionPointInfo injectionPoint : validationPhase.getContext().get(BuildExtension.Key.INJECTION_POINTS)) { + if (injectionPoint.getRequiredType().name().equals(MAIL_TEMPLATE)) { + AnnotationInstance resourcePath = injectionPoint.getRequiredQualifier(QuteProcessor.RESOURCE_PATH); + String name; + if (resourcePath != null) { + name = resourcePath.value().asString(); + } else if (injectionPoint.hasDefaultedQualifier()) { + name = QuteProcessor.getName(injectionPoint); + } else { + name = null; + } + if (name != null) { + // For "@Inject MailTemplate items" we try to match "items" + // For "@ResourcePath("github/pulls") MailTemplate pulls" we try to match "github/pulls" + if (filePaths.stream().noneMatch(path -> path.endsWith(name))) { + validationErrors.produce(new ValidationErrorBuildItem( + new IllegalStateException("No mail template found for " + injectionPoint.getTargetInfo()))); + } + } + } + } + } } diff --git a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java index 28f603c3d0e9b..129291056155f 100644 --- a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java +++ b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/InjectionTest.java @@ -4,13 +4,16 @@ import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.inject.Singleton; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.qute.api.ResourcePath; import io.quarkus.test.QuarkusUnitTest; import io.vertx.ext.mail.MailClient; @@ -23,7 +26,14 @@ public class InjectionTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(BeanUsingAxleMailClient.class, BeanUsingBareMailClient.class, BeanUsingRxClient.class) .addClasses(BeanUsingBlockingMailer.class, BeanUsingReactiveMailer.class) - .addAsResource("mock-config.properties", "application.properties")); + .addClasses(MailTemplates.class) + .addAsResource("mock-config.properties", "application.properties") + .addAsResource(new StringAsset("" + + "{name}"), "templates/test1.html") + .addAsResource(new StringAsset("" + + "{name}"), "templates/test1.txt") + .addAsResource(new StringAsset("" + + "{name}"), "templates/mails/test2.html")); @Inject BeanUsingAxleMailClient beanUsingBare; @@ -40,13 +50,18 @@ public class InjectionTest { @Inject BeanUsingBlockingMailer beanUsingBlockingMailer; + @Inject + MailTemplates templates; + @Test public void testInjection() { beanUsingAxle.verify(); beanUsingBare.verify(); beanUsingRx.verify(); beanUsingBlockingMailer.verify(); - beanUsingReactiveMailer.verify().toCompletableFuture().join(); + beanUsingReactiveMailer.verify(); + templates.send1(); + templates.send2().toCompletableFuture().join(); } @ApplicationScoped @@ -103,4 +118,23 @@ void verify() { mailer.send(Mail.withText("quarkus@quarkus.io", "test mailer", "blocking test!")); } } + + @Singleton + static class MailTemplates { + + @Inject + MailTemplate test1; + + @ResourcePath("mails/test2") + MailTemplate testMail; + + CompletionStage send1() { + return test1.to("quarkus@quarkus.io").subject("Test").data("name", "John").send(); + } + + CompletionStage send2() { + return testMail.to("quarkus@quarkus.io").subject("Test").data("name", "Lu").send(); + } + + } } diff --git a/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/MailTemplateValidationTest.java b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/MailTemplateValidationTest.java new file mode 100644 index 0000000000000..4d58136a273d3 --- /dev/null +++ b/extensions/mailer/deployment/src/test/java/io/quarkus/mailer/MailTemplateValidationTest.java @@ -0,0 +1,46 @@ +package io.quarkus.mailer; + +import java.util.concurrent.CompletionStage; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class MailTemplateValidationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MailTemplates.class) + .addAsResource("mock-config.properties", "application.properties") + .addAsResource(new StringAsset("" + + "{name}"), "templates/test1.html")) + .setExpectedException(DeploymentException.class); + + @Test + public void testValidationFailed() { + // This method should not be invoked + Assertions.fail(); + } + + @Singleton + static class MailTemplates { + + @Inject + MailTemplate doesNotExist; + + CompletionStage send() { + return doesNotExist.to("quarkus@quarkus.io").subject("Test").data("name", "Foo").send(); + } + + } +} diff --git a/extensions/mailer/runtime/pom.xml b/extensions/mailer/runtime/pom.xml index d156dddc2210c..b771259686b78 100644 --- a/extensions/mailer/runtime/pom.xml +++ b/extensions/mailer/runtime/pom.xml @@ -20,11 +20,14 @@ io.quarkus quarkus-vertx + + io.quarkus + quarkus-qute + io.smallrye.reactive smallrye-axle-mail-client - org.subethamail subethasmtp diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/MailTemplate.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/MailTemplate.java new file mode 100644 index 0000000000000..a849b7c151872 --- /dev/null +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/MailTemplate.java @@ -0,0 +1,57 @@ +package io.quarkus.mailer; + +import java.util.concurrent.CompletionStage; + +/** + * Represents an e-mail definition based on a template. + */ +public interface MailTemplate { + + /** + * + * @return a new template instance + */ + MailTemplateInstance instance(); + + default MailTemplateInstance of(Mail mail) { + return instance().mail(mail); + } + + default MailTemplateInstance to(String... values) { + return instance().to(values); + } + + default MailTemplateInstance data(String key, Object value) { + return instance().data(key, value); + } + + /** + * Represents an instance of {@link MailTemplate}. + *

+ * This construct is not thread-safe. + */ + interface MailTemplateInstance { + + MailTemplateInstance mail(Mail mail); + + MailTemplateInstance to(String... to); + + MailTemplateInstance cc(String... cc); + + MailTemplateInstance bcc(String... bcc); + + MailTemplateInstance subject(String subject); + + MailTemplateInstance from(String from); + + MailTemplateInstance replyTo(String replyTo); + + MailTemplateInstance bounceAddress(String bounceAddress); + + MailTemplateInstance data(String key, Object value); + + CompletionStage send(); + + } + +} diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateInstanceImpl.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateInstanceImpl.java new file mode 100644 index 0000000000000..f922231a90950 --- /dev/null +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateInstanceImpl.java @@ -0,0 +1,160 @@ +package io.quarkus.mailer.runtime; + +import static io.quarkus.qute.api.VariantTemplate.SELECTED_VARIANT; +import static io.quarkus.qute.api.VariantTemplate.VARIANTS; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +import io.quarkus.mailer.Mail; +import io.quarkus.mailer.MailTemplate; +import io.quarkus.mailer.MailTemplate.MailTemplateInstance; +import io.quarkus.mailer.ReactiveMailer; +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Variant; +import io.quarkus.qute.api.VariantTemplate; + +class MailTemplateInstanceImpl implements MailTemplate.MailTemplateInstance { + + private final ReactiveMailer mailer; + private final TemplateInstance templateInstance; + private final Map data; + private Mail mail; + + MailTemplateInstanceImpl(ReactiveMailer mailer, TemplateInstance templateInstance) { + this.mailer = mailer; + this.templateInstance = templateInstance; + this.data = new HashMap<>(); + this.mail = new Mail(); + } + + @Override + public MailTemplateInstance mail(Mail mail) { + this.mail = mail; + return this; + } + + @Override + public MailTemplateInstance to(String... to) { + this.mail.addTo(to); + return this; + } + + @Override + public MailTemplateInstance cc(String... cc) { + this.mail.addCc(cc); + return this; + } + + @Override + public MailTemplateInstance bcc(String... bcc) { + this.mail.addBcc(bcc); + return this; + } + + @Override + public MailTemplateInstance subject(String subject) { + this.mail.setSubject(subject); + return this; + } + + @Override + public MailTemplateInstance from(String from) { + this.mail.setFrom(from); + return this; + } + + @Override + public MailTemplateInstance replyTo(String replyTo) { + this.mail.setReplyTo(replyTo); + return this; + } + + @Override + public MailTemplateInstance bounceAddress(String bounceAddress) { + this.mail.setBounceAddress(bounceAddress); + return this; + } + + @Override + public MailTemplateInstance data(String key, Object value) { + this.data.put(key, value); + return this; + } + + @Override + public CompletionStage send() { + + CompletableFuture result = new CompletableFuture<>(); + + if (templateInstance.getAttribute(VariantTemplate.VARIANTS) != null) { + + List results = new ArrayList<>(); + + @SuppressWarnings("unchecked") + List variants = (List) templateInstance.getAttribute(VARIANTS); + for (Variant variant : variants) { + if (variant.mediaType.equals(Variant.TEXT_HTML) || variant.mediaType.equals(Variant.TEXT_PLAIN)) { + results.add(new Result(variant, + templateInstance.setAttribute(SELECTED_VARIANT, variant).data(data).renderAsync() + .toCompletableFuture())); + } + } + + if (results.isEmpty()) { + throw new IllegalStateException("No suitable template variant found"); + } + + CompletableFuture all = CompletableFuture + .allOf(results.stream().map(Result::getValue).toArray(CompletableFuture[]::new)); + all.whenComplete((r1, t1) -> { + if (t1 != null) { + result.completeExceptionally(t1); + } else { + try { + for (Result res : results) { + if (res.variant.mediaType.equals(Variant.TEXT_HTML)) { + mail.setHtml(res.value.get()); + } else if (res.variant.mediaType.equals(Variant.TEXT_PLAIN)) { + mail.setText(res.value.get()); + } + } + } catch (InterruptedException | ExecutionException e) { + result.completeExceptionally(e); + } + mailer.send(mail).whenComplete((r, t) -> { + if (t != null) { + result.completeExceptionally(t); + } else { + result.complete(null); + } + }); + } + }); + } else { + throw new IllegalStateException("No template variant found"); + } + return result; + } + + static class Result { + + final Variant variant; + final CompletableFuture value; + + public Result(Variant variant, CompletableFuture result) { + this.variant = variant; + this.value = result; + } + + CompletableFuture getValue() { + return value; + } + } + +} diff --git a/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateProducer.java b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateProducer.java new file mode 100644 index 0000000000000..1e02de0be0619 --- /dev/null +++ b/extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateProducer.java @@ -0,0 +1,79 @@ +package io.quarkus.mailer.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.AnnotatedParameter; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.logging.Logger; + +import io.quarkus.mailer.MailTemplate; +import io.quarkus.mailer.ReactiveMailer; +import io.quarkus.qute.api.ResourcePath; +import io.quarkus.qute.api.VariantTemplate; + +@Singleton +public class MailTemplateProducer { + + private static final Logger LOGGER = Logger.getLogger(MailTemplateProducer.class); + + @Inject + ReactiveMailer mailer; + + @Any + Instance template; + + @Produces + MailTemplate getDefault(InjectionPoint injectionPoint) { + + final String name; + if (injectionPoint.getMember() instanceof Field) { + // For "@Inject MailTemplate test" use "test" + name = injectionPoint.getMember().getName(); + } else { + AnnotatedParameter parameter = (AnnotatedParameter) injectionPoint.getAnnotated(); + if (parameter.getJavaParameter().isNamePresent()) { + name = parameter.getJavaParameter().getName(); + } else { + name = injectionPoint.getMember().getName(); + LOGGER.warnf("Parameter name not present - using the method name as the template name instead %s", name); + } + } + + return new MailTemplate() { + @Override + public MailTemplateInstance instance() { + return new MailTemplateInstanceImpl(mailer, template.select(new ResourcePath.Literal(name)).get().instance()); + } + + }; + } + + @ResourcePath("ignored") + @Produces + MailTemplate get(InjectionPoint injectionPoint) { + ResourcePath path = null; + for (Annotation qualifier : injectionPoint.getQualifiers()) { + if (qualifier.annotationType().equals(ResourcePath.class)) { + path = (ResourcePath) qualifier; + break; + } + } + if (path == null || path.value().isEmpty()) { + throw new IllegalStateException("No template reource path specified"); + } + final String name = path.value(); + return new MailTemplate() { + @Override + public MailTemplateInstance instance() { + return new MailTemplateInstanceImpl(mailer, template.select(new ResourcePath.Literal(name)).get().instance()); + } + }; + } +} diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index bda1ffc9c445d..8ae0021315c49 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -275,7 +275,7 @@ void collectTemplateExtensionMethods(BeanArchiveIndexBuildItem beanArchiveIndex, } @BuildStep - void validateInjectedBeans(ApplicationArchivesBuildItem applicationArchivesBuildItem, + void validateBeansInjectedInTemplates(ApplicationArchivesBuildItem applicationArchivesBuildItem, TemplatesAnalysisBuildItem analysis, BeanArchiveIndexBuildItem beanArchiveIndex, BuildProducer incorrectExpressions, List templateExtensionMethods, @@ -496,31 +496,21 @@ void collectTemplates(QuteConfig config, ApplicationArchivesBuildItem applicatio } @BuildStep - void processInjectionPoints(QuteConfig config, ApplicationArchivesBuildItem applicationArchivesBuildItem, - List templatePaths, ValidationPhaseBuildItem validationPhase, - BuildProducer validationErrors, BuildProducer templateVariants) - throws IOException { + void validateTemplateInjectionPoints(List templatePaths, ValidationPhaseBuildItem validationPhase, + BuildProducer validationErrors) { - ApplicationArchive applicationArchive = applicationArchivesBuildItem.getRootArchive(); - String basePath = "templates/"; - Path templatesPath = applicationArchive.getChildPath(basePath); - - // Remove suffix from the path; e.g. "items.html" becomes "items" Set filePaths = new HashSet(); for (TemplatePathBuildItem templatePath : templatePaths) { - String filePath = templatePath.getPath(); - if (File.separatorChar != '/') { - filePath = filePath.replace(File.separatorChar, '/'); - } - filePaths.add(filePath); - int idx = filePath.lastIndexOf('.'); + String path = templatePath.getPath(); + filePaths.add(path); + int idx = path.lastIndexOf('.'); if (idx != -1) { - filePaths.add(filePath.substring(0, idx)); + // Also add version without suffix from the path + // For example for "items.html" also add "items" + filePaths.add(path.substring(0, idx)); } } - Set variantBases = new HashSet<>(); - for (InjectionPointInfo injectionPoint : validationPhase.getContext().get(BuildExtension.Key.INJECTION_POINTS)) { if (injectionPoint.getRequiredType().name().equals(TEMPLATE)) { @@ -558,19 +548,31 @@ void processInjectionPoints(QuteConfig config, ApplicationArchivesBuildItem appl if (filePaths.stream().noneMatch(path -> path.endsWith(name))) { validationErrors.produce(new ValidationErrorBuildItem( new IllegalStateException("No variant template found for " + injectionPoint.getTargetInfo()))); - } else { - variantBases.add(name); } } } } + } - if (!variantBases.isEmpty()) { - Map> variants = new HashMap<>(); - scanVariants(basePath, templatesPath, templatesPath, variantBases, variants); - templateVariants.produce(new TemplateVariantsBuildItem(variants)); - LOGGER.debugf("Variant templates found: %s", variants); + @BuildStep + TemplateVariantsBuildItem collectTemplateVariants(List templatePaths) throws IOException { + Set allPaths = templatePaths.stream().map(TemplatePathBuildItem::getPath).collect(Collectors.toSet()); + // item -> [item.html, item.txt] + Map> baseToVariants = new HashMap<>(); + for (String path : allPaths) { + int idx = path.lastIndexOf('.'); + if (idx != -1) { + String base = path.substring(0, idx); + List variants = baseToVariants.get(base); + if (variants == null) { + variants = new ArrayList<>(); + baseToVariants.put(base, variants); + } + variants.add(path); + } } + LOGGER.debugf("Variant templates found: %s", baseToVariants); + return new TemplateVariantsBuildItem(baseToVariants); } @BuildStep @@ -781,7 +783,7 @@ private Set collectInjectExpressions(TemplateAnalysis analysis) { return injectExpressions; } - private String getName(InjectionPointInfo injectionPoint) { + public static String getName(InjectionPointInfo injectionPoint) { if (injectionPoint.isField()) { return injectionPoint.getTarget().asField().name(); } else if (injectionPoint.isParam()) { @@ -813,36 +815,19 @@ private void scan(Path root, Path directory, String basePath, BuildProducer files = Files.list(directory)) { Iterator iter = files.iterator(); while (iter.hasNext()) { - Path path = iter.next(); - if (Files.isRegularFile(path)) { - LOGGER.debugf("Found template: %s", path); - String templatePath = root.relativize(path).toString(); - produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources, basePath, templatePath, path, - false); - } else if (Files.isDirectory(path) && !path.getFileName().toString().equals("tags")) { - LOGGER.debugf("Scan directory: %s", path); - scan(root, path, basePath, watchedPaths, templatePaths, nativeImageResources); - } - } - } - } - - void scanVariants(String basePath, Path root, Path directory, Set variantBases, Map> variants) - throws IOException { - try (Stream files = Files.list(directory)) { - Iterator iter = files.iterator(); - while (iter.hasNext()) { - Path path = iter.next(); - if (Files.isRegularFile(path)) { - for (String base : variantBases) { - if (path.toAbsolutePath().toString().contains(base)) { - // Variants are relative paths to base, e.g. "detail/item2" - variants.computeIfAbsent(base, i -> new ArrayList<>()) - .add(root.relativize(path).toString()); - } + Path filePath = iter.next(); + if (Files.isRegularFile(filePath)) { + LOGGER.debugf("Found template: %s", filePath); + String templatePath = root.relativize(filePath).toString(); + if (File.separatorChar != '/') { + templatePath = templatePath.replace(File.separatorChar, '/'); } - } else if (Files.isDirectory(path)) { - scanVariants(basePath, root, path, variantBases, variants); + produceTemplateBuildItems(templatePaths, watchedPaths, nativeImageResources, basePath, templatePath, + filePath, + false); + } else if (Files.isDirectory(filePath) && !filePath.getFileName().toString().equals("tags")) { + LOGGER.debugf("Scan directory: %s", filePath); + scan(root, filePath, basePath, watchedPaths, templatePaths, nativeImageResources); } } } diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java index 72f3608dbb03f..76745c01c3da8 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java @@ -13,24 +13,34 @@ public final class TemplatePathBuildItem extends MultiBuildItem { private final Path fullPath; private final boolean tag; - public TemplatePathBuildItem(String path, Path fullPath) { - this(path, fullPath, false); - } - public TemplatePathBuildItem(String path, Path fullPath, boolean tag) { this.path = path; this.fullPath = fullPath; this.tag = tag; } + /** + * Uses the {@code /} path separator. + * + * @return the path relative to the template root + */ public String getPath() { return path; } + /** + * Uses the system-dependent path separator. + * + * @return the full path of the template + */ public Path getFullPath() { return fullPath; } + /** + * + * @return {@code true} if it represents a user tag, {@code false} otherwise + */ public boolean isTag() { return tag; } diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/ResourcePath.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/ResourcePath.java index b2b16209b735c..c06865a57dc26 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/ResourcePath.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/api/ResourcePath.java @@ -8,6 +8,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import javax.enterprise.util.AnnotationLiteral; import javax.enterprise.util.Nonbinding; import javax.inject.Qualifier; @@ -26,4 +27,24 @@ @Nonbinding String value(); + /** + * Supports inline instantiation of this qualifier. + */ + public static final class Literal extends AnnotationLiteral implements ResourcePath { + + private static final long serialVersionUID = 1L; + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + + } + } \ No newline at end of file diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java index bfb9137f0accc..8f68e86db0cb1 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java @@ -12,8 +12,7 @@ public class QuteConfig { /** * The set of suffixes used when attempting to locate a template file. * - * By default, `engine.getTemplate("foo")` would result in several lookups: `src/main/resources/templates/foo`, - * `src/main/resources/templates/foo.html` and `src/main/resources/templates/foo.txt`. + * By default, `engine.getTemplate("foo")` would result in several lookups: `foo`, `foo.html`, `foo.txt`, etc. * * @asciidoclet */ diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java index 190d339bb0158..9e1fb06067491 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/VariantTemplateProducer.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; @@ -127,7 +128,7 @@ class VariantTemplateInstanceImpl extends TemplateInstanceBase { private final TemplateVariants variants; VariantTemplateInstanceImpl(TemplateVariants variants) { - this.variants = variants; + this.variants = Objects.requireNonNull(variants); setAttribute(VariantTemplate.VARIANTS, new ArrayList<>(variants.variantToTemplate.keySet())); } From fe6714af0224385f84395e9e59d4ba3872f7823f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 18 Dec 2019 12:36:12 +0100 Subject: [PATCH 398/602] Update mailer docs - mention MailTemplate --- docs/src/main/asciidoc/mailer.adoc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index 148c0ced3d535..9dcb2b8c4e663 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -197,6 +197,33 @@ By spec, when you create the inline attachment, the content-id must be structure If you don't wrap your content-id between `<>`, it is automatically wrapped for you. When you want to reference your attachment, for instance in the `src` attribute, use `cid:id@domain` (without the `<` and `>`). +== Message Body Based on Qute Templates + +It's also possible to inject a mail template, where the message body is created automatically using link:qute[Qute templates]. + +[source, java] +---- +@Inject +MailTemplate hello; <1> + +@GET +@Path("/mail") +public CompletionStage send() { + return hello.to("to@acme.org") <2> + .subject("Hello from Qute template") + // the template looks like: Hello {name}! + .data("name", "John") <3> + .send() <4> + .thenApply(x -> Response.accepted().build()); +} +---- +<1> If there is no `@ResourcePath` qualifier provided, the field name is used to locate the template. In this particular case, we will use the `hello.html` and `hello.txt` templates to create the message body. +<2> Create a mail template instance and set the recipient. +<3> Set the data used in the template. +<4> `MailTemplate.send()` triggers the rendering and, once finished, sends the e-mail via a `Mailer` instance. + +TIP: Injected mail templates are validated during build. If there is no matching template in `src/main/resources/templates` the build fails. + == Testing email sending Because it is very inconvenient to send emails during development and testing, you can set the `quarkus.mailer.mock` boolean From 9291fa497dd636952b47ae1be266711e3119ac47 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2019 20:22:13 +0000 Subject: [PATCH 399/602] Bump debezium-core from 0.10.0.Final to 1.0.0.Final Bumps [debezium-core](https://github.com/debezium/debezium) from 0.10.0.Final to 1.0.0.Final. - [Release notes](https://github.com/debezium/debezium/releases) - [Changelog](https://github.com/debezium/debezium/blob/master/CHANGELOG.md) - [Commits](https://github.com/debezium/debezium/compare/v0.10.0.Final...v1.0.0.Final) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 723111254db5e..66e3c187551ed 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -128,7 +128,7 @@ 0.0.10 2.3.1 2.3.1 - 0.10.0.Final + 1.0.0.Final 3.4.14 2.12.9 From 33d878a21ad29a0d2b08f5d7ecba32cdfacd39d4 Mon Sep 17 00:00:00 2001 From: Andrew Guibert Date: Mon, 16 Dec 2019 11:26:39 -0600 Subject: [PATCH 400/602] Transfer JAX-B annotations from fields to accessor For Panache entity enhancement we add @XmlTransient to the original fields to force Jackson to use the generated accessor methods. However, this can cause conflicts if the field was originally annotated with JAX-B annotations that are mutually exclusive with @XmlTransient. Signed-off-by: Andrew Guibert --- .../common/deployment/EntityField.java | 14 ++++++ .../deployment/PanacheEntityEnhancer.java | 26 ++++++++-- .../io/quarkus/it/panache/JAXBEntity.java | 27 +++++++++++ .../io/quarkus/it/panache/TestEndpoint.java | 48 +++++++++++++++++++ .../it/panache/PanacheFunctionalityTest.java | 8 ++++ 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/JAXBEntity.java diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java index 8b53d73281ae9..459016407fa42 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java @@ -1,5 +1,8 @@ package io.quarkus.panache.common.deployment; +import java.util.HashSet; +import java.util.Set; + import io.quarkus.deployment.bean.JavaBeanUtil; public class EntityField { @@ -7,6 +10,7 @@ public class EntityField { public final String name; public final String descriptor; public String signature; + public final Set annotations = new HashSet<>(2); public EntityField(String name, String descriptor) { this.name = name; @@ -21,4 +25,14 @@ public String getSetterName() { return JavaBeanUtil.getSetterName(name); } + public static class EntityFieldAnnotation { + public final String descriptor; + public String name; + public Object value; + + public EntityFieldAnnotation(String desc) { + this.descriptor = desc; + } + } + } diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java index 9e756db4bec9a..b33dc085ed119 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java @@ -21,6 +21,7 @@ import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.deployment.EntityField.EntityFieldAnnotation; public abstract class PanacheEntityEnhancer> implements BiFunction { @@ -33,6 +34,7 @@ public abstract class PanacheEntityEnhancer Date: Mon, 16 Dec 2019 15:44:55 -0600 Subject: [PATCH 401/602] Support annotations with nested annotation arrays --- .../common/deployment/EntityField.java | 32 +++++++++++++++- .../deployment/PanacheEntityEnhancer.java | 20 +++------- .../PanacheMovingAnnotationVisitor.java | 37 +++++++++++++++++++ .../io/quarkus/it/panache/JAXBEntity.java | 8 ++++ .../io/quarkus/it/panache/TestEndpoint.java | 11 ++++++ 5 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMovingAnnotationVisitor.java diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java index 459016407fa42..703c4bdc1d1ca 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/EntityField.java @@ -1,8 +1,16 @@ package io.quarkus.panache.common.deployment; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.MethodVisitor; + import io.quarkus.deployment.bean.JavaBeanUtil; public class EntityField { @@ -27,12 +35,32 @@ public String getSetterName() { public static class EntityFieldAnnotation { public final String descriptor; - public String name; - public Object value; + public final Map attributes = new HashMap<>(2); + public final List nestedAnnotations = new ArrayList<>(2); public EntityFieldAnnotation(String desc) { this.descriptor = desc; } + + public void writeToVisitor(MethodVisitor mv) { + AnnotationVisitor av = mv.visitAnnotation(descriptor, true); + for (Entry e : attributes.entrySet()) { + av.visit(e.getKey(), e.getValue()); + } + if (!nestedAnnotations.isEmpty()) { + // Always visit nested annotations as an array because this covers JAX-B annotations + AnnotationVisitor nestedVisitor = av.visitArray("value"); + for (EntityFieldAnnotation nestedAnno : nestedAnnotations) { + AnnotationVisitor arrayAnnoVisitor = nestedVisitor.visitAnnotation(null, nestedAnno.descriptor); + for (Entry e : nestedAnno.attributes.entrySet()) { + arrayAnnoVisitor.visit(e.getKey(), e.getValue()); + } + arrayAnnoVisitor.visitEnd(); + } + nestedVisitor.visitEnd(); + } + av.visitEnd(); + } } } diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java index b33dc085ed119..c26c31e49c7ab 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java @@ -84,17 +84,12 @@ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { descriptors.add(descriptor); if (!descriptor.startsWith(JAXB_ANNOTATION_PREFIX)) { return super.visitAnnotation(descriptor, visible); + } else { + // Save off JAX-B annotations on the field so they can be applied to the generated getter later + EntityFieldAnnotation efAnno = new EntityFieldAnnotation(descriptor); + ef.annotations.add(efAnno); + return new PanacheMovingAnnotationVisitor(efAnno); } - // Save off JAX-B annotations on the field so they can be applied to the generated getter later - EntityFieldAnnotation efAnno = new EntityFieldAnnotation(descriptor); - ef.annotations.add(efAnno); - return new AnnotationVisitor(Opcodes.ASM7) { - @Override - public void visit(String name, Object value) { - efAnno.name = name; - efAnno.value = value; - } - }; } @Override @@ -200,10 +195,7 @@ private void generateAccessors() { mv.visitMaxs(0, 0); // Apply JAX-B annotations that are being transferred from the field for (EntityFieldAnnotation anno : field.annotations) { - AnnotationVisitor av = mv.visitAnnotation(anno.descriptor, true); - if (anno.name != null) - av.visit(anno.name, anno.value); - av.visitEnd(); + anno.writeToVisitor(mv); } mv.visitEnd(); } diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMovingAnnotationVisitor.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMovingAnnotationVisitor.java new file mode 100644 index 0000000000000..434973d57edf3 --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMovingAnnotationVisitor.java @@ -0,0 +1,37 @@ +package io.quarkus.panache.common.deployment; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.panache.common.deployment.EntityField.EntityFieldAnnotation; + +/** + * An AnnotationVisitor that intercepts and records annotations so that they can + * be applied to a different element later + */ +public class PanacheMovingAnnotationVisitor extends AnnotationVisitor { + + private final EntityFieldAnnotation fieldAnno; + + public PanacheMovingAnnotationVisitor(EntityFieldAnnotation fieldAnno) { + super(Opcodes.ASM7); + this.fieldAnno = fieldAnno; + } + + @Override + public void visit(String name, Object value) { + fieldAnno.attributes.put(name, value); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + EntityFieldAnnotation nestedAnno = new EntityFieldAnnotation(descriptor); + fieldAnno.nestedAnnotations.add(nestedAnno); + return new PanacheMovingAnnotationVisitor(nestedAnno); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } +} diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/JAXBEntity.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/JAXBEntity.java index 112294af1aaaf..80783566da025 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/JAXBEntity.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/JAXBEntity.java @@ -4,6 +4,8 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; @@ -23,5 +25,11 @@ public class JAXBEntity extends PanacheEntity { @XmlAttribute public String defaultAnnotatedProp; + @XmlElements({ + @XmlElement(name = "array1"), + @XmlElement(name = "array2") + }) + public String arrayAnnotatedProp; + public String unAnnotatedProp; } diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java index 4175883282e7b..19395e2e63ce0 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java @@ -22,6 +22,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlTransient; import org.hibernate.engine.spi.SelfDirtinessTracker; @@ -1032,11 +1033,21 @@ public String testJaxbAnnotationTransfer() throws Exception { assertNull(m.getAnnotation(XmlAttribute.class)); assertNotNull(m.getAnnotation(XmlTransient.class)); + m = JAXBEntity.class.getMethod("getArrayAnnotatedProp"); + assertNull(m.getAnnotation(XmlTransient.class)); + XmlElements elementsAnno = m.getAnnotation(XmlElements.class); + assertNotNull(elementsAnno); + assertNotNull(elementsAnno.value()); + assertEquals(2, elementsAnno.value().length); + assertEquals("array1", elementsAnno.value()[0].name()); + assertEquals("array2", elementsAnno.value()[1].name()); + // Ensure that all original fields were labeled @XmlTransient and had their original JAX-B annotations removed ensureFieldSanitized("namedAnnotatedProp"); ensureFieldSanitized("transientProp"); ensureFieldSanitized("defaultAnnotatedProp"); ensureFieldSanitized("unAnnotatedProp"); + ensureFieldSanitized("arrayAnnotatedProp"); return "OK"; } From ba5e76157fc9806817bac6cc16c722eff2bb66ab Mon Sep 17 00:00:00 2001 From: Grzegorz Piwowarek Date: Fri, 20 Dec 2019 07:28:08 +0100 Subject: [PATCH 402/602] Update writing-extensions.adoc --- docs/src/main/asciidoc/writing-extensions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 3e4a2fff423ef..cb83ee6db300f 100755 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -547,7 +547,7 @@ public ServiceWriterBuildItem registerOneService() { * providers of multiple configuration-related services. */ @BuildStep -public void registerSeveralSerivces( +public void registerSeveralServices( BuildProducer providerProducer ) { providerProducer.produce(new ServiceWriterBuildItem( From 76177a70830528f3794840da10367ad940bfe063 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 20 Dec 2019 10:37:48 +0530 Subject: [PATCH 403/602] Prevent UnsupportedOperationException in addSourcePaths --- .../src/main/java/io/quarkus/dev/DevModeContext.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java index ba3f3d19da602..6d4a0e0d2cc05 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java @@ -5,7 +5,9 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -163,7 +165,7 @@ public ModuleInfo( String resourcePath) { this.name = name; this.projectDirectory = projectDirectory; - this.sourcePaths = sourcePaths; + this.sourcePaths = sourcePaths == null ? new HashSet<>() : new HashSet<>(sourcePaths); this.classesPath = classesPath; this.resourcePath = resourcePath; } @@ -177,7 +179,7 @@ public String getProjectDirectory() { } public Set getSourcePaths() { - return sourcePaths; + return Collections.unmodifiableSet(sourcePaths); } public void addSourcePaths(Collection additionalPaths) { From 0c331d98bf97dab94a47f2ffa0ead697aae7abd9 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Fri, 13 Dec 2019 14:03:31 +0100 Subject: [PATCH 404/602] Provided ManagedExecutor and ThreadContext beans should be default so that users can override them easily. --- .../context/test/CustomProducersTest.java | 41 +++++++++++++++++++ .../io/quarkus/context/test/ProducerBean.java | 33 +++++++++++++++ .../SmallRyeContextPropagationProvider.java | 3 ++ 3 files changed, 77 insertions(+) create mode 100644 extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/CustomProducersTest.java create mode 100644 extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ProducerBean.java diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/CustomProducersTest.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/CustomProducersTest.java new file mode 100644 index 0000000000000..60dd521ed3fc6 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/CustomProducersTest.java @@ -0,0 +1,41 @@ +package io.quarkus.context.test; + +import javax.inject.Inject; + +import org.eclipse.microprofile.context.ManagedExecutor; +import org.eclipse.microprofile.context.ThreadContext; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.ClientProxy; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that user can override default beans for {@code ManagedExecutor} and {@code ThreadContext}. + * Default beans are singletons (no proxy) whereas the newly defined beans here are application scoped. + * Therefore it is enough to check that the injected values are proxied. + */ +public class CustomProducersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ProducerBean.class)); + + @Inject + ManagedExecutor me; + + @Inject + ThreadContext tc; + + @Test + public void testDefaultBeansCanBeOverriden() { + Assertions.assertNotNull(me); + Assertions.assertNotNull(tc); + Assertions.assertTrue(me instanceof ClientProxy); + Assertions.assertTrue(tc instanceof ClientProxy); + } +} diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ProducerBean.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ProducerBean.java new file mode 100644 index 0000000000000..6791140938600 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ProducerBean.java @@ -0,0 +1,33 @@ +package io.quarkus.context.test; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.eclipse.microprofile.context.ManagedExecutor; +import org.eclipse.microprofile.context.ThreadContext; + +@ApplicationScoped +public class ProducerBean { + + @Produces + @ApplicationScoped + public ThreadContext getAllThreadContext() { + return ThreadContext.builder().propagated(ThreadContext.ALL_REMAINING).cleared().unchanged().build(); + } + + @ApplicationScoped + @Produces + public ManagedExecutor getAllManagedExecutor() { + return ManagedExecutor.builder().build(); + } + + @Produces + public String doIt() { + return "foo"; + } + + public void disposeExecutor(@Disposes ManagedExecutor me) { + me.shutdown(); + } +} diff --git a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationProvider.java b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationProvider.java index 10c979d42da06..87de11f26c9f1 100644 --- a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationProvider.java +++ b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationProvider.java @@ -11,6 +11,7 @@ import org.eclipse.microprofile.context.ManagedExecutor; import org.eclipse.microprofile.context.ThreadContext; +import io.quarkus.arc.DefaultBean; import io.smallrye.context.SmallRyeManagedExecutor; import io.smallrye.context.SmallRyeThreadContext; @@ -36,6 +37,7 @@ public List shutdownNow() { @Produces @Singleton + @DefaultBean public ThreadContext getAllThreadContext() { return ThreadContext.builder().propagated(ThreadContext.ALL_REMAINING).cleared().unchanged().build(); } @@ -43,6 +45,7 @@ public ThreadContext getAllThreadContext() { @Typed(ManagedExecutor.class) @Produces @Singleton + @DefaultBean public ManagedExecutor getAllManagedExecutor() { return managedExecutor; } From 1ac04aad44e36d1d0b9dcf1891c5ad5fbd37c09f Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Thu, 19 Dec 2019 17:37:25 +0100 Subject: [PATCH 405/602] Fix table design on safari and mobiles --- docs/src/main/asciidoc/stylesheet/config.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/stylesheet/config.css b/docs/src/main/asciidoc/stylesheet/config.css index 8e3d860bc6ea3..9a4a4fc7048f4 100644 --- a/docs/src/main/asciidoc/stylesheet/config.css +++ b/docs/src/main/asciidoc/stylesheet/config.css @@ -1,6 +1,7 @@ table.configuration-reference.tableblock { border-collapse: separate; border-spacing: 1px; + border: none; } table.configuration-reference.tableblock thead th.tableblock { @@ -21,7 +22,6 @@ table.configuration-reference.tableblock tbody tr th { border: none; border-bottom: 1px solid #4695eb; vertical-align: bottom; - color: white; } table.configuration-reference.tableblock tbody tr:first-child th { @@ -37,6 +37,7 @@ table.configuration-reference.tableblock tbody tr td:nth-child(3) { table.configuration-reference.tableblock tbody tr th:nth-child(2) p, table.configuration-reference.tableblock tbody tr th:nth-child(3) p { font-weight: normal; + color: black; } table.configuration-reference.tableblock tbody tr th p { @@ -45,6 +46,7 @@ table.configuration-reference.tableblock tbody tr th p { table.configuration-reference.tableblock tbody tr td { padding-left: 30px; + border: none; } table.configuration-reference.tableblock tbody tr td > .content > .paragraph .icon { @@ -127,7 +129,6 @@ input#config-search-0 { input#config-search-0 { color: color(#4695eb a(0.8)); - text-transform: uppercase; letter-spacing: 1.5px; } @@ -153,4 +154,3 @@ table.configuration-reference.tableblock .description-decoration i { font-size: 0.7rem; color: #4695eb; } - From dbca0e3d27a774c05756eb3314cb737913d62fea Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Thu, 19 Dec 2019 17:41:07 +0100 Subject: [PATCH 406/602] Use uppercase only in placeholder for the config search --- docs/src/main/asciidoc/javascript/config.js | 2 +- docs/src/main/asciidoc/stylesheet/config.css | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/javascript/config.js b/docs/src/main/asciidoc/javascript/config.js index 22bbd6187b13b..b3b6f603c04e1 100644 --- a/docs/src/main/asciidoc/javascript/config.js +++ b/docs/src/main/asciidoc/javascript/config.js @@ -13,7 +13,7 @@ if(tables){ if (table.classList.contains('searchable')) { // activate search engine only when needed var input = document.createElement("input"); input.setAttribute("type", "search"); - input.setAttribute("placeholder", "filter configuration"); + input.setAttribute("placeholder", "FILTER CONFIGURATION"); input.id = "config-search-"+(idx++); caption.children.item(0).appendChild(input); input.addEventListener("keyup", initiateSearch); diff --git a/docs/src/main/asciidoc/stylesheet/config.css b/docs/src/main/asciidoc/stylesheet/config.css index 9a4a4fc7048f4..76d10fa97bb2a 100644 --- a/docs/src/main/asciidoc/stylesheet/config.css +++ b/docs/src/main/asciidoc/stylesheet/config.css @@ -108,6 +108,7 @@ table.configuration-reference.tableblock td.tableblock > .content > :last-child } input#config-search-0 { + -webkit-appearance: none; display: block; width: 100%; margin-top: 10px; @@ -115,7 +116,7 @@ input#config-search-0 { transition: transform 250ms ease-in-out; font-size: 14px; line-height: 18px; - color: #4695eb; + color: color(#4695eb a(0.8)); background-color: transparent; background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath stroke='white' fill='white' d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -125,10 +126,6 @@ input#config-search-0 { transition: all 250ms ease-in-out; backface-visibility: hidden; transform-style: preserve-3d; -} - -input#config-search-0 { - color: color(#4695eb a(0.8)); letter-spacing: 1.5px; } From 1e1ec229a61c47fbfe9344bf7a7d2c6547b0b25d Mon Sep 17 00:00:00 2001 From: Arne Mejlholm <31166597+mejlholm@users.noreply.github.com> Date: Thu, 19 Dec 2019 23:22:34 +0100 Subject: [PATCH 407/602] Mention possibility to disable JSON for non prod Remove surrounding extension - it's not valid. Add small example of how to use human readable logging for dev/test and structured JSON for production. --- docs/src/main/asciidoc/logging.adoc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index e37151f25e315..23000e377ba2e 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -91,7 +91,6 @@ application POM as the following snippet illustrates. .Modifications to POM file to add the JSON logging extension [source,xml] ---- - @@ -99,13 +98,21 @@ application POM as the following snippet illustrates. quarkus-logging-json - ---- The presence of this extension will, by default, replace the output format configuration from the console configuration. This means that the format string and the color settings (if any) will be ignored. The other console configuration items (including those controlling asynchronous logging and the log level) will continue to be applied. +For some, it will make sense to use logging that is humanly readable (unstructured) in dev mode and JSON logging (structured) in production mode. This can be achieved using different profiles, as shown in the following configuration. + +.Disable JSON logging in application.properties for dev and test mode +[source, properties] +---- +%dev.quarkus.log.console.json=false +%test.quarkus.log.console.json=false +---- + ===== Configuration The JSON logging extension can be configured in various ways. The following properties are supported: From f9f5ef1679d7f189d4cb8f4381eeb2512a8404e4 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Tue, 17 Dec 2019 20:23:10 +0100 Subject: [PATCH 408/602] Include sun.security.util.Resources bundle in native image --- .../deployment/steps/ResourceBundleStep.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java new file mode 100644 index 0000000000000..f2cfba72a2ce0 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ResourceBundleStep.java @@ -0,0 +1,17 @@ +package io.quarkus.deployment.steps; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; + +public class ResourceBundleStep { + + @BuildStep + public NativeImageResourceBundleBuildItem nativeImageResourceBundle() { + /* + * The following resource bundle sometimes needs to be included into the native image with JDK 11. + * This might no longer be required if GraalVM auto-includes it in a future release. + * See https://github.com/oracle/graal/issues/2005 for more details about it. + */ + return new NativeImageResourceBundleBuildItem("sun.security.util.Resources"); + } +} From 8559533418e52fe173aff21505c9aadbc32ebe71 Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Wed, 18 Dec 2019 09:39:42 +0100 Subject: [PATCH 409/602] Devmode test for Metrics --- .../smallrye-metrics/deployment/pom.xml | 16 ++++ .../deployment/DevModeMetricsTest.java | 87 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java diff --git a/extensions/smallrye-metrics/deployment/pom.xml b/extensions/smallrye-metrics/deployment/pom.xml index 882e931391bd6..1067529b58fb1 100644 --- a/extensions/smallrye-metrics/deployment/pom.xml +++ b/extensions/smallrye-metrics/deployment/pom.xml @@ -34,6 +34,22 @@ io.quarkus quarkus-smallrye-metrics + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-resteasy-deployment + test + diff --git a/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java new file mode 100644 index 0000000000000..ebad0e6d54e14 --- /dev/null +++ b/extensions/smallrye-metrics/deployment/src/test/java/io/quarkus/smallrye/metrics/deployment/DevModeMetricsTest.java @@ -0,0 +1,87 @@ +package io.quarkus.smallrye.metrics.deployment; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.*; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +import org.eclipse.microprofile.metrics.MetricID; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.annotation.Counted; +import org.hamcrest.CoreMatchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +public class DevModeMetricsTest { + + @Path("/") + public static class MetricsResource { + + @Inject + MetricRegistry metricRegistry; + + @Counted(name = "mycounter", absolute = true) + @GET + @Path("/increment-counter") + public void countedMethod() { + } + + //MARKER-KEEP-ME + + @GET + @Path("/getvalue/{name}") + @Produces("text/plain") + public Long getCounterValue(@PathParam("name") String name) { + return metricRegistry.getCounters().get(new MetricID(name)).getCount(); + } + + } + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(DevModeMetricsTest.class, MetricsResource.class)); + + @Test + public void test() { + // increment the mycounter twice and verify its value + when().get("/increment-counter").then().statusCode(204); + when().get("/increment-counter").then().statusCode(204); + when().get("/getvalue/mycounter").then().body(equalTo("2")); + + // trigger a reload by adding a new metric (mycounter2) + TEST.modifySourceFile(DevModeMetricsTest.class, (s) -> s.replaceFirst("MARKER-KEEP-ME", + "MARKER-KEEP-ME\n" + + "@Counted(name = \"mycounter2\", absolute = true)\n" + + "@GET\n" + + "@Path(\"/increment-counter2\")\n" + + "public void countedMethod2() {\n" + + "} //MARKER-END")); + + // check that the original mycounter is re-registered and its value is zero + when().get("/getvalue/mycounter").then().body(equalTo("0")); + + // check that mycounter2 is present and can be used + when().get("/getvalue/mycounter2").then().body(equalTo("0")); + when().get("/increment-counter2").then().statusCode(204); + when().get("/getvalue/mycounter2").then().body(equalTo("1")); + + // trigger a reload by removing mycounter2 again + TEST.modifySourceFile(DevModeMetricsTest.class, + (s) -> s.replaceFirst("//MARKER-KEEP-ME[\\s\\S]+?MARKER-END", "")); + + // verify that mycounter2 no longer exists + when().get("/getvalue/mycounter2").then() + .statusCode(500) + .body(CoreMatchers.containsString("NullPointerException")); + } + +} From ac02fa3c184b641ab141ee373ff33f1aff5fb4ee Mon Sep 17 00:00:00 2001 From: Jan Martiska Date: Fri, 20 Dec 2019 10:17:25 +0100 Subject: [PATCH 410/602] Prevent throwing NPE when recorded objects have null collections --- .../io/quarkus/deployment/recording/BytecodeRecorderImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index d7274a26c0de5..c0f9977c0e031 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -1067,7 +1067,7 @@ public void prepare(MethodContext context) { handledProperties.add(i.getName()); Collection propertyValue = (Collection) i.read(param); - if (!propertyValue.isEmpty()) { + if (propertyValue != null && !propertyValue.isEmpty()) { List params = new ArrayList<>(); for (Object c : propertyValue) { @@ -1106,7 +1106,7 @@ public void prepare(MethodContext context) { handledProperties.add(i.getName()); Map propertyValue = (Map) i.read(param); - if (!propertyValue.isEmpty()) { + if (propertyValue != null && !propertyValue.isEmpty()) { Map def = new LinkedHashMap<>(); for (Map.Entry entry : propertyValue.entrySet()) { DeferredParameter key = loadObjectInstance(entry.getKey(), existing, From ee71e7afc5dece788dc8d0f368ae652077bdf12f Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 20 Dec 2019 11:32:02 +0100 Subject: [PATCH 411/602] Add note about Sentry re-licensing to BSL --- docs/src/main/asciidoc/logging-sentry.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc index f6475be0da5b1..2e7e254703fe2 100644 --- a/docs/src/main/asciidoc/logging-sentry.adoc +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -78,3 +78,5 @@ quarkus.log.sentry.dsn=https://abcd@sentry.io/1234 quarkus.log.sentry.level=ERROR quarkus.log.sentry.in-app-packages=org.example ---- + +NOTE: Sentry has recently https://blog.sentry.io/2019/11/06/relicensing-sentry[re-licensed most of its code to BSL] - which is not an OSI-approved license. The Java SDK is still under the 3-Clause BSD license. From e6d99d795fd80f8d28d27e7a8cfca34feec63fea Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 20 Dec 2019 20:07:03 +0530 Subject: [PATCH 412/602] issue-6280 Address Class-Path parsing issues with Java 11 and Windows, for tests launched using QuarkusDevModeTest --- .../io/quarkus/test/QuarkusDevModeTest.java | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java index ed079283a1d0c..6684c0d67a936 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.net.URL; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -15,10 +17,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.List; import java.util.ServiceLoader; import java.util.function.Function; import java.util.function.Supplier; +import java.util.jar.Attributes; +import java.util.jar.Manifest; import java.util.stream.Stream; import org.jboss.shrinkwrap.api.exporter.ExplodedExporter; @@ -45,13 +50,12 @@ * testing their extension functionality in dev mode. Unlike {@link QuarkusUnitTest} this will test against * a clean deployment for each test method. This is nessesary to prevent undefined behaviour by making sure the * deployment starts in a clean state for each test. - * - * + *

+ *

* NOTE: These tests do not run with {@link io.quarkus.runtime.LaunchMode#TEST} but rather with * {@link io.quarkus.runtime.LaunchMode#DEVELOPMENT}. This is necessary to ensure dev mode is tested correctly. - * + *

* A side effect of this is that the tests will run on port 8080 by default instead of port 8081. - * */ public class QuarkusDevModeTest implements BeforeEachCallback, AfterEachCallback, TestInstanceFactory { @@ -275,12 +279,64 @@ private DevModeContext exportArchive(Path deploymentDir, Path testSourceDir) { Collections.singleton(deploymentSourcePath.toAbsolutePath().toString()), classes.toAbsolutePath().toString(), deploymentResourcePath.toAbsolutePath().toString())); + setDevModeRunnerJarFile(context); return context; } catch (Exception e) { throw new RuntimeException("Unable to create the archive", e); } } + private static void setDevModeRunnerJarFile(final DevModeContext context) { + try { + /* + * See https://github.com/quarkusio/quarkus/issues/6280 + * Maven surefire plugin launches the (forked) JVM for tests using a "surefirebooter" jar file. + * This jar file's name starts with the prefix "surefirebooter" and ends with the extension ".jar". + * The jar is launched using "java -jar .../surefirebooter*.jar ..." semantics. This jar has a + * MANIFEST which contains "Class-Path" entries. These entries trigger a bug in the JDK code + * https://bugs.openjdk.java.net/browse/JDK-8232170 which causes hot deployment related logic in Quarkus + * to fail in dev mode. + * The goal in this next section is to narrow down to this specific surefirebooter*.jar which was used to launch + * the tests and mark it as the "dev mode runner jar" (through DevModeContext#setDevModeRunnerJarFile), + * so that programmatic compilation of code (during hot deployment) doesn't run into issues noted in + * https://bugs.openjdk.java.net/browse/JDK-8232170. + * In reality the surefirebooter*.jar isn't really a "dev mode runner jar" (i.e. it's not the -dev.jar that + * Quarkus generates), but it's fine to mark it as such to get past this issue. This is more of a workaround + * on top of another workaround. In the medium/long term the actual JDK issue fix will make its way into + * almost all prominently used Java versions. + */ + final Enumeration manifests = QuarkusDevModeTest.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); + while (manifests.hasMoreElements()) { + final URL url = manifests.nextElement(); + // don't open streams to manifest entries unless it resembles to the one + // we are interested in + if (!url.getPath().contains("surefirebooter")) { + continue; + } + try (final InputStream is = url.openStream()) { + final Manifest manifest = new Manifest(is); + final String mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + // additional check to make sure we are probing the right jar + if ("org.apache.maven.surefire.booter.ForkedBooter".equals(mainClass)) { + final String manifestFilePath = url.getPath(); + if (manifestFilePath.startsWith("file:")) { + // manifest file path will be of the form jar:file:....!META-INF/MANIFEST.MF + final String jarFilePath = manifestFilePath.substring(5, manifestFilePath.lastIndexOf('!')); + final File surefirebooterJar = new File( + URLDecoder.decode(jarFilePath, StandardCharsets.UTF_8.name())); + context.setDevModeRunnerJarFile(surefirebooterJar); + } + break; + } + + } + } + } catch (Throwable t) { + // ignore and move on + return; + } + } + /** * Modifies a source file. * @@ -303,7 +359,7 @@ public void modifySourceFile(Class sourceFile, Function mutat /** * Adds the source file that corresponds to the given class to the deployment - * + * * @param sourceFile */ public void addSourceFile(Class sourceFile) { From 3da9841e7551fb6ac3b23cbc88ab455a32d3d707 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Fri, 20 Dec 2019 10:42:34 +0100 Subject: [PATCH 413/602] Fix package name of Neo4j health check. --- .../java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java | 2 +- .../neo4j/runtime/{heath => health}/Neo4jHealthCheck.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/{heath => health}/Neo4jHealthCheck.java (98%) diff --git a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java index da6a6106ed1d3..e128aa1f39b4e 100644 --- a/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java +++ b/extensions/neo4j/deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java @@ -41,7 +41,7 @@ void configureDriverProducer(Neo4jDriverRecorder recorder, BeanContainerBuildIte @BuildStep HealthBuildItem addHealthCheck(Neo4jBuildTimeConfig buildTimeConfig) { - return new HealthBuildItem("io.quarkus.neo4j.runtime.heath.Neo4jHealthCheck", + return new HealthBuildItem("io.quarkus.neo4j.runtime.health.Neo4jHealthCheck", buildTimeConfig.healthEnabled, "neo4j"); } } diff --git a/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/health/Neo4jHealthCheck.java similarity index 98% rename from extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java rename to extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/health/Neo4jHealthCheck.java index f236bbde913b3..caa1bc77e6fdf 100644 --- a/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/heath/Neo4jHealthCheck.java +++ b/extensions/neo4j/runtime/src/main/java/io/quarkus/neo4j/runtime/health/Neo4jHealthCheck.java @@ -1,4 +1,4 @@ -package io.quarkus.neo4j.runtime.heath; +package io.quarkus.neo4j.runtime.health; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; From 908f30a30683509277d7a0bd72d7f7949a6d47f5 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 10 Dec 2019 11:56:49 -0300 Subject: [PATCH 414/602] Add README.md for the Gradle plugin Fixes #5804 Co-Authored-By: Guillaume Smet --- devtools/gradle/README.md | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 devtools/gradle/README.md diff --git a/devtools/gradle/README.md b/devtools/gradle/README.md new file mode 100644 index 0000000000000..ece2cc9872c1b --- /dev/null +++ b/devtools/gradle/README.md @@ -0,0 +1,46 @@ +Quarkus Gradle Plugin +===================== + +Builds a Quarkus application, and provides helpers to launch dev-mode, the Quarkus CLI and the build of native images. + +Releases are published at https://plugins.gradle.org/plugin/io.quarkus . + +Functional Tests +---------------- + +To run the functional tests, run the following command: + +```bash +./gradlew functionalTests +``` + +Local development +----------------- + +1. Build the entire Quarkus codebase by running `mvn clean install -DskipTests -DskipITs` in the project root + - This should install the Gradle plugin in your local maven repository. + +2. Create a sample project using the Maven plugin: + +```bash + mvn io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=my-gradle-project \ + -DclassName="org.acme.quickstart.GreetingResource" \ + -DplatformArtifactId=quarkus-bom \ + -Dpath="/hello" \ + -DbuildTool=gradle +``` + +Follow the instructions in the [Gradle Tooling Guide](https://quarkus.io/guides/gradle-tooling) for more information about the available commands. + +Importing using Intellij +------------------------- + +Disable "Maven Auto Import" for the Quarkus projects. Since the Gradle plugin has a pom.xml, +IntelliJ will configure this project as a Maven project. You need to configure it to be a Gradle +project. To do so, follow these instructions: + + +1. Go to File -> Project Structure +2. In Modules, remove the `quarkus-gradle-plugin` and re-import as a Gradle project. From 30e60cd4d39fe25f2ce097d721fffb49911899e1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2019 20:37:54 +0000 Subject: [PATCH 415/602] Bump flyway-core from 6.1.2 to 6.1.3 Bumps [flyway-core](https://github.com/flyway/flyway) from 6.1.2 to 6.1.3. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-6.1.2...flyway-6.1.3) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 66e3c187551ed..e98b299c5671e 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -147,7 +147,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.1.2 + 6.1.3 1.0.5 4.0.0 3.10.2 From fa66dfd633e5e754ce80b90fe05f9e8ad414bc20 Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sat, 21 Dec 2019 09:14:20 +0100 Subject: [PATCH 416/602] sentry.io is not opensource --- docs/src/main/asciidoc/logging-sentry.adoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc index 2e7e254703fe2..1489dee8eb3de 100644 --- a/docs/src/main/asciidoc/logging-sentry.adoc +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -17,12 +17,15 @@ include::{generated-dir}/config/quarkus-logging-sentry.adoc[opts=optional, level == Description + Sentry is a really easy way to be notified of errors happening on you Quarkus application. -It is a Open Source, Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. +It is a self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. They offer a free starter price for cloud-based or you can self host it for free. +WARNING: Sentry's Java SDK is open source, but recently sentry.io https://blog.sentry.io/2019/11/06/relicensing-sentry[changed the license] for their backend to the non-open source BSL license. This might or might not be an issue for your project and product. + == Configuration To start of, you need to get a Sentry DSN either by https://sentry.io/signup/[creating a Sentry account] or https://docs.sentry.io/server/[installing your self-hosted Sentry]. From 19c1a7358a7da320f7331e44b6adcabf1edc14c7 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Mon, 16 Dec 2019 18:10:00 -0300 Subject: [PATCH 417/602] [fixes #5926] - Attempt to make code flow test more stable --- .../io/quarkus/it/keycloak/CodeFlowTest.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index bf18f6101ddf1..ef416bee47712 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -21,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.util.JsonSerialization; +import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; @@ -134,8 +135,7 @@ private static UserRepresentation createUser(String username, String... realmRol @Test public void testCodeFlowNoConsent() throws IOException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -158,8 +158,7 @@ public void testCodeFlowNoConsent() throws IOException { @Test public void testTokenTimeoutLogout() throws IOException, InterruptedException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -173,9 +172,9 @@ public void testTokenTimeoutLogout() throws IOException, InterruptedException { assertEquals("Welcome to Test App", page.getTitleText()); - Thread.sleep(5000); + Thread.sleep(10000); - page = webClient.getPage("http://localhost:8081/index.html"); + webClient.getPage("http://localhost:8081/index.html"); Cookie sessionCookie = getSessionCookie(webClient); @@ -188,9 +187,8 @@ public void testTokenTimeoutLogout() throws IOException, InterruptedException { } @Test - public void testIdTokenInjection() throws IOException, InterruptedException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + public void testIdTokenInjection() throws IOException { + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -211,9 +209,8 @@ public void testIdTokenInjection() throws IOException, InterruptedException { } @Test - public void testAccessTokenInjection() throws IOException, InterruptedException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + public void testAccessTokenInjection() throws IOException { + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -234,9 +231,8 @@ public void testAccessTokenInjection() throws IOException, InterruptedException } @Test - public void testAccessAndRefreshTokenInjection() throws IOException, InterruptedException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + public void testAccessAndRefreshTokenInjection() throws IOException { + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/index.html"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -258,8 +254,7 @@ public void testAccessAndRefreshTokenInjection() throws IOException, Interrupted @Test public void testAccessAndRefreshTokenInjectionWithoutIndexHtml() throws IOException, InterruptedException { - try (final WebClient webClient = new WebClient()) { - webClient.getCache().setMaxSize(0); + try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh"); assertEquals("Log in to quarkus", page.getTitleText()); @@ -283,6 +278,14 @@ public void testNoCodeFlowUnprotected() { .body(Matchers.equalTo("no user")); } + private WebClient createWebClient() { + WebClient webClient = new WebClient(); + + webClient.setCssErrorHandler(new SilentCssErrorHandler()); + + return webClient; + } + private Cookie getSessionCookie(WebClient webClient) { return webClient.getCookieManager().getCookie("q_session"); } From cea8d45bcd208b2268422f7530676b7fdf868614 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sat, 21 Dec 2019 18:02:01 +0100 Subject: [PATCH 418/602] Only have one warning about the non-open source Sentry license --- docs/src/main/asciidoc/logging-sentry.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc index 1489dee8eb3de..5de07cbbe86df 100644 --- a/docs/src/main/asciidoc/logging-sentry.adoc +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -24,7 +24,7 @@ It is a self-hosted and cloud-based error monitoring that helps software teams d They offer a free starter price for cloud-based or you can self host it for free. -WARNING: Sentry's Java SDK is open source, but recently sentry.io https://blog.sentry.io/2019/11/06/relicensing-sentry[changed the license] for their backend to the non-open source BSL license. This might or might not be an issue for your project and product. +WARNING: Sentry's Java SDK is open source, but recently sentry.io https://blog.sentry.io/2019/11/06/relicensing-sentry[changed the license] for their backend to the non-open source https://github.com/getsentry/sentry/blob/master/LICENSE[BSL license]. This might or might not be an issue for your project and product. == Configuration @@ -82,4 +82,3 @@ quarkus.log.sentry.level=ERROR quarkus.log.sentry.in-app-packages=org.example ---- -NOTE: Sentry has recently https://blog.sentry.io/2019/11/06/relicensing-sentry[re-licensed most of its code to BSL] - which is not an OSI-approved license. The Java SDK is still under the 3-Clause BSD license. From f99c09893204864f4b42eb03b5dec915f4a36e99 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 22 Dec 2019 12:54:26 +0200 Subject: [PATCH 419/602] Use same all-open configuration for generated Kotlin Maven projects as Gradle --- .../resources/templates/basic-rest/kotlin/pom.xml-template.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl index 05aab87c9ea2f..2760fe516d630 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl @@ -114,6 +114,8 @@ + + From 24b9edd2125f3d8cd34bd679029b3dc037194e41 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Sun, 22 Dec 2019 13:17:49 +0200 Subject: [PATCH 420/602] Ensure that the Kotlin compiler adds method param names to bytecode We already do this for Java, so let's make it consistent for Kotlin too Fixes: #6041 --- .../templates/basic-rest/kotlin/build.gradle-template.ftl | 1 + .../resources/templates/basic-rest/kotlin/pom.xml-template.ftl | 1 + .../io/quarkus/kotlin/deployment/KotlinCompilationProvider.java | 1 + 3 files changed, 3 insertions(+) diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl index ecd42c7355c36..f604d4391c0da 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/build.gradle-template.ftl @@ -42,6 +42,7 @@ java { compileKotlin { kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 + kotlinOptions.javaParameters = true } compileTestKotlin { diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl index 2760fe516d630..a20ef9c175dec 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl @@ -106,6 +106,7 @@ + true all-open diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java index b727268450aa6..4a8350e1014a1 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinCompilationProvider.java @@ -37,6 +37,7 @@ public Set handledExtensions() { @Override public void compile(Set filesToCompile, Context context) { K2JVMCompilerArguments compilerArguments = new K2JVMCompilerArguments(); + compilerArguments.setJavaParameters(true); if (context.getCompilePluginArtifacts() != null && !context.getCompilePluginArtifacts().isEmpty()) { compilerArguments.setPluginClasspaths(context.getCompilePluginArtifacts().toArray(new String[0])); } From d65329ed7286b823849aac45a1fe07e014e022d2 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 12:33:58 -0300 Subject: [PATCH 421/602] Adopt new label nomenclature in issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/epic.md | 2 +- .github/ISSUE_TEMPLATE/extension_proposal.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/housekeeping.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f59ffe5e0d68a..2dd5915408ce5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Report a bug in Quarkus title: '' -labels: bug +labels: kind/bug assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/epic.md b/.github/ISSUE_TEMPLATE/epic.md index 501498789e741..88ba1f93b76bf 100644 --- a/.github/ISSUE_TEMPLATE/epic.md +++ b/.github/ISSUE_TEMPLATE/epic.md @@ -2,7 +2,7 @@ name: Epic about: " A large piece of work requiring high-level visibility and planning" title: '' -labels: Epic +labels: kind/epic assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/extension_proposal.md b/.github/ISSUE_TEMPLATE/extension_proposal.md index 32b46ba0e0dcd..2c464154e2940 100644 --- a/.github/ISSUE_TEMPLATE/extension_proposal.md +++ b/.github/ISSUE_TEMPLATE/extension_proposal.md @@ -2,7 +2,7 @@ name: Extension Proposal about: Propose a new extension in Quarkus title: '' -labels: extension-proposal +labels: kind/extension-proposal assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5264caf315ff8..4a64304b61371 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Request or propose a new feature title: '' -labels: enhancement +labels: kind/enhancement assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/housekeeping.md b/.github/ISSUE_TEMPLATE/housekeeping.md index adc19d2b832b4..88487e4539ef6 100644 --- a/.github/ISSUE_TEMPLATE/housekeeping.md +++ b/.github/ISSUE_TEMPLATE/housekeeping.md @@ -2,7 +2,7 @@ name: Housekeeping about: A generalized task or cleanup not associated with a bug report or enhancement title: '' -labels: housekeeping +labels: area/housekeeping assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 30646816dd2fe..4231f9dd88167 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -2,7 +2,7 @@ name: Question about: I have a question about something... title: '' -labels: question +labels: kind/question assignees: '' --- From 16f88e94e33cc7be8d9da4b2eba19d80109b0164 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 09:25:05 -0300 Subject: [PATCH 422/602] Added back methods mistakenly considered unused in QuarkusPluginExtension These methods were removed in 3265edd082955d6f5b0498dfeb79033c2c073219 because they were considered unused but they are used in the Kotlin Gradle build. Added tests for all SourceTypes now Fixes #6311 --- .../gradle/QuarkusPluginFunctionalTest.java | 25 ++++++++++++++----- .../gradle/QuarkusPluginExtension.java | 20 +++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index 8042cb4dbd070..d36f406899b04 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -9,19 +9,30 @@ import io.quarkus.cli.commands.CreateProject; import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.generators.BuildTool; +import io.quarkus.generators.SourceType; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import static org.assertj.core.api.Assertions.assertThat; public class QuarkusPluginFunctionalTest { + private File projectRoot; + + @BeforeEach + void setUp(@TempDir File projectRoot) { + this.projectRoot = projectRoot; + } + @Test - public void canRunListExtensions(@TempDir File projectRoot) throws IOException { - createProject(projectRoot); + public void canRunListExtensions() throws IOException { + createProject(SourceType.JAVA); BuildResult build = GradleRunner.create() .forwardOutput() @@ -34,9 +45,10 @@ public void canRunListExtensions(@TempDir File projectRoot) throws IOException { assertThat(build.getOutput()).contains("Quarkus - Core"); } - @Test - public void canBuild(@TempDir File projectRoot) throws IOException { - createProject(projectRoot); + @ParameterizedTest(name = "Build {0} project") + @EnumSource(SourceType.class) + public void canBuild(SourceType sourceType) throws IOException { + createProject(sourceType); BuildResult build = GradleRunner.create() .forwardOutput() @@ -60,12 +72,13 @@ private List arguments(String argument) { return arguments; } - private void createProject(@TempDir File projectRoot) throws IOException { + private void createProject(SourceType sourceType) throws IOException { assertThat(new CreateProject(new FileProjectWriter(projectRoot)) .groupId("com.acme.foo") .artifactId("foo") .version("1.0.0-SNAPSHOT") .buildTool(BuildTool.GRADLE) + .sourceType(sourceType) .doCreateProject(new HashMap<>())) .withFailMessage("Project was not created") .isTrue(); diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 6141059a686d4..ca47aeca28cbc 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -36,6 +36,10 @@ public File outputDirectory() { return new File(outputDirectory); } + public void setOutputDirectory(String outputDirectory) { + this.outputDirectory = outputDirectory; + } + public File outputConfigDirectory() { if (outputConfigDirectory == null) { outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) @@ -44,6 +48,10 @@ public File outputConfigDirectory() { return new File(outputConfigDirectory); } + public void setOutputConfigDirectory(String outputConfigDirectory) { + this.outputConfigDirectory = outputConfigDirectory; + } + public File sourceDir() { if (sourceDir == null) { sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) @@ -52,6 +60,10 @@ public File sourceDir() { return new File(sourceDir); } + public void setSourceDir(String sourceDir) { + this.sourceDir = sourceDir; + } + public File workingDir() { if (workingDir == null) { workingDir = outputDirectory().getPath(); @@ -60,6 +72,10 @@ public File workingDir() { return new File(workingDir); } + public void setWorkingDir(String workingDir) { + this.workingDir = workingDir; + } + public String finalName() { if (finalName == null || finalName.length() == 0) { this.finalName = String.format("%s-%s", project.getName(), project.getVersion()); @@ -67,6 +83,10 @@ public String finalName() { return finalName; } + public void setFinalName(String finalName) { + this.finalName = finalName; + } + public Set resourcesDir() { return project.getConvention().getPlugin(JavaPluginConvention.class) .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getResources().getSrcDirs(); From ea7848c8efd1b3738369661390ebf35de33fc312 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 11:57:10 -0300 Subject: [PATCH 423/602] Show stacktrace when building the gradle plugin --- devtools/gradle/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index d41f3f8654bd1..e7ec22027c3b6 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -64,6 +64,7 @@ ${gradle.task} -Pdescription=${project.description} -S + --stacktrace ${settings.localRepository} From 57b1fc4520db2439fec402aee209aebab3bfcd09 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 12:29:50 -0300 Subject: [PATCH 424/602] Disabling Scala gradle build test for now --- .../java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index d36f406899b04..29b1a25bf92a4 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -46,7 +46,8 @@ public void canRunListExtensions() throws IOException { } @ParameterizedTest(name = "Build {0} project") - @EnumSource(SourceType.class) + //TODO: Fix Scala build in Windows + @EnumSource(value = SourceType.class, names = {"JAVA","KOTLIN"}) public void canBuild(SourceType sourceType) throws IOException { createProject(sourceType); From cbe0a1689f747fae26d64377ee73164b748a6b4c Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 23 Dec 2019 00:34:30 -0300 Subject: [PATCH 425/602] basic-rest/scala/build.gradle-template.ftl requires scala-library Fixes #6316 --- .../templates/basic-rest/scala/build.gradle-template.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl index 821abd974352e..9603752b64751 100644 --- a/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl +++ b/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/scala/build.gradle-template.ftl @@ -10,6 +10,7 @@ repositories { dependencies { implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'org.scala-lang:scala-library:${scala_version}' implementation 'io.quarkus:quarkus-resteasy' testImplementation 'io.quarkus:quarkus-junit5' From 29ba6aeca44b6c2d2d63a7904e9344a770e95f00 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 23:34:22 -0300 Subject: [PATCH 426/602] Fixes Illegal char error in Gradle plugin --- .../gradle/QuarkusPluginExtension.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index ca47aeca28cbc..3e3a0c27bfec8 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -4,6 +4,7 @@ import java.util.Set; import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; @@ -14,66 +15,65 @@ public class QuarkusPluginExtension { private final Project project; - private String outputDirectory; + private File outputDirectory; private String finalName; - private String sourceDir; + private File sourceDir; - private String workingDir; + private File workingDir; - private String outputConfigDirectory; + private File outputConfigDirectory; public QuarkusPluginExtension(Project project) { this.project = project; } public File outputDirectory() { - if (outputDirectory == null) - outputDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs().getAsPath(); - - return new File(outputDirectory); + if (outputDirectory == null) { + outputDirectory = getLastFile(project.getConvention().getPlugin(JavaPluginConvention.class) + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getClassesDirs()); + } + return outputDirectory; } public void setOutputDirectory(String outputDirectory) { - this.outputDirectory = outputDirectory; + this.outputDirectory = new File(outputDirectory); } public File outputConfigDirectory() { if (outputConfigDirectory == null) { outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir().getAbsolutePath(); + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir(); } - return new File(outputConfigDirectory); + return outputConfigDirectory; } public void setOutputConfigDirectory(String outputConfigDirectory) { - this.outputConfigDirectory = outputConfigDirectory; + this.outputConfigDirectory = new File(outputConfigDirectory); } public File sourceDir() { if (sourceDir == null) { - sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) - .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava().getSourceDirectories().getAsPath(); + sourceDir = getLastFile(project.getConvention().getPlugin(JavaPluginConvention.class) + .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getAllJava().getSourceDirectories()); } - return new File(sourceDir); + return sourceDir; } public void setSourceDir(String sourceDir) { - this.sourceDir = sourceDir; + this.sourceDir = new File(sourceDir); } public File workingDir() { if (workingDir == null) { - workingDir = outputDirectory().getPath(); + workingDir = outputDirectory(); } - - return new File(workingDir); + return workingDir; } public void setWorkingDir(String workingDir) { - this.workingDir = workingDir; + this.workingDir = new File(workingDir); } public String finalName() { @@ -100,4 +100,16 @@ public AppArtifact getAppArtifact() { public AppModelResolver resolveAppModel() { return new AppModelGradleResolver(project); } + + /** + * Returns the last file from the specified {@link FileCollection}. + * Needed for the Scala plugin. + */ + private File getLastFile(FileCollection fileCollection) { + File result = null; + for (File f : fileCollection) { + result = f; + } + return result; + } } From 5d056454c6b54f514f2552109d529ba7b9f1d860 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sun, 22 Dec 2019 23:34:35 -0300 Subject: [PATCH 427/602] Revert "Disabling Scala gradle build test for now" This reverts commit b3ea880dabcbbeb135f5d47879e91838aa11016a. --- .../java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index 29b1a25bf92a4..d36f406899b04 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -46,8 +46,7 @@ public void canRunListExtensions() throws IOException { } @ParameterizedTest(name = "Build {0} project") - //TODO: Fix Scala build in Windows - @EnumSource(value = SourceType.class, names = {"JAVA","KOTLIN"}) + @EnumSource(SourceType.class) public void canBuild(SourceType sourceType) throws IOException { createProject(sourceType); From b6a7fd99c625a9d9854cff836e4c85c4f3108afb Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Mon, 23 Dec 2019 00:05:13 -0300 Subject: [PATCH 428/602] Set className and path to trigger sample code generation --- .../java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index d36f406899b04..c43693b5d0289 100644 --- a/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/devtools/gradle/src/functionalTest/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import io.quarkus.cli.commands.CreateProject; import io.quarkus.cli.commands.writer.FileProjectWriter; @@ -73,13 +74,16 @@ private List arguments(String argument) { } private void createProject(SourceType sourceType) throws IOException { + Map context = new HashMap<>(); + context.put("path", "/greeting"); assertThat(new CreateProject(new FileProjectWriter(projectRoot)) .groupId("com.acme.foo") .artifactId("foo") .version("1.0.0-SNAPSHOT") .buildTool(BuildTool.GRADLE) + .className("org.acme.GreetingResource") .sourceType(sourceType) - .doCreateProject(new HashMap<>())) + .doCreateProject(context)) .withFailMessage("Project was not created") .isTrue(); } From 2d2102bee18f305ebad7d089e601cd2b6a9e9df3 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 23 Dec 2019 13:53:43 +0200 Subject: [PATCH 429/602] Increase timeout in one of the native image stages that frequently fails in CI --- ci-templates/stages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 035df977c0bcd..f76c9eb0bf4b5 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -239,7 +239,7 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 25 + timeoutInMinutes: 30 modules: - artemis-core - artemis-jms From f97fac41c3b6ad18b9448a7b5f4e69d897d35b12 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 16:47:20 +0100 Subject: [PATCH 430/602] Fix a source extract in Qute documentation --- docs/src/main/asciidoc/qute.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index f0a3170105443..1efa8f0ca1c16 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -33,6 +33,7 @@ In your `pom.xml` file, add: We'll start with a very simple template: .hello.txt +[source] ---- Hello {name}! <1> ---- From c30062717dbc4cff5bced0c4848360d72708dc7b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 17:09:05 +0100 Subject: [PATCH 431/602] Be a bit more consistent in Qute documentation indentation --- docs/src/main/asciidoc/qute-reference.adoc | 97 +++++++++++----------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 36c3fa283f8ad..bdc22a6d99bce 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -25,7 +25,7 @@ We will always need some *template contents*: [source,html] ---- -

Hello {name}! <1> +

Hello {name}! <1> ---- <1> `{name}` is a value expression that is evaluated when the template is rendered. @@ -144,7 +144,7 @@ A typical example is the for/each loop - during iteration the content of the sec [source] ---- {#each items} - {count}. {it.name} <1> + {count}. {it.name} <1> {/each} {! Another form of iteration... !} @@ -238,7 +238,7 @@ The first one is using the `each` name alias. [source] ---- {#each items} - {it.name} <1> + {it.name} <1> {/each} ---- <1> `it` is an implicit alias. `name` is resolved against the current iteration element. @@ -257,7 +257,7 @@ It's also possible to access the iteration metadata inside the loop: [source] ---- {#each items} - {count}. {it.name} <1> + {count}. {it.name} <1> {/each} ---- <1> `count` represents one-based index. Metadata also include zero-based `index`, `hasNext`, `odd`, `even`. @@ -270,7 +270,7 @@ The simplest possible version accepts a single parameter and renders the content [source] ---- {#if item.active} - This item is active. + This item is active. {/if} ---- @@ -302,7 +302,7 @@ You can also use the following operators: [source] ---- {#if item.age > 10} - This item is very old. + This item is very old. {/if} ---- @@ -313,13 +313,13 @@ You can add any number of "else" blocks: [source] ---- {#if item.age > 10} - This item is very old. + This item is very old. {#else if item.age > 5} - This item is quite old. + This item is quite old. {#else if item.age > 2} - This item is old. + This item is old. {#else} - This item is not old at all! + This item is not old at all! {/if} ---- @@ -331,7 +331,7 @@ This could be useful to simplify the template structure. [source] ---- {#with item.parent} - {name} <1> + {name} <1> {/with} ---- <1> The name will be resolved against the `item.parent`. @@ -341,7 +341,7 @@ It's also possible to specify an alias for the context object: [source] ---- {#with item.parent as myParent} - {myParent.name} + {myParent.name} {/with} ---- @@ -359,7 +359,7 @@ These sections can be used to include another template and possibly override som {#insert title}Default Title{/} <1> - {#insert body}No body!{/} <2> + {#insert body}No body!{/} <2> ---- @@ -370,10 +370,10 @@ These sections can be used to include another template and possibly override som [source,html] ---- {#include base} <1> - {#title}My Title{/title} <2> - {#body} + {#title}My Title{/title} <2> + {#body}

- My body. + My body.
{/include} ---- @@ -414,9 +414,9 @@ We can include the tag like this: ----
    {#each items} -
  • - {#item this showImage=true /} <1> -
  • +
  • + {#item this showImage=true /} <1> +
  • {/each}
---- @@ -439,8 +439,8 @@ If you want to use Qute in your Quarkus application add the following dependency [source,xml] ---- - io.quarkus - quarkus-qute + io.quarkus + quarkus-qute ---- @@ -455,15 +455,14 @@ import io.quarkus.qute.api.ResourcePath; class MyBean { - @Inject - Template items; <1> - - @ResourcePath("detail/items2_v1.html") <2> - Template items2; - - @Inject - Engine engine; <3> - + @Inject + Template items; <1> + + @ResourcePath("detail/items2_v1.html") <2> + Template items2; + + @Inject + Engine engine; <3> } ---- <1> If there is no `ResourcePath` qualifier provided, the field name is used to locate the template. In this particular case, the container will attempt to locate a template with path `src/main/resources/templates/items.html`. @@ -503,8 +502,8 @@ NOTE: Only properties are currently validated in expressions; virtual methods ar Qute Hello -

{title}

<2> - Hello {foo.message}! <3> +

{title}

<2> + Hello {foo.message}! <3> ---- @@ -526,10 +525,10 @@ NOTE: A value resolver is also generated for all types used in parameter declara Qute Hello -

{foo.message}

<1> - {#for foo in baz.foos} -

Hello {foo.message}!

<2> - {/for} +

{foo.message}

<1> + {#for foo in baz.foos} +

Hello {foo.message}!

<2> + {/for} ---- @@ -571,7 +570,7 @@ This template extension method makes it possible to render the following templat [source,html] ---- {#each items} <1> - {it.discountedPrice} + {it.discountedPrice} {/each} ---- <1> `items` is resolved to a list of `org.acme.Item` instances. @@ -607,7 +606,7 @@ Any instance of `Item` can be used directly in the template: [source,html] ---- {#each items} <1> - {it.price} / {it.discountedPrice} + {it.price} / {it.discountedPrice} {/each} ---- <1> `items` is resolved to a list of `org.acme.Item` instances. @@ -632,7 +631,7 @@ class Item { [source,html] ---- {#each items} <1> - {it.price.setScale(2, rounding)} <1> + {it.price.setScale(2, rounding)} <1> {/each} ---- <1> The generated value resolver knows how to invoke the `BigDecimal.setScale()` method. @@ -692,15 +691,15 @@ Sometimes it could be useful to render a specific variant of the template based ---- @Path("/detail") class DetailResource { - - @Inject - VariantTemplate item; <1> - - @GET - @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN }) - public Rendering item() { - return item.data(new Item("Alpha", 1000)); <2> - } + + @Inject + VariantTemplate item; <1> + + @GET + @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN }) + public Rendering item() { + return item.data(new Item("Alpha", 1000)); <2> + } } ---- <1> Inject a variant template with base path derived from the injected field - `src/main/resources/templates/item`. @@ -717,4 +716,4 @@ include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional] == Extension Points -TODO \ No newline at end of file +TODO From 8ece5378807bc5f917b1f3894d1781f8177961ed Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 17:09:24 +0100 Subject: [PATCH 432/602] Fix a minor indentation issue in Gradle tooling documentation --- docs/src/main/asciidoc/gradle-tooling.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index 6260522f28103..045394c4e8a05 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -98,7 +98,7 @@ or, if you use the Gradle Kotlin DSL: ---- tasks.test { systemProperty("quarkus.test.profile", "foo") <1> - } +} ---- <1> The `foo` configuration profile will be used to run the tests. From fa7be0612416991b312bbc34a8ce5b8d4ff00609 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 17:09:38 +0100 Subject: [PATCH 433/602] Review the Logging to Sentry documentation --- docs/src/main/asciidoc/logging-sentry.adoc | 37 +++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/src/main/asciidoc/logging-sentry.adoc b/docs/src/main/asciidoc/logging-sentry.adoc index 5de07cbbe86df..f222b52b315a5 100644 --- a/docs/src/main/asciidoc/logging-sentry.adoc +++ b/docs/src/main/asciidoc/logging-sentry.adoc @@ -3,22 +3,15 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// -= Quarkus - Configuring Sentry Logging += Quarkus - Logging to Sentry include::./attributes.adoc[] -This guide explains sentry logging and how to configure it. - -== Run Time Configuration - -Run time configuration of logging is done through the normal `application.properties` file. - -include::{generated-dir}/config/quarkus-logging-sentry.adoc[opts=optional, leveloffset=+1] +This guide explains how to configure Quarkus to log to Sentry. == Description - -Sentry is a really easy way to be notified of errors happening on you Quarkus application. +Sentry is a really easy way to be notified of errors happening in your Quarkus application. It is a self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. @@ -28,12 +21,12 @@ WARNING: Sentry's Java SDK is open source, but recently sentry.io https://blog.s == Configuration -To start of, you need to get a Sentry DSN either by https://sentry.io/signup/[creating a Sentry account] or https://docs.sentry.io/server/[installing your self-hosted Sentry]. +To start with, you need to get a Sentry DSN either by https://sentry.io/signup/[creating a Sentry account] or https://docs.sentry.io/server/[installing your own self-hosted Sentry]. -In order to configure Sentry logging, the `quarkus-logging-sentry` extension should be employed. Add this extension to your -application POM as the following snippet illustrates. +In order to configure Sentry logging, add the `quarkus-logging-sentry` extension to your +application `pom.xml` as illustrated in the following snippet: -.Modifications to POM file to add the Sentry logging extension +.Add the Sentry logging extension to your pom.xml [source,xml] ---- @@ -51,12 +44,14 @@ application POM as the following snippet illustrates. === “In Application” Stack Frames Sentry differentiates stack frames that are directly related to your application (“in application”) from stack frames that come from other packages such as the standard library, frameworks, or other dependencies. The difference is visible in the Sentry web interface where only the “in application” frames are displayed by default. -You can configure which package prefixes your application uses with the stacktrace.app.packages option, which takes a comma separated list. +You can configure which package prefixes your application uses with the `in-app-packages` option, which takes a comma separated list of packages: + [source, properties] ---- quarkus.log.sentry.in-app-packages=com.mycompany,com.other.name ---- -If you don’t want to use this feature but want to disable the warning, simply set it to "*": + +If you don’t want to use this feature but want to disable the warning, simply set it to `*`: [source, properties] ---- @@ -65,9 +60,9 @@ quarkus.log.sentry.in-app-packages=* == Example -.All errors and warnings occuring in any the packages will be sent to Sentry with DSN `https://abcd@sentry.io/1234` +.All errors and warnings occuring in any of the packages will be sent to Sentry with DSN `https://abcd@sentry.io/1234` [source, properties] -` +---- quarkus.log.sentry=true quarkus.log.sentry.dsn=https://abcd@sentry.io/1234 quarkus.log.sentry.in-app-packages=* @@ -82,3 +77,9 @@ quarkus.log.sentry.level=ERROR quarkus.log.sentry.in-app-packages=org.example ---- +== Configuration Reference + +This extension is configured through the standard `application.properties` file. + +include::{generated-dir}/config/quarkus-logging-sentry.adoc[opts=optional, leveloffset=+1] + From 1f21d2ee210f5d73e8b3231c689631633cb1bc46 Mon Sep 17 00:00:00 2001 From: Alex Soto Date: Mon, 23 Dec 2019 17:52:27 +0100 Subject: [PATCH 434/602] Fixes spaces to be double spaces in all the examples --- docs/src/main/asciidoc/config.adoc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index dc517ade7abfb..11f37bd2c438e 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -620,12 +620,12 @@ Just add the `%profile` wrapped in quotation marks before defining the key-value [source,yaml] ---- "%dev": - quarkus: + quarkus: datasource: - url: jdbc:postgresql://localhost:5432/some-database - driver: org.postgresql.Driver - username: quarkus - password: quarkus + url: jdbc:postgresql://localhost:5432/some-database + driver: org.postgresql.Driver + username: quarkus + password: quarkus ---- === Configuration key conflicts @@ -641,10 +641,10 @@ This is solved by using a null key (normally represented by `~`) for any YAML pr [source,yaml] ---- quarkus: - http: - cors: - ~: true - methods: GET,PUT,POST + http: + cors: + ~: true + methods: GET,PUT,POST ---- In general, null YAML keys are not included in assembly of the configuration property name, allowing them to be used to From 2cea94218ff76181c27e9efe63cce87b37612b41 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 20 Dec 2019 09:45:36 +0530 Subject: [PATCH 435/602] issue-5942 Introduce a deterministic ordering, between extensions, for database schema updates --- .../DataSourceSchemaReadyBuildItem.java | 23 +++++++++ .../io/quarkus/flyway/FlywayProcessor.java | 9 +++- .../orm/deployment/HibernateOrmProcessor.java | 4 +- .../quartz/deployment/QuartzProcessor.java | 4 +- integration-tests/flyway/pom.xml | 4 ++ .../java/io/quarkus/it/flyway/AppEntity.java | 51 +++++++++++++++++++ .../src/main/resources/application.properties | 5 +- .../db/location1/V1.0.1__Quarkus.sql | 2 +- 8 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceSchemaReadyBuildItem.java create mode 100644 integration-tests/flyway/src/main/java/io/quarkus/it/flyway/AppEntity.java diff --git a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceSchemaReadyBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceSchemaReadyBuildItem.java new file mode 100644 index 0000000000000..a90b1ce43e930 --- /dev/null +++ b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceSchemaReadyBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.agroal.deployment; + +import java.util.Collection; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item which can be used to order build processors which need a datasource's + * schema to be ready (which really means that the tables have been created and + * any migration run on them) for processing + */ +public final class DataSourceSchemaReadyBuildItem extends SimpleBuildItem { + + private final Collection datasourceNames; + + public DataSourceSchemaReadyBuildItem(final Collection datasourceNames) { + this.datasourceNames = datasourceNames; + } + + public Collection getDatasourceNames() { + return this.datasourceNames; + } +} diff --git a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java index d8e6f219e6829..ac194451a64c7 100644 --- a/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -26,6 +26,7 @@ import org.jboss.logging.Logger; import io.quarkus.agroal.deployment.DataSourceInitializedBuildItem; +import io.quarkus.agroal.deployment.DataSourceSchemaReadyBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; @@ -103,9 +104,15 @@ void build(BuildProducer additionalBeanProducer, ServiceStartBuildItem configureRuntimeProperties(FlywayRecorder recorder, FlywayRuntimeConfig flywayRuntimeConfig, BeanContainerBuildItem beanContainer, - DataSourceInitializedBuildItem dataSourceInitializedBuildItem) { + DataSourceInitializedBuildItem dataSourceInitializedBuildItem, + BuildProducer schemaReadyBuildItem) { recorder.configureFlywayProperties(flywayRuntimeConfig, beanContainer.getValue()); recorder.doStartActions(flywayRuntimeConfig, beanContainer.getValue()); + // once we are done running the migrations, we produce a build item indicating that the + // schema is "ready" + final Collection dataSourceNames = DataSourceInitializedBuildItem + .dataSourceNamesOf(dataSourceInitializedBuildItem); + schemaReadyBuildItem.produce(new DataSourceSchemaReadyBuildItem(dataSourceNames)); return new ServiceStartBuildItem("flyway"); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index b81646a93b48f..2d38a869cf5da 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -45,6 +45,7 @@ import io.quarkus.agroal.deployment.DataSourceDriverBuildItem; import io.quarkus.agroal.deployment.DataSourceInitializedBuildItem; +import io.quarkus.agroal.deployment.DataSourceSchemaReadyBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; @@ -335,7 +336,8 @@ public void build(HibernateOrmRecorder recorder, public void startPersistenceUnits(HibernateOrmRecorder recorder, BeanContainerBuildItem beanContainer, Optional dataSourceInitialized, JpaEntitiesBuildItem jpaEntities, List nonJpaModels, - List integrationsRuntimeConfigured) throws Exception { + List integrationsRuntimeConfigured, + Optional schemaReadyBuildItem) throws Exception { if (!hasEntities(jpaEntities, nonJpaModels)) { return; } diff --git a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java index 9c8f66702c9cf..4761cf2031d28 100644 --- a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java +++ b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java @@ -23,6 +23,7 @@ import org.quartz.simpl.SimpleThreadPool; import io.quarkus.agroal.deployment.DataSourceDriverBuildItem; +import io.quarkus.agroal.deployment.DataSourceSchemaReadyBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.Capabilities; @@ -167,7 +168,8 @@ public List logCleanup(QuartzBuildTimeConfig config) @Record(RUNTIME_INIT) public void build(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, QuartzRecorder recorder, BeanContainerBuildItem beanContainer, - BuildProducer serviceStart, QuartzJDBCDriverDialectBuildItem driverDialect) { + BuildProducer serviceStart, QuartzJDBCDriverDialectBuildItem driverDialect, + Optional schemaReadyBuildItem) { recorder.initialize(runtimeConfig, buildTimeConfig, beanContainer.getValue(), driverDialect.getDriver()); // Make sure that StartupEvent is fired after the init serviceStart.produce(new ServiceStartBuildItem("quartz")); diff --git a/integration-tests/flyway/pom.xml b/integration-tests/flyway/pom.xml index 6a075b6817098..43ba95c789fcb 100644 --- a/integration-tests/flyway/pom.xml +++ b/integration-tests/flyway/pom.xml @@ -18,6 +18,10 @@ io.quarkus quarkus-flyway
+ + io.quarkus + quarkus-hibernate-orm + io.quarkus quarkus-jdbc-h2 diff --git a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/AppEntity.java b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/AppEntity.java new file mode 100644 index 0000000000000..e0f5de42a5f84 --- /dev/null +++ b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/AppEntity.java @@ -0,0 +1,51 @@ +package io.quarkus.it.flyway; + +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * Entity used within tests + */ +@Entity +@Table(name = "quarkus_table2", schema = "TEST_SCHEMA") +public class AppEntity { + + @Id + private int id; + + private String name; + + public int getId() { + return id; + } + + public void setId(final int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final AppEntity appEntity = (AppEntity) o; + return id == appEntity.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/integration-tests/flyway/src/main/resources/application.properties b/integration-tests/flyway/src/main/resources/application.properties index 2a89b81924006..74452e2b4e17c 100644 --- a/integration-tests/flyway/src/main/resources/application.properties +++ b/integration-tests/flyway/src/main/resources/application.properties @@ -11,4 +11,7 @@ quarkus.flyway.connect-retries=10 quarkus.flyway.schemas=TEST_SCHEMA quarkus.flyway.table=flyway_quarkus_history quarkus.flyway.locations=db/location1,classpath:db/location2 -quarkus.flyway.sql-migration-prefix=V \ No newline at end of file +quarkus.flyway.sql-migration-prefix=V +quarkus.flyway.migrate-at-start=true + +quarkus.hibernate-orm.database.generation=validate \ No newline at end of file diff --git a/integration-tests/flyway/src/main/resources/db/location1/V1.0.1__Quarkus.sql b/integration-tests/flyway/src/main/resources/db/location1/V1.0.1__Quarkus.sql index a8559cd9d5cd9..29f8f69d5abca 100644 --- a/integration-tests/flyway/src/main/resources/db/location1/V1.0.1__Quarkus.sql +++ b/integration-tests/flyway/src/main/resources/db/location1/V1.0.1__Quarkus.sql @@ -4,4 +4,4 @@ CREATE TABLE TEST_SCHEMA.quarkus_table2 name VARCHAR(20) ); INSERT INTO TEST_SCHEMA.quarkus_table2(id, name) -VALUES (1, 'QUARKED'); \ No newline at end of file +VALUES (1, '1.0.1 QUARKED'); \ No newline at end of file From 921e572a3e523b9152932490280dbb79a46d9b63 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 16:29:24 +0100 Subject: [PATCH 436/602] Add missing descriptions to recently added extensions --- extensions/config-yaml/runtime/pom.xml | 3 ++- extensions/logging-gelf/runtime/pom.xml | 1 + extensions/quartz/runtime/pom.xml | 1 + extensions/spring-security/runtime/pom.xml | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/config-yaml/runtime/pom.xml b/extensions/config-yaml/runtime/pom.xml index 0be9a852ee550..4b6fcb3e2acbe 100644 --- a/extensions/config-yaml/runtime/pom.xml +++ b/extensions/config-yaml/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-config-yaml Quarkus - Configuration - YAML - Runtime + Use YAML to configure your Quarkus application @@ -59,4 +60,4 @@
- \ No newline at end of file + diff --git a/extensions/logging-gelf/runtime/pom.xml b/extensions/logging-gelf/runtime/pom.xml index d6a5663e7c803..c275db7c06231 100644 --- a/extensions/logging-gelf/runtime/pom.xml +++ b/extensions/logging-gelf/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-logging-gelf Quarkus - Logging - GELF - Runtime + Log using the Graylog Extended Log Format and centralize your logs in ELK or EFK diff --git a/extensions/quartz/runtime/pom.xml b/extensions/quartz/runtime/pom.xml index 9ad81a1c8642f..b558869c18f4c 100644 --- a/extensions/quartz/runtime/pom.xml +++ b/extensions/quartz/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-quartz Quarkus - Quartz - Runtime + Schedule clustered tasks with Quartz io.quarkus diff --git a/extensions/spring-security/runtime/pom.xml b/extensions/spring-security/runtime/pom.xml index 863b9467381b6..1d6295649bd72 100644 --- a/extensions/spring-security/runtime/pom.xml +++ b/extensions/spring-security/runtime/pom.xml @@ -12,6 +12,7 @@ quarkus-spring-security Quarkus - Spring Security - Runtime + Secure your application with Spring Security annotations From 8ea6ae2d248054fe12fd37087770dd10def80504 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 23 Dec 2019 16:30:11 +0100 Subject: [PATCH 437/602] Tweak the Sentry extension description --- extensions/logging-sentry/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/logging-sentry/runtime/pom.xml b/extensions/logging-sentry/runtime/pom.xml index 81072784e1424..5decc83d22da5 100644 --- a/extensions/logging-sentry/runtime/pom.xml +++ b/extensions/logging-sentry/runtime/pom.xml @@ -12,7 +12,7 @@ quarkus-logging-sentry Quarkus - Logging - Sentry - Runtime - Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. + Use Sentry, a self-hosted or cloud-based error monitoring solution From daf961dfaab72530c52bf8059595682b5082605e Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 23 Dec 2019 15:43:17 +0200 Subject: [PATCH 438/602] Ensure that Kotlin Data classes with default values work in native with JAX-RS Fixes: #3954 --- ...FinalFieldsWritablePredicateBuildItem.java | 26 +++++++++ .../steps/ReflectiveHierarchyStep.java | 56 ++++++++++++++----- ...IsDataClassWithDefaultValuesPredicate.java | 38 +++++++++++++ .../kotlin/deployment/KotlinProcessor.java | 10 ++++ 4 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassFinalFieldsWritablePredicateBuildItem.java create mode 100644 extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/IsDataClassWithDefaultValuesPredicate.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassFinalFieldsWritablePredicateBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassFinalFieldsWritablePredicateBuildItem.java new file mode 100644 index 0000000000000..48140a71f201f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassFinalFieldsWritablePredicateBuildItem.java @@ -0,0 +1,26 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import java.util.function.Predicate; + +import org.jboss.jandex.ClassInfo; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Used by {@code io.quarkus.deployment.steps.ReflectiveHierarchyStep} to determine whether or + * not the final fields of the class should be writable (which they aren't by default) + * + * If any one of the predicates returns true for a class, then ReflectiveHierarchyStep uses that true value + */ +public final class ReflectiveClassFinalFieldsWritablePredicateBuildItem extends MultiBuildItem { + + private final Predicate predicate; + + public ReflectiveClassFinalFieldsWritablePredicateBuildItem(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java index eac766828b33e..f8c1cc48e64a7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectiveHierarchyStep.java @@ -28,6 +28,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassFinalFieldsWritablePredicateBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; @@ -47,12 +48,25 @@ public class ReflectiveHierarchyStep { @Inject List ignored; + @Inject + List finalFieldsWritablePredicates; + @BuildStep public void build() throws Exception { Set processedReflectiveHierarchies = new HashSet<>(); Set unindexedClasses = new TreeSet<>(); + + Predicate finalFieldsWritable = (c) -> false; // no need to make final fields writable by default + if (!finalFieldsWritablePredicates.isEmpty()) { + // create a predicate that returns true if any of the predicates says that final fields need to be writable + finalFieldsWritable = finalFieldsWritablePredicates + .stream() + .map(ReflectiveClassFinalFieldsWritablePredicateBuildItem::getPredicate) + .reduce(c -> false, Predicate::or); + } + for (ReflectiveHierarchyBuildItem i : hierarchy) { - addReflectiveHierarchy(i, i.getType(), processedReflectiveHierarchies, unindexedClasses); + addReflectiveHierarchy(i, i.getType(), processedReflectiveHierarchies, unindexedClasses, finalFieldsWritable); } for (ReflectiveHierarchyIgnoreWarningBuildItem i : ignored) { unindexedClasses.remove(i.getDotName()); @@ -69,7 +83,8 @@ public void build() throws Exception { } private void addReflectiveHierarchy(ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, Type type, - Set processedReflectiveHierarchies, Set unindexedClasses) { + Set processedReflectiveHierarchies, Set unindexedClasses, + Predicate finalFieldsWritable) { if (type instanceof VoidType || type instanceof PrimitiveType || type instanceof UnresolvedTypeVariable) { @@ -79,47 +94,55 @@ private void addReflectiveHierarchy(ReflectiveHierarchyBuildItem reflectiveHiera return; } - addClassTypeHierarchy(reflectiveHierarchyBuildItem, type.name(), processedReflectiveHierarchies, unindexedClasses); + addClassTypeHierarchy(reflectiveHierarchyBuildItem, type.name(), processedReflectiveHierarchies, unindexedClasses, + finalFieldsWritable); for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) { addClassTypeHierarchy(reflectiveHierarchyBuildItem, subclass.name(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) { addClassTypeHierarchy(reflectiveHierarchyBuildItem, subclass.name(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } } else if (type instanceof ArrayType) { addReflectiveHierarchy(reflectiveHierarchyBuildItem, type.asArrayType().component(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; if (!reflectiveHierarchyBuildItem.getIgnorePredicate().test(parameterizedType.name())) { addClassTypeHierarchy(reflectiveHierarchyBuildItem, parameterizedType.name(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } for (Type typeArgument : parameterizedType.arguments()) { addReflectiveHierarchy(reflectiveHierarchyBuildItem, typeArgument, processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } } } private void addClassTypeHierarchy(ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, DotName name, Set processedReflectiveHierarchies, - Set unindexedClasses) { + Set unindexedClasses, Predicate finalFieldsWritable) { if (skipClass(name, reflectiveHierarchyBuildItem.getIgnorePredicate(), processedReflectiveHierarchies)) { return; } processedReflectiveHierarchies.add(name); - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, name.toString())); + ClassInfo info = (reflectiveHierarchyBuildItem.getIndex() != null ? reflectiveHierarchyBuildItem.getIndex() : combinedIndexBuildItem.getIndex()).getClassByName(name); + reflectiveClass.produce( + ReflectiveClassBuildItem + .builder(name.toString()) + .methods(true) + .fields(true) + .finalFieldsWritable(doFinalFieldsNeedToBeWritable(info, finalFieldsWritable)) + .build()); if (info == null) { unindexedClasses.add(name); } else { addClassTypeHierarchy(reflectiveHierarchyBuildItem, info.superName(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); for (FieldInfo field : info.fields()) { if (Modifier.isStatic(field.flags()) || field.name().startsWith("this$") || field.name().startsWith("val$")) { // skip the static fields (especially loggers) @@ -127,7 +150,7 @@ private void addClassTypeHierarchy(ReflectiveHierarchyBuildItem reflectiveHierar continue; } addReflectiveHierarchy(reflectiveHierarchyBuildItem, field.type(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } for (MethodInfo method : info.methods()) { if (method.parameters().size() > 0 || Modifier.isStatic(method.flags()) @@ -136,7 +159,7 @@ private void addClassTypeHierarchy(ReflectiveHierarchyBuildItem reflectiveHierar continue; } addReflectiveHierarchy(reflectiveHierarchyBuildItem, method.returnType(), processedReflectiveHierarchies, - unindexedClasses); + unindexedClasses, finalFieldsWritable); } } } @@ -144,4 +167,11 @@ private void addClassTypeHierarchy(ReflectiveHierarchyBuildItem reflectiveHierar private boolean skipClass(DotName name, Predicate ignorePredicate, Set processedReflectiveHierarchies) { return ignorePredicate.test(name) || processedReflectiveHierarchies.contains(name); } + + private boolean doFinalFieldsNeedToBeWritable(ClassInfo classInfo, Predicate finalFieldsWritable) { + if (classInfo == null) { + return false; + } + return finalFieldsWritable.test(classInfo); + } } diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/IsDataClassWithDefaultValuesPredicate.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/IsDataClassWithDefaultValuesPredicate.java new file mode 100644 index 0000000000000..c7d3f55d3aa7c --- /dev/null +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/IsDataClassWithDefaultValuesPredicate.java @@ -0,0 +1,38 @@ +package io.quarkus.kotlin.deployment; + +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.function.Predicate; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; + +/** + * Tests whether a class is a data class (based on this answer: + * https://discuss.kotlinlang.org/t/detect-data-class-in-runtime/6155/2) + * and whether the class has default values for fields (default values leads to having multiple constructors in bytecode) + */ +public class IsDataClassWithDefaultValuesPredicate implements Predicate { + + @Override + public boolean test(ClassInfo classInfo) { + int ctorCount = 0; + boolean hasCopyMethod = false; + boolean hasStaticCopyMethod = false; + boolean hasComponent1Method = false; + List methods = classInfo.methods(); + for (MethodInfo method : methods) { + String methodName = method.name(); + if ("".equals(methodName)) { + ctorCount++; + } else if ("component1".equals(methodName) && Modifier.isFinal(method.flags())) { + hasComponent1Method = true; + } else if ("copy".equals(methodName) && Modifier.isFinal(method.flags())) { + hasCopyMethod = true; + } else if ("copy$default".equals(methodName) && Modifier.isStatic(method.flags())) { + hasStaticCopyMethod = true; + } + } + return ctorCount > 1 && hasComponent1Method && hasCopyMethod && hasStaticCopyMethod; + } +} diff --git a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java index e26857051a35d..b50d11b76f878 100644 --- a/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java +++ b/extensions/kotlin/deployment/src/main/java/io/quarkus/kotlin/deployment/KotlinProcessor.java @@ -3,6 +3,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassFinalFieldsWritablePredicateBuildItem; import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem; public class KotlinProcessor { @@ -27,4 +28,13 @@ void registerKotlinJacksonModule(BuildProducer } catch (Exception ignored) { } } + + /** + * Kotlin data classes that have multiple constructors need to have their final fields writable, + * otherwise creating a instance of them with default values, fails in native mode + */ + @BuildStep + ReflectiveClassFinalFieldsWritablePredicateBuildItem dataClassPredicate() { + return new ReflectiveClassFinalFieldsWritablePredicateBuildItem(new IsDataClassWithDefaultValuesPredicate()); + } } From 92b88ad5dca912d3fe824bd2e1d98db4b036be4a Mon Sep 17 00:00:00 2001 From: Gytis Trikleris Date: Wed, 18 Dec 2019 17:07:44 +0200 Subject: [PATCH 439/602] Spring Boot properties extension --- bom/deployment/pom.xml | 5 + bom/runtime/pom.xml | 23 ++++ ci-templates/stages.yml | 1 + .../builditem/FeatureBuildItem.java | 1 + .../ConfigPropertiesBuildStep.java | 67 +++-------- .../ConfigPropertiesMetadataBuildItem.java | 68 +++++++++++ extensions/pom.xml | 1 + .../spring-boot-properties/deployment/pom.xml | 42 +++++++ .../ConfigurationPropertiesProcessor.java | 64 ++++++++++ extensions/spring-boot-properties/pom.xml | 21 ++++ .../spring-boot-properties/runtime/pom.xml | 31 +++++ .../resources/META-INF/quarkus-extension.yaml | 9 ++ integration-tests/pom.xml | 1 + .../spring-boot-properties/pom.xml | 113 ++++++++++++++++++ .../quarkus/it/spring/boot/AnotherClass.java | 14 +++ .../it/spring/boot/BeanProperties.java | 37 ++++++ .../spring/boot/BeanPropertiesResource.java | 24 ++++ .../it/spring/boot/ClassProperties.java | 27 +++++ .../spring/boot/ClassPropertiesResource.java | 24 ++++ .../it/spring/boot/DefaultProperties.java | 17 +++ .../boot/DefaultPropertiesResource.java | 18 +++ .../it/spring/boot/InterfaceProperties.java | 9 ++ .../boot/InterfacePropertiesResource.java | 18 +++ .../it/spring/boot/SampleApplication.java | 11 ++ .../src/main/resources/application.properties | 5 + .../it/spring/boot/BeanPropertiesIT.java | 7 ++ .../it/spring/boot/BeanPropertiesTest.java | 27 +++++ .../it/spring/boot/ClassPropertiesIT.java | 7 ++ .../it/spring/boot/ClassPropertiesTest.java | 27 +++++ .../it/spring/boot/DefaultPropertiesIT.java | 7 ++ .../it/spring/boot/DefaultPropertiesTest.java | 20 ++++ .../it/spring/boot/InterfacePropertiesIT.java | 7 ++ .../spring/boot/InterfacePropertiesTest.java | 20 ++++ 33 files changed, 723 insertions(+), 50 deletions(-) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesMetadataBuildItem.java create mode 100644 extensions/spring-boot-properties/deployment/pom.xml create mode 100644 extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesProcessor.java create mode 100644 extensions/spring-boot-properties/pom.xml create mode 100644 extensions/spring-boot-properties/runtime/pom.xml create mode 100644 extensions/spring-boot-properties/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 integration-tests/spring-boot-properties/pom.xml create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/AnotherClass.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanProperties.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanPropertiesResource.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassProperties.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassPropertiesResource.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultProperties.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultPropertiesResource.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfaceProperties.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfacePropertiesResource.java create mode 100644 integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/SampleApplication.java create mode 100644 integration-tests/spring-boot-properties/src/main/resources/application.properties create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesIT.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesTest.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesIT.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesTest.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesIT.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesTest.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesIT.java create mode 100644 integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesTest.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index 756c5db10608d..19e8672dff034 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -501,6 +501,11 @@ quarkus-spring-data-deployment ${project.version} + + io.quarkus + quarkus-spring-boot-properties-deployment + ${project.version} + io.quarkus quarkus-jgit-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index e98b299c5671e..61623bf604429 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -167,6 +167,7 @@ 5.1.8.RELEASE 2.1.9.RELEASE 5.2.0.RELEASE + 2.1.10.RELEASE 2.4.4.Final 3.0.0 5.3.1 @@ -640,6 +641,11 @@ quarkus-spring-data-jpa ${project.version} + + io.quarkus + quarkus-spring-boot-properties + ${project.version} + io.quarkus quarkus-swagger-ui @@ -2556,6 +2562,23 @@ + + + org.springframework.boot + spring-boot + ${spring-boot.version} + + + org.springframework + spring-core + + + org.springframework + spring-context + + + + org.keycloak diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index f76c9eb0bf4b5..af510eea19418 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -363,6 +363,7 @@ stages: - spring-di - spring-web - spring-data-jpa + - spring-boot-properties name: spring - template: native-build-steps.yaml diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 602826188c27e..c4c02efad123e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -79,6 +79,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String SPRING_WEB = "spring-web"; public static final String SPRING_DATA_JPA = "spring-data-jpa"; public static final String SPRING_SECURITY = "spring-security"; + public static final String SPRING_BOOT_PROPERTIES = "spring-boot-properties"; public static final String SWAGGER_UI = "swagger-ui"; public static final String TIKA = "tika"; public static final String UNDERTOW_WEBSOCKETS = "undertow-websockets"; diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesBuildStep.java index 547f39fe2640f..174183df328dd 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesBuildStep.java @@ -1,24 +1,16 @@ package io.quarkus.arc.deployment.configproperties; -import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; -import static io.quarkus.runtime.util.StringUtil.join; -import static io.quarkus.runtime.util.StringUtil.lowerCase; -import static io.quarkus.runtime.util.StringUtil.withoutSuffix; - import java.lang.reflect.Modifier; -import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.inject.Singleton; import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; -import io.quarkus.arc.config.ConfigProperties; import io.quarkus.arc.deployment.ConfigPropertyBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; @@ -35,17 +27,24 @@ public class ConfigPropertiesBuildStep { + @BuildStep + void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, + BuildProducer configPropertiesMetadataProducer) { + for (AnnotationInstance annotation : combinedIndex.getIndex().getAnnotations(DotNames.CONFIG_PROPERTIES)) { + configPropertiesMetadataProducer.produce(new ConfigPropertiesMetadataBuildItem(annotation)); + } + } + @BuildStep void setup(CombinedIndexBuildItem combinedIndex, ApplicationIndexBuildItem applicationIndex, + List configPropertiesMetadataList, BuildProducer generatedClasses, BuildProducer generatedBeans, BuildProducer defaultConfigValues, BuildProducer configProperties, DeploymentClassLoaderBuildItem deploymentClassLoader) { - IndexView index = combinedIndex.getIndex(); - Collection instances = index.getAnnotations(DotNames.CONFIG_PROPERTIES); - if (instances.isEmpty()) { + if (configPropertiesMetadataList.isEmpty()) { return; } @@ -62,11 +61,10 @@ void setup(CombinedIndexBuildItem combinedIndex, .build(); producerClassCreator.addAnnotation(Singleton.class); - Set configClassesThatNeedValidation = new HashSet<>(instances.size()); - for (AnnotationInstance configPropertiesInstance : instances) { - ClassInfo classInfo = configPropertiesInstance.target().asClass(); + Set configClassesThatNeedValidation = new HashSet<>(configPropertiesMetadataList.size()); + for (ConfigPropertiesMetadataBuildItem configPropertiesMetadata : configPropertiesMetadataList) { + ClassInfo classInfo = configPropertiesMetadata.getClassInfo(); - String prefixStr = determinePrefix(configPropertiesInstance); if (Modifier.isInterface(classInfo.flags())) { /* * In this case we need to generate an implementation of the interface that for each interface method @@ -75,7 +73,7 @@ void setup(CombinedIndexBuildItem combinedIndex, */ String generatedClassName = InterfaceConfigPropertiesUtil.generateImplementationForInterfaceConfigProperties( - classInfo, nonBeansClassOutput, index, prefixStr, + classInfo, nonBeansClassOutput, combinedIndex.getIndex(), configPropertiesMetadata.getPrefix(), defaultConfigValues, configProperties); InterfaceConfigPropertiesUtil.addProducerMethodForInterfaceConfigProperties(producerClassCreator, classInfo.name(), generatedClassName); @@ -86,8 +84,8 @@ void setup(CombinedIndexBuildItem combinedIndex, * and call setters for value obtained from MP Config */ boolean needsValidation = ClassConfigPropertiesUtil.addProducerMethodForClassConfigProperties( - deploymentClassLoader.getClassLoader(), classInfo, producerClassCreator, prefixStr, - applicationIndex.getIndex(), configProperties); + deploymentClassLoader.getClassLoader(), classInfo, producerClassCreator, + configPropertiesMetadata.getPrefix(), applicationIndex.getIndex(), configProperties); if (needsValidation) { configClassesThatNeedValidation.add(classInfo.name()); } @@ -101,35 +99,4 @@ void setup(CombinedIndexBuildItem combinedIndex, configClassesThatNeedValidation); } } - - /** - * Use the annotation value - */ - private String determinePrefix(AnnotationInstance configPropertiesInstance) { - String fromAnnotation = getPrefixFromAnnotation(configPropertiesInstance); - if (fromAnnotation != null) { - return fromAnnotation; - } - return getPrefixFromClassName(configPropertiesInstance.target().asClass().name()); - } - - private String getPrefixFromAnnotation(AnnotationInstance configPropertiesInstance) { - AnnotationValue annotationValue = configPropertiesInstance.value("prefix"); - if (annotationValue == null) { - return null; - } - String value = annotationValue.asString(); - if (ConfigProperties.UNSET_PREFIX.equals(value) || value.isEmpty()) { - return null; - } - return value; - } - - private String getPrefixFromClassName(DotName className) { - String simpleName = className.isInner() ? className.local() : className.withoutPackagePrefix(); - return join("-", - withoutSuffix(lowerCase(camelHumpsIterator(simpleName)), "config", "configuration", - "properties", "props")); - } - } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesMetadataBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesMetadataBuildItem.java new file mode 100644 index 0000000000000..cb7b8497968e7 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/configproperties/ConfigPropertiesMetadataBuildItem.java @@ -0,0 +1,68 @@ +package io.quarkus.arc.deployment.configproperties; + +import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; +import static io.quarkus.runtime.util.StringUtil.join; +import static io.quarkus.runtime.util.StringUtil.lowerCase; +import static io.quarkus.runtime.util.StringUtil.withoutSuffix; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.config.ConfigProperties; +import io.quarkus.builder.item.MultiBuildItem; + +public final class ConfigPropertiesMetadataBuildItem extends MultiBuildItem { + + private static final DotName CONFIG_PROPERTIES_ANNOTATION = DotName.createSimple(ConfigProperties.class.getName()); + + private final ClassInfo classInfo; + + private final String prefix; + + public ConfigPropertiesMetadataBuildItem(AnnotationInstance annotation) { + if (!CONFIG_PROPERTIES_ANNOTATION.equals(annotation.name())) { + throw new IllegalArgumentException(annotation + " is not an instance of " + ConfigProperties.class.getSimpleName()); + } + + this.classInfo = annotation.target().asClass(); + this.prefix = extractPrefix(annotation); + } + + public ConfigPropertiesMetadataBuildItem(ClassInfo classInfo, String prefix) { + this.classInfo = classInfo; + this.prefix = sanitisePrefix(prefix); + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public String getPrefix() { + return prefix; + } + + private String extractPrefix(AnnotationInstance annotationInstance) { + AnnotationValue value = annotationInstance.value("prefix"); + return sanitisePrefix(value == null ? null : value.asString()); + } + + private String sanitisePrefix(String prefix) { + if (isPrefixUnset(prefix)) { + return getPrefixFromClassName(classInfo.name()); + } + return prefix; + } + + private boolean isPrefixUnset(String prefix) { + return prefix == null || "".equals(prefix.trim()) || ConfigProperties.UNSET_PREFIX.equals(prefix.trim()); + } + + private String getPrefixFromClassName(DotName className) { + String simpleName = className.isInner() ? className.local() : className.withoutPackagePrefix(); + return join("-", + withoutSuffix(lowerCase(camelHumpsIterator(simpleName)), "config", "configuration", + "properties", "props")); + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index 02e982ef782c5..3d6ec81c0e154 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -93,6 +93,7 @@ spring-web spring-data-jpa spring-security + spring-boot-properties security diff --git a/extensions/spring-boot-properties/deployment/pom.xml b/extensions/spring-boot-properties/deployment/pom.xml new file mode 100644 index 0000000000000..60e3b3c0da4c9 --- /dev/null +++ b/extensions/spring-boot-properties/deployment/pom.xml @@ -0,0 +1,42 @@ + + + + quarkus-spring-boot-properties-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-spring-boot-properties-deployment + Quarkus - Spring Boot - Properties - Deployment + + + + io.quarkus + quarkus-spring-boot-properties + + + io.quarkus + quarkus-arc-deployment + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + \ No newline at end of file diff --git a/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesProcessor.java b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesProcessor.java new file mode 100644 index 0000000000000..c638db20bf2b6 --- /dev/null +++ b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesProcessor.java @@ -0,0 +1,64 @@ +package io.quarkus.spring.boot.properties.deployment; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import io.quarkus.arc.deployment.configproperties.ConfigPropertiesMetadataBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +public class ConfigurationPropertiesProcessor { + + private static final DotName CONFIGURATION_PROPERTIES_ANNOTATION = DotName + .createSimple(ConfigurationProperties.class.getName()); + + @BuildStep + public FeatureBuildItem registerFeature() { + return new FeatureBuildItem(FeatureBuildItem.SPRING_BOOT_PROPERTIES); + } + + @BuildStep + public void produceConfigPropertiesMetadata(CombinedIndexBuildItem combinedIndex, + BuildProducer configPropertiesMetadataProducer) { + combinedIndex.getIndex() + .getAnnotations(CONFIGURATION_PROPERTIES_ANNOTATION) + .stream() + .map(annotation -> createConfigPropertiesMetadata(annotation, combinedIndex.getIndex())) + .forEach(configPropertiesMetadataProducer::produce); + } + + private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadata(AnnotationInstance annotation, IndexView index) { + switch (annotation.target().kind()) { + case CLASS: + return createConfigPropertiesMetadataFromClass(annotation); + case METHOD: + return createConfigPropertiesMetadataFromMethod(annotation, index); + default: + throw new IllegalArgumentException("Unsupported annotation target kind " + annotation.target().kind().name()); + } + } + + private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadataFromClass(AnnotationInstance annotation) { + return new ConfigPropertiesMetadataBuildItem(annotation.target().asClass(), getPrefix(annotation)); + } + + private ConfigPropertiesMetadataBuildItem createConfigPropertiesMetadataFromMethod(AnnotationInstance annotation, + IndexView index) { + return new ConfigPropertiesMetadataBuildItem(index.getClassByName(annotation.target().asMethod().returnType().name()), + getPrefix(annotation)); + } + + private String getPrefix(AnnotationInstance annotation) { + if (annotation.value() != null) { + return annotation.value().asString(); + } else if (annotation.value("prefix") != null) { + return annotation.value("prefix").asString(); + } + + return null; + } +} diff --git a/extensions/spring-boot-properties/pom.xml b/extensions/spring-boot-properties/pom.xml new file mode 100644 index 0000000000000..474cea7cfeb4d --- /dev/null +++ b/extensions/spring-boot-properties/pom.xml @@ -0,0 +1,21 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-spring-boot-properties-parent + Quarkus - Spring Boot - Properties + pom + + + deployment + runtime + + \ No newline at end of file diff --git a/extensions/spring-boot-properties/runtime/pom.xml b/extensions/spring-boot-properties/runtime/pom.xml new file mode 100644 index 0000000000000..77fc157559514 --- /dev/null +++ b/extensions/spring-boot-properties/runtime/pom.xml @@ -0,0 +1,31 @@ + + + + quarkus-spring-boot-properties-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-spring-boot-properties + Quarkus - Spring Boot - Properties - Runtime + Use Spring Boot properties annotations to configure your application + + + + org.springframework.boot + spring-boot + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + \ No newline at end of file diff --git a/extensions/spring-boot-properties/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/spring-boot-properties/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..71f0d04ddcee2 --- /dev/null +++ b/extensions/spring-boot-properties/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +name: "Quarkus Extension for Spring Boot properties" +metadata: + keywords: + - "spring-boot" + - "properties" + categories: + - "compatibility" + status: "preview" diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 07a44e6242e93..1584b414ca0f7 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -47,6 +47,7 @@ spring-di spring-web spring-data-jpa + spring-boot-properties infinispan-cache-jpa elytron-security elytron-security-oauth2 diff --git a/integration-tests/spring-boot-properties/pom.xml b/integration-tests/spring-boot-properties/pom.xml new file mode 100644 index 0000000000000..a90bcbac960d0 --- /dev/null +++ b/integration-tests/spring-boot-properties/pom.xml @@ -0,0 +1,113 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-spring-boot-properties + Quarkus - Integration Tests - Spring Boot properties + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-spring-boot-properties + + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + maven-compiler-plugin + + true + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/AnotherClass.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/AnotherClass.java new file mode 100644 index 0000000000000..fa865138d5e2d --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/AnotherClass.java @@ -0,0 +1,14 @@ +package io.quarkus.it.spring.boot; + +public final class AnotherClass { + + private boolean value; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanProperties.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanProperties.java new file mode 100644 index 0000000000000..b74f7fe05475f --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanProperties.java @@ -0,0 +1,37 @@ +package io.quarkus.it.spring.boot; + +public final class BeanProperties { + + private int value; + + private InnerClass innerClass; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public InnerClass getInnerClass() { + return innerClass; + } + + public void setInnerClass(InnerClass innerClass) { + this.innerClass = innerClass; + } + + public static class InnerClass { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanPropertiesResource.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanPropertiesResource.java new file mode 100644 index 0000000000000..2a07e6a7bf647 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/BeanPropertiesResource.java @@ -0,0 +1,24 @@ +package io.quarkus.it.spring.boot; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/bean") +public class BeanPropertiesResource { + + @Inject + BeanProperties properties; + + @Path("/value") + @GET + public int getValue() { + return properties.getValue(); + } + + @Path("/innerClass/value") + @GET + public String getInnerClassValue() { + return properties.getInnerClass().getValue(); + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassProperties.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassProperties.java new file mode 100644 index 0000000000000..2ebf71240ef5e --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassProperties.java @@ -0,0 +1,27 @@ +package io.quarkus.it.spring.boot; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("cl") +public final class ClassProperties { + + private String value; + + private AnotherClass anotherClass; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public AnotherClass getAnotherClass() { + return anotherClass; + } + + public void setAnotherClass(AnotherClass anotherClass) { + this.anotherClass = anotherClass; + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassPropertiesResource.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassPropertiesResource.java new file mode 100644 index 0000000000000..2cf1a0b143609 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/ClassPropertiesResource.java @@ -0,0 +1,24 @@ +package io.quarkus.it.spring.boot; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/class") +public class ClassPropertiesResource { + + @Inject + ClassProperties properties; + + @Path("/value") + @GET + public String getValue() { + return properties.getValue(); + } + + @Path("/anotherClass/value") + @GET + public boolean isAnotherClassValue() { + return properties.getAnotherClass().isValue(); + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultProperties.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultProperties.java new file mode 100644 index 0000000000000..10e93421b2ccc --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultProperties.java @@ -0,0 +1,17 @@ +package io.quarkus.it.spring.boot; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties +public class DefaultProperties { + + private String value = "default-value"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultPropertiesResource.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultPropertiesResource.java new file mode 100644 index 0000000000000..8e53fab28e530 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/DefaultPropertiesResource.java @@ -0,0 +1,18 @@ +package io.quarkus.it.spring.boot; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/default") +public class DefaultPropertiesResource { + + @Inject + DefaultProperties properties; + + @Path("/value") + @GET + public String getDefaultValue() { + return properties.getValue(); + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfaceProperties.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfaceProperties.java new file mode 100644 index 0000000000000..507d1df8a3505 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfaceProperties.java @@ -0,0 +1,9 @@ +package io.quarkus.it.spring.boot; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties +public interface InterfaceProperties { + + String getValue(); +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfacePropertiesResource.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfacePropertiesResource.java new file mode 100644 index 0000000000000..5f577c55a75cc --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/InterfacePropertiesResource.java @@ -0,0 +1,18 @@ +package io.quarkus.it.spring.boot; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/interface") +public class InterfacePropertiesResource { + + @Inject + InterfaceProperties properties; + + @Path("/value") + @GET + public String getValue() { + return properties.getValue(); + } +} diff --git a/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/SampleApplication.java b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/SampleApplication.java new file mode 100644 index 0000000000000..f9c782ef96271 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/java/io/quarkus/it/spring/boot/SampleApplication.java @@ -0,0 +1,11 @@ +package io.quarkus.it.spring.boot; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +public class SampleApplication { + + @ConfigurationProperties + public BeanProperties beanProperties() { + return new BeanProperties(); + } +} diff --git a/integration-tests/spring-boot-properties/src/main/resources/application.properties b/integration-tests/spring-boot-properties/src/main/resources/application.properties new file mode 100644 index 0000000000000..dd2729e5c592f --- /dev/null +++ b/integration-tests/spring-boot-properties/src/main/resources/application.properties @@ -0,0 +1,5 @@ +cl.value=class-value +cl.anotherClass.value=true +bean.value=1 +bean.innerClass.value=inner-class-value +interface.value=interface-value \ No newline at end of file diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesIT.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesIT.java new file mode 100644 index 0000000000000..460058bd199bb --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.spring.boot; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class BeanPropertiesIT extends BeanPropertiesTest { +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesTest.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesTest.java new file mode 100644 index 0000000000000..d5c8cab275b0e --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/BeanPropertiesTest.java @@ -0,0 +1,27 @@ +package io.quarkus.it.spring.boot; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class BeanPropertiesTest { + + @Test + void shouldHaveValue() { + when().get("/bean/value") + .then() + .body(is(equalTo("1"))); + } + + @Test + void shouldHaveInnerClassValue() { + when().get("/bean/innerClass/value") + .then() + .body(is(equalTo("inner-class-value"))); + } +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesIT.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesIT.java new file mode 100644 index 0000000000000..ba0840c3b360c --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.spring.boot; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class ClassPropertiesIT extends ClassPropertiesTest { +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesTest.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesTest.java new file mode 100644 index 0000000000000..b3418599c71b1 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/ClassPropertiesTest.java @@ -0,0 +1,27 @@ +package io.quarkus.it.spring.boot; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class ClassPropertiesTest { + + @Test + void shouldHaveValue() { + when().get("/class/value") + .then() + .body(is(equalTo("class-value"))); + } + + @Test + void shouldHaveAnotherClassValue() { + when().get("/class/anotherClass/value") + .then() + .body(is(equalTo("true"))); + } +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesIT.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesIT.java new file mode 100644 index 0000000000000..33f3de2948a58 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.spring.boot; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class DefaultPropertiesIT extends DefaultPropertiesTest { +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesTest.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesTest.java new file mode 100644 index 0000000000000..65b9b5173adf3 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/DefaultPropertiesTest.java @@ -0,0 +1,20 @@ +package io.quarkus.it.spring.boot; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class DefaultPropertiesTest { + + @Test + void shouldGetDefaultValue() { + when().get("/default/value") + .then() + .body(is(equalTo("default-value"))); + } +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesIT.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesIT.java new file mode 100644 index 0000000000000..9929d8e7d14a8 --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.spring.boot; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class InterfacePropertiesIT extends InterfacePropertiesTest { +} diff --git a/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesTest.java b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesTest.java new file mode 100644 index 0000000000000..3eb26bf148faf --- /dev/null +++ b/integration-tests/spring-boot-properties/src/test/java/io/quarkus/it/spring/boot/InterfacePropertiesTest.java @@ -0,0 +1,20 @@ +package io.quarkus.it.spring.boot; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class InterfacePropertiesTest { + + @Test + void shouldHaveInt() { + when().get("/interface/value") + .then() + .body(is(equalTo("interface-value"))); + } +} From 1f2ca2ef93a9d543de7d9014b0ae892cae9c6936 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 19 Dec 2019 16:44:35 +0100 Subject: [PATCH 440/602] Reactive routes - detect unordered conflicting routes - resolves #6165 --- .../web/deployment/VertxWebProcessor.java | 175 ++++++++++++++---- .../vertx/web/runtime/RouteMatcher.java | 74 ++++++++ .../vertx/web/runtime/VertxWebRecorder.java | 37 ++-- 3 files changed, 231 insertions(+), 55 deletions(-) create mode 100644 extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteMatcher.java diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index dc831effa5d67..27aaefa436530 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -1,9 +1,18 @@ package io.quarkus.vertx.web.deployment; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; import java.util.function.Function; import javax.inject.Singleton; @@ -12,6 +21,7 @@ import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.logging.Logger; @@ -21,6 +31,7 @@ import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassAnnotationExclusion; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; @@ -36,11 +47,9 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.AnnotationProxyBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.deployment.recording.AnnotationProxyProvider.AnnotationProxyBuilder; import io.quarkus.deployment.util.HashUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; @@ -56,9 +65,11 @@ import io.quarkus.vertx.web.RouteFilter; import io.quarkus.vertx.web.RoutingExchange; import io.quarkus.vertx.web.runtime.RouteHandler; +import io.quarkus.vertx.web.runtime.RouteMatcher; import io.quarkus.vertx.web.runtime.RoutingExchangeImpl; import io.quarkus.vertx.web.runtime.VertxWebRecorder; import io.vertx.core.Handler; +import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @@ -82,7 +93,9 @@ class VertxWebProcessor { private static final String VALUE_REGEX = "regex"; private static final String VALUE_PRODUCES = "produces"; private static final String VALUE_CONSUMES = "consumes"; - private static final String DASH = "/"; + private static final String VALUE_METHODS = "methods"; + private static final String VALUE_ORDER = "order"; + private static final String SLASH = "/"; @BuildStep FeatureBuildItem feature() { @@ -161,14 +174,16 @@ void addAdditionalRoutes( List routeHandlerBusinessMethods, List routeFilterBusinessMethods, BuildProducer generatedClass, - AnnotationProxyBuildItem annotationProxy, BuildProducer reflectiveClasses, io.quarkus.vertx.http.deployment.BodyHandlerBuildItem bodyHandler, BuildProducer routeProducer, BuildProducer filterProducer, - List bodyHandlerRequired) throws IOException { + List bodyHandlerRequired, + BeanArchiveIndexBuildItem beanArchive) throws IOException { ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); + IndexView index = beanArchive.getIndex(); + Map matchers = new HashMap<>(); for (AnnotatedRouteHandlerBuildItem businessMethod : routeHandlerBusinessMethods) { @@ -178,8 +193,8 @@ void addAdditionalRoutes( AnnotationInstance routeBaseAnnotation = businessMethod.getRouteBase(); String pathPrefix = null; - String[] produces = null; - String[] consumes = null; + String[] baseProduces = null; + String[] baseConsumes = null; if (routeBaseAnnotation != null) { AnnotationValue pathPrefixValue = routeBaseAnnotation.value(VALUE_PATH); @@ -188,52 +203,66 @@ void addAdditionalRoutes( } AnnotationValue producesValue = routeBaseAnnotation.value(VALUE_PRODUCES); if (producesValue != null) { - produces = producesValue.asStringArray(); + baseProduces = producesValue.asStringArray(); } AnnotationValue consumesValue = routeBaseAnnotation.value(VALUE_CONSUMES); if (consumesValue != null) { - consumes = consumesValue.asStringArray(); + baseConsumes = consumesValue.asStringArray(); } } - for (AnnotationInstance routeAnnotation : businessMethod.getRoutes()) { - AnnotationProxyBuilder builder = annotationProxy.builder(routeAnnotation, Route.class); - AnnotationValue regexValue = routeAnnotation.value(VALUE_REGEX); - AnnotationValue pathValue = routeAnnotation.value(VALUE_PATH); + for (AnnotationInstance route : businessMethod.getRoutes()) { + AnnotationValue regexValue = route.value(VALUE_REGEX); + AnnotationValue pathValue = route.value(VALUE_PATH); + AnnotationValue orderValue = route.valueWithDefault(index, VALUE_ORDER); + AnnotationValue producesValue = route.valueWithDefault(index, VALUE_PRODUCES); + AnnotationValue consumesValue = route.valueWithDefault(index, VALUE_CONSUMES); + AnnotationValue methodsValue = route.valueWithDefault(index, VALUE_METHODS); + + String path = null; + String regex = null; + String[] produces = producesValue.asStringArray(); + String[] consumes = consumesValue.asStringArray(); + HttpMethod[] methods = Arrays.stream(methodsValue.asEnumArray()).map(HttpMethod::valueOf) + .toArray(HttpMethod[]::new); + Integer order = orderValue.asInt(); if (regexValue == null) { if (pathPrefix != null) { - StringBuilder path = new StringBuilder(); - path.append(pathPrefix); + StringBuilder prefixedPath = new StringBuilder(); + prefixedPath.append(pathPrefix); if (pathValue == null) { - path.append(DASH); - path.append(dashify(businessMethod.getMethod().name())); + prefixedPath.append(SLASH); + prefixedPath.append(dashify(businessMethod.getMethod().name())); } else { - String pathValueStr = pathValue.asString(); - if (!pathValueStr.startsWith(DASH)) { - path.append(DASH); + if (!pathValue.asString().startsWith(SLASH)) { + prefixedPath.append(SLASH); } - path.append(pathValue.asString()); + prefixedPath.append(pathValue.asString()); } - builder.withValue(VALUE_PATH, path.toString()); + path = prefixedPath.toString(); } else { - if (pathValue == null) { - builder.withDefaultValue(VALUE_PATH, dashify(businessMethod.getMethod().name())); - } + path = pathValue != null ? pathValue.asString() : dashify(businessMethod.getMethod().name()); + } + if (!path.startsWith(SLASH)) { + path = SLASH + path; } + } else { + regex = regexValue.asString(); } - if (routeAnnotation.value(VALUE_PRODUCES) == null && produces != null) { - builder.withValue(VALUE_PRODUCES, produces); + if (route.value(VALUE_PRODUCES) == null && baseProduces != null) { + produces = baseProduces; } - if (routeAnnotation.value(VALUE_CONSUMES) == null && consumes != null) { - builder.withValue(VALUE_CONSUMES, consumes); + if (route.value(VALUE_CONSUMES) == null && baseConsumes != null) { + consumes = baseConsumes; } - Route route = builder.build(classOutput); - Function routeFunction = recorder.createRouteFunction(route, + RouteMatcher matcher = new RouteMatcher(path, regex, produces, consumes, methods, order); + matchers.put(matcher, businessMethod.getMethod()); + Function routeFunction = recorder.createRouteFunction(matcher, bodyHandler.getHandler()); - AnnotationValue typeValue = routeAnnotation.value("type"); + AnnotationValue typeValue = route.value("type"); HandlerType handlerType = HandlerType.NORMAL; if (typeValue != null) { String typeString = typeValue.asEnum(); @@ -263,6 +292,8 @@ void addAdditionalRoutes( filterProducer.produce(new FilterBuildItem(routingHandler, priorityValue != null ? priorityValue.asInt() : RouteFilter.DEFAULT_PRIORITY)); } + + detectConflictingRoutes(matchers); } @BuildStep @@ -397,4 +428,84 @@ private static String dashify(String value) { } return ret.toString(); } + + private void detectConflictingRoutes(Map matchers) { + if (matchers.isEmpty()) { + return; + } + // First we need to group matchers that could potentially match the same request + Set> groups = new HashSet<>(); + for (Iterator> iterator = matchers.entrySet().iterator(); iterator.hasNext();) { + Entry entry = iterator.next(); + LinkedHashSet group = new LinkedHashSet<>(); + group.add(entry.getKey()); + matchers.entrySet().stream().filter(e -> { + if (e.getKey().equals(entry.getKey())) { + // Skip - the same matcher + return false; + } + if (e.getValue().equals(entry.getValue())) { + // Skip - the same method + return false; + } + if (e.getKey().getOrder() != entry.getKey().getOrder()) { + // Skip - different order set + return false; + } + return canMatchSameRequest(entry.getKey(), e.getKey()); + }).map(Entry::getKey).forEach(group::add); + groups.add(group); + } + // Log a warning for any group that contains more than one member + boolean conflictExists = false; + for (Set group : groups) { + if (group.size() > 1) { + Iterator it = group.iterator(); + RouteMatcher firstMatcher = it.next(); + MethodInfo firstMethod = matchers.get(firstMatcher); + conflictExists = true; + StringBuilder conflictingRoutes = new StringBuilder(); + while (it.hasNext()) { + RouteMatcher rm = it.next(); + MethodInfo method = matchers.get(rm); + conflictingRoutes.append("\n\t- ").append(method.declaringClass().name().toString()).append("#") + .append(method.name()).append("()"); + } + LOGGER.warnf( + "Route %s#%s() can match the same request and has the same order [%s] as:%s", + firstMethod.declaringClass().name(), + firstMethod.name(), firstMatcher.getOrder(), conflictingRoutes); + } + } + if (conflictExists) { + LOGGER.warn("You can use @Route#order() to ensure the routes are not executed in random order"); + } + } + + static boolean canMatchSameRequest(RouteMatcher m1, RouteMatcher m2) { + // regex not null and other not equal + if (m1.getRegex() != null) { + if (!Objects.equals(m1.getRegex(), m2.getRegex())) { + return false; + } + } else { + // path not null and other not equal + if (m1.getPath() != null && !Objects.equals(m1.getPath(), m2.getPath())) { + return false; + } + } + // methods not matching + if (m1.getMethods().length > 0 && m2.getMethods().length > 0 && !Arrays.equals(m1.getMethods(), m2.getMethods())) { + return false; + } + // produces not matching + if (m1.getProduces().length > 0 && m2.getProduces().length > 0 && !Arrays.equals(m1.getProduces(), m2.getProduces())) { + return false; + } + // consumes not matching + if (m1.getConsumes().length > 0 && m2.getConsumes().length > 0 && !Arrays.equals(m1.getConsumes(), m2.getConsumes())) { + return false; + } + return true; + } } diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteMatcher.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteMatcher.java new file mode 100644 index 0000000000000..663d72946d385 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteMatcher.java @@ -0,0 +1,74 @@ +package io.quarkus.vertx.web.runtime; + +import io.vertx.core.http.HttpMethod; + +public class RouteMatcher { + + private String path; + private String regex; + private String[] produces; + private String[] consumes; + private HttpMethod[] methods; + private int order; + + public RouteMatcher() { + } + + public RouteMatcher(String path, String regex, String[] produces, String[] consumes, HttpMethod[] methods, int order) { + this.path = path; + this.regex = regex; + this.produces = produces; + this.consumes = consumes; + this.methods = methods; + this.order = order; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public String[] getProduces() { + return produces; + } + + public void setProduces(String[] produces) { + this.produces = produces; + } + + public String[] getConsumes() { + return consumes; + } + + public void setConsumes(String[] consumes) { + this.consumes = consumes; + } + + public HttpMethod[] getMethods() { + return methods; + } + + public void setMethods(HttpMethod[] methods) { + this.methods = methods; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index c6d69288c10ab..bd655db0ebcad 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -5,7 +5,6 @@ import io.quarkus.runtime.annotations.Recorder; import io.quarkus.vertx.http.runtime.RouterProducer; -import io.quarkus.vertx.web.Route; import io.vertx.core.Handler; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; @@ -30,34 +29,34 @@ public Handler createHandler(String handlerClassName) { } } - public Function createRouteFunction(Route routeAnnotation, + public Function createRouteFunction(RouteMatcher matcher, Handler bodyHandler) { return new Function() { @Override public io.vertx.ext.web.Route apply(Router router) { io.vertx.ext.web.Route route; - if (!routeAnnotation.regex().isEmpty()) { - route = router.routeWithRegex(routeAnnotation.regex()); - } else if (!routeAnnotation.path().isEmpty()) { - route = router.route(ensureStartWithSlash(routeAnnotation.path())); + if (matcher.getRegex() != null && !matcher.getRegex().isEmpty()) { + route = router.routeWithRegex(matcher.getRegex()); + } else if (matcher.getPath() != null && !matcher.getPath().isEmpty()) { + route = router.route(matcher.getPath()); } else { route = router.route(); } - if (routeAnnotation.methods().length > 0) { - for (HttpMethod method : routeAnnotation.methods()) { + if (matcher.getMethods().length > 0) { + for (HttpMethod method : matcher.getMethods()) { route.method(method); } } - if (routeAnnotation.order() > 0) { - route.order(routeAnnotation.order()); + if (matcher.getOrder() > 0) { + route.order(matcher.getOrder()); } - if (routeAnnotation.produces().length > 0) { - for (String produces : routeAnnotation.produces()) { + if (matcher.getProduces().length > 0) { + for (String produces : matcher.getProduces()) { route.produces(produces); } } - if (routeAnnotation.consumes().length > 0) { - for (String consumes : routeAnnotation.consumes()) { + if (matcher.getConsumes().length > 0) { + for (String consumes : matcher.getConsumes()) { route.consumes(consumes); } } @@ -69,12 +68,4 @@ public io.vertx.ext.web.Route apply(Router router) { }; } - private String ensureStartWithSlash(String path) { - if (path.startsWith("/")) { - return path; - } else { - return "/" + path; - } - } - -} +} \ No newline at end of file From 3b7efb00cd2fea426e861eb966d15fd1f24f56a0 Mon Sep 17 00:00:00 2001 From: kdnakt Date: Sat, 28 Dec 2019 11:09:55 +0900 Subject: [PATCH 441/602] fix typo --- .../resources/archetype-resources/src/main/java/TestLambda.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/TestLambda.java b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/TestLambda.java index eeb61e79975bb..167b7f25510b2 100644 --- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/TestLambda.java +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/TestLambda.java @@ -14,6 +14,6 @@ public class TestLambda implements RequestHandler { @Override public OutputObject handleRequest(InputObject input, Context context) { - return service.proces(input).setRequestId(context.getAwsRequestId()); + return service.process(input).setRequestId(context.getAwsRequestId()); } } From ef90bcca2462637f6a4ddbabe4a38aa4bbbe5676 Mon Sep 17 00:00:00 2001 From: Romain Quinio Date: Thu, 26 Dec 2019 13:53:29 +0100 Subject: [PATCH 442/602] Handle javax.inject.Named on implict singleton bean in spring-di --- .../io/quarkus/spring/di/deployment/SpringDIProcessor.java | 6 +++++- .../main/java/io/quarkus/it/spring/AppConfiguration.java | 6 ++++++ .../io/quarkus/it/spring/InjectedSpringBeansResource.java | 7 +++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index 7fd9463ccce3e..3ceef51339151 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -313,7 +313,11 @@ Set getAnnotationsToAdd( } } } - final DotName declaredScope = getScope(classInfo); + DotName declaredScope = getScope(classInfo); + // @Named is a bean-defining annotation in Spring, but not in Arc. + if (declaredScope == null && classInfo.classAnnotation(CDI_NAMED_ANNOTATION) != null) { + declaredScope = CDI_SINGLETON_ANNOTATION; // implicit default scope in spring + } final boolean isAnnotation = isAnnotation(classInfo.flags()); if (declaredScope != null) { annotationsToAdd.add(create( diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java index 5fef2202c950f..c2c0d08be8ba9 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java @@ -1,5 +1,6 @@ package io.quarkus.it.spring; +import javax.inject.Named; import javax.inject.Singleton; import org.springframework.beans.factory.annotation.Qualifier; @@ -50,4 +51,9 @@ private static class AnotherRequestBean { public static class CustomPrototypeBean { } + + @Named + public static class NamedBean { + + } } diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java index f0ceb74896841..3dd0a8ffd287b 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java @@ -1,7 +1,5 @@ package io.quarkus.it.spring; -import static io.quarkus.it.spring.AppConfiguration.CustomPrototypeBean; - import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; @@ -11,6 +9,9 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import io.quarkus.it.spring.AppConfiguration.CustomPrototypeBean; +import io.quarkus.it.spring.AppConfiguration.NamedBean; + @Path("/") public class InjectedSpringBeansResource { @@ -22,6 +23,8 @@ public class InjectedSpringBeansResource { SessionBean sessionBean; @Inject CustomPrototypeBean anotherRequestBean; + @Inject + NamedBean namedBean; @GET @Produces(MediaType.TEXT_PLAIN) From 34769642ea0b7fc932c61db8d814b0abd89705e1 Mon Sep 17 00:00:00 2001 From: Ian Wormsbecker Date: Mon, 30 Dec 2019 18:21:20 -0500 Subject: [PATCH 443/602] Update docs with import for RegisterForReflection --- docs/src/main/asciidoc/rest-json.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/asciidoc/rest-json.adoc b/docs/src/main/asciidoc/rest-json.adoc index ae3b6bf817d2c..a5b649f20c1cb 100644 --- a/docs/src/main/asciidoc/rest-json.adoc +++ b/docs/src/main/asciidoc/rest-json.adoc @@ -365,6 +365,8 @@ Hopefully, this will change in the future and make the error more obvious. We can register `Legume` for reflection manually by adding the `@RegisterForReflection` annotation on our `Legume` class: [source,JAVA] ---- +import io.quarkus.runtime.annotations.RegisterForReflection; + @RegisterForReflection public class Legume { // ... From d2b925baea313d635017b11736632d1a40f2ea32 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Sat, 28 Dec 2019 22:34:39 -0300 Subject: [PATCH 444/602] Using constants in Gradle tests --- .../java/io/quarkus/gradle/QuarkusPlugin.java | 4 ++- .../io/quarkus/gradle/QuarkusPluginTest.java | 29 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 29fce93e67077..ff82454012279 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -27,6 +27,8 @@ public class QuarkusPlugin implements Plugin { + public static final String ID = "io.quarkus"; + public static final String EXTENSION_NAME = "quarkus"; public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions"; public static final String ADD_EXTENSION_TASK_NAME = "addExtension"; @@ -75,7 +77,7 @@ private void registerTasks(Project project) { buildNative.dependsOn(tasks.getByName(BasePlugin.ASSEMBLE_TASK_NAME)); - SourceSetContainer sourceSets = project.getConvention().findPlugin(JavaPluginConvention.class) + SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class) .getSourceSets(); SourceSet nativeTestSourceSet = sourceSets.create(NATIVE_TEST_SOURCE_SET_NAME); SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); diff --git a/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 450cf057f435d..337df0ded4573 100644 --- a/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.gradle.api.Project; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.tasks.TaskContainer; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; @@ -14,38 +16,41 @@ public class QuarkusPluginTest { @Test public void shouldCreateTasks() { Project project = ProjectBuilder.builder().build(); - project.getPluginManager().apply("io.quarkus"); + project.getPluginManager().apply(QuarkusPlugin.ID); - assertTrue(project.getPluginManager().hasPlugin("io.quarkus")); + assertTrue(project.getPluginManager().hasPlugin(QuarkusPlugin.ID)); TaskContainer tasks = project.getTasks(); - assertNotNull(tasks.getByName("quarkusBuild")); - assertNotNull(tasks.getByName("quarkusDev")); - assertNotNull(tasks.getByName("buildNative")); - assertNotNull(tasks.getByName("listExtensions")); - assertNotNull(tasks.getByName("addExtension")); + assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME)); + assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_DEV_TASK_NAME)); + assertNotNull(tasks.getByName(QuarkusPlugin.BUILD_NATIVE_TASK_NAME)); + assertNotNull(tasks.getByName(QuarkusPlugin.LIST_EXTENSIONS_TASK_NAME)); + assertNotNull(tasks.getByName(QuarkusPlugin.ADD_EXTENSION_TASK_NAME)); } @Test public void shouldMakeAssembleDependOnQuarkusBuild() { Project project = ProjectBuilder.builder().build(); - project.getPluginManager().apply("io.quarkus"); + project.getPluginManager().apply(QuarkusPlugin.ID); project.getPluginManager().apply("base"); TaskContainer tasks = project.getTasks(); - assertThat(tasks.getByName("assemble").getDependsOn()).contains(tasks.getByName("quarkusBuild")); + assertThat(tasks.getByName(BasePlugin.ASSEMBLE_TASK_NAME).getDependsOn()) + .contains(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME)); } @Test public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { Project project = ProjectBuilder.builder().build(); - project.getPluginManager().apply("io.quarkus"); + project.getPluginManager().apply(QuarkusPlugin.ID); project.getPluginManager().apply("java"); TaskContainer tasks = project.getTasks(); - assertThat(tasks.getByName("quarkusBuild").getDependsOn()).contains(tasks.getByName("classes")); - assertThat(tasks.getByName("quarkusDev").getDependsOn()).contains(tasks.getByName("classes")); + assertThat(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME).getDependsOn()) + .contains(tasks.getByName(JavaPlugin.CLASSES_TASK_NAME)); + assertThat(tasks.getByName(QuarkusPlugin.QUARKUS_DEV_TASK_NAME).getDependsOn()) + .contains(tasks.getByName(JavaPlugin.CLASSES_TASK_NAME)); } } From 946d9655a086185651ac008095e69dd339f23d8a Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 31 Dec 2019 18:21:07 +0100 Subject: [PATCH 445/602] docs(qute): add link to Qute reference guide in the Qute templating engine guide Fixes https://github.com/quarkusio/quarkus/issues/6367 --- docs/src/main/asciidoc/qute.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index 1efa8f0ca1c16..2b10e3095e606 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -263,6 +263,10 @@ public class ReportGenerator { <2> Use the `@Scheduled` annotation to instruct Quarkus to execute this method on the half hour. For more information see the link:scheduler[Scheduler] guide. <3> The `TemplateInstance.render()` method triggers rendering. Note that this method blocks the current thread. +== Qute Reference Guide + +To learn more about Qute, please refer to the link:qute-reference[Qute reference guide]. + [[qute-configuration-reference]] == Qute Configuration Reference From 4d9a47b42de249dacd18f76206e0450767180b29 Mon Sep 17 00:00:00 2001 From: kdnakt Date: Thu, 2 Jan 2020 12:31:12 +0900 Subject: [PATCH 446/602] docs: fix project artifact id for jackson --- docs/src/main/asciidoc/rest-json.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rest-json.adoc b/docs/src/main/asciidoc/rest-json.adoc index a5b649f20c1cb..a5870964b0fd8 100644 --- a/docs/src/main/asciidoc/rest-json.adoc +++ b/docs/src/main/asciidoc/rest-json.adoc @@ -60,7 +60,7 @@ Quarkus also supports https://github.com/FasterXML/jackson[Jackson] so, if you p ---- mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ - -DprojectArtifactId=rest-json \ + -DprojectArtifactId=rest-json-quickstart \ -DclassName="org.acme.rest.json.FruitResource" \ -Dpath="/fruits" \ -Dextensions="resteasy-jackson" From 6f7c9803542293a26bd2e8c28dfd72d0c5197ca1 Mon Sep 17 00:00:00 2001 From: kdnakt Date: Thu, 2 Jan 2020 17:27:33 +0900 Subject: [PATCH 447/602] fix project name in the link to legumes.html --- docs/src/main/asciidoc/rest-json.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/rest-json.adoc b/docs/src/main/asciidoc/rest-json.adoc index a5870964b0fd8..9609042b14eed 100644 --- a/docs/src/main/asciidoc/rest-json.adoc +++ b/docs/src/main/asciidoc/rest-json.adoc @@ -339,7 +339,8 @@ public class LegumeResource { ---- Now let's add a simple web page to display our list of legumes. -In the `src/main/resources/META-INF/resources` directory, add a `legumes.html` file with the content from this https://raw.githubusercontent.com/quarkusio/quarkus-quickstarts/master/rest-json/src/main/resources/META-INF/resources/legumes.html[legumes.html] file in it. +In the `src/main/resources/META-INF/resources` directory, add a `legumes.html` file with the content from this +{quickstarts-blob-url}/rest-json-quickstart/src/main/resources/META-INF/resources/legumes.html[legumes.html] file in it. Open a browser to http://localhost:8080/legumes.html and you will see our list of legumes. From bccf1d983479ecb1a375c7e00dd22627aa37bc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Mon, 30 Dec 2019 09:43:10 +0100 Subject: [PATCH 448/602] Fix Elytron properties perf issue --- .../security/runtime/ElytronPropertiesFileRecorder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/extensions/elytron-security-properties-file/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPropertiesFileRecorder.java b/extensions/elytron-security-properties-file/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPropertiesFileRecorder.java index fbad99bf7e10f..75805ad91a405 100644 --- a/extensions/elytron-security-properties-file/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPropertiesFileRecorder.java +++ b/extensions/elytron-security-properties-file/runtime/src/main/java/io/quarkus/elytron/security/runtime/ElytronPropertiesFileRecorder.java @@ -44,6 +44,8 @@ public class ElytronPropertiesFileRecorder { static final Logger log = Logger.getLogger(ElytronPropertiesFileRecorder.class); + private static final Provider[] PROVIDERS = new Provider[] { new WildFlyElytronProvider() }; + /** * Load the user.properties and roles.properties files into the {@linkplain SecurityRealm} * @@ -172,7 +174,7 @@ public RuntimeValue createRealm(PropertiesRealmConfig config) thr .setProviders(new Supplier() { @Override public Provider[] get() { - return new Provider[] { new WildFlyElytronProvider() }; + return PROVIDERS; } }) .setPlainText(config.plainText) @@ -193,7 +195,7 @@ public RuntimeValue createRealm(MPRealmConfig config) { Supplier providers = new Supplier() { @Override public Provider[] get() { - return new Provider[] { new WildFlyElytronProvider() }; + return PROVIDERS; } }; SecurityRealm realm = new SimpleMapBackedSecurityRealm(NameRewriter.IDENTITY_REWRITER, providers); From bf950997e00889a242a8b79fb8d6e0b65fee30ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Mon, 30 Dec 2019 14:23:43 +0100 Subject: [PATCH 449/602] Fix Elytron JDBC perf issue --- .../java/io/quarkus/elytron/security/jdbc/JdbcRecorder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/elytron-security-jdbc/runtime/src/main/java/io/quarkus/elytron/security/jdbc/JdbcRecorder.java b/extensions/elytron-security-jdbc/runtime/src/main/java/io/quarkus/elytron/security/jdbc/JdbcRecorder.java index 8baf6209befb8..1949f76614804 100644 --- a/extensions/elytron-security-jdbc/runtime/src/main/java/io/quarkus/elytron/security/jdbc/JdbcRecorder.java +++ b/extensions/elytron-security-jdbc/runtime/src/main/java/io/quarkus/elytron/security/jdbc/JdbcRecorder.java @@ -19,6 +19,8 @@ @Recorder public class JdbcRecorder { + private static final Provider[] PROVIDERS = new Provider[] { new WildFlyElytronProvider() }; + /** * Create a runtime value for a {@linkplain JdbcSecurityRealm} * @@ -29,7 +31,7 @@ public RuntimeValue createRealm(JdbcSecurityRealmConfig config) { Supplier providers = new Supplier() { @Override public Provider[] get() { - return new Provider[] { new WildFlyElytronProvider() }; + return PROVIDERS; } }; JdbcSecurityRealmBuilder builder = JdbcSecurityRealm.builder().setProviders(providers); From 9027479f4282650f528367ff443a44d941663168 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 2 Jan 2020 11:09:48 +0200 Subject: [PATCH 450/602] Ensure that MySQL integration test works properly with Docker Maven plugin Fixes: #6372 --- integration-tests/jpa-mysql/pom.xml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/integration-tests/jpa-mysql/pom.xml b/integration-tests/jpa-mysql/pom.xml index 5494541abb1f5..a1c69bdcb57ad 100644 --- a/integration-tests/jpa-mysql/pom.xml +++ b/integration-tests/jpa-mysql/pom.xml @@ -195,14 +195,12 @@ /var/lib/mysql - - - mapped - - 3306 - - - + + + + + mysqladmin ping -h localhost -u hibernate_orm_test -phibernate_orm_test + From c9fbc2e0d7da3ee98598341234a39cb0477fe49b Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 17 Dec 2019 08:26:20 +0100 Subject: [PATCH 451/602] Add a quick workaround to get javadoc generation happy --- .../security/common/deployment/DummyForJavadoc.java | 8 ++++++++ .../security/common/runtime/graal/DummyForJavadoc.java | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 extensions/elytron-security-common/deployment/src/main/java/io/quarkus/elytron/security/common/deployment/DummyForJavadoc.java create mode 100644 extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/DummyForJavadoc.java diff --git a/extensions/elytron-security-common/deployment/src/main/java/io/quarkus/elytron/security/common/deployment/DummyForJavadoc.java b/extensions/elytron-security-common/deployment/src/main/java/io/quarkus/elytron/security/common/deployment/DummyForJavadoc.java new file mode 100644 index 0000000000000..0eb1f2036ad72 --- /dev/null +++ b/extensions/elytron-security-common/deployment/src/main/java/io/quarkus/elytron/security/common/deployment/DummyForJavadoc.java @@ -0,0 +1,8 @@ +package io.quarkus.elytron.security.common.deployment; + +/** + * Quick workaround to have at least one public class and generate a Javadoc jar. + */ +public class DummyForJavadoc { + +} diff --git a/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/DummyForJavadoc.java b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/DummyForJavadoc.java new file mode 100644 index 0000000000000..d9752130350ab --- /dev/null +++ b/extensions/elytron-security-common/runtime/src/main/java/io/quarkus/elytron/security/common/runtime/graal/DummyForJavadoc.java @@ -0,0 +1,8 @@ +package io.quarkus.elytron.security.common.runtime.graal; + +/** + * Quick workaround to have at least one public class and generate a Javadoc jar. + */ +public class DummyForJavadoc { + +} From 2c84de3a61f99a551359f8bb568b6c866dc3b898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Thu, 2 Jan 2020 17:31:19 +0100 Subject: [PATCH 452/602] Add Emitter usage to reactive messaging guides --- docs/src/main/asciidoc/amqp.adoc | 36 +++++++++++++++++++++++++++++++ docs/src/main/asciidoc/kafka.adoc | 36 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/docs/src/main/asciidoc/amqp.adoc b/docs/src/main/asciidoc/amqp.adoc index 63918544723db..54191201fe2b1 100644 --- a/docs/src/main/asciidoc/amqp.adoc +++ b/docs/src/main/asciidoc/amqp.adoc @@ -305,6 +305,42 @@ You can build the native executable with: ./mvnw package -Pnative ---- +== Imperative usage + +Sometimes you need to have an imperative way of sending messages. + +For example, if you need to send a message to a stream from inside a REST endpoint when receiving a POST request. +In this case, you cannot use `@Output` because your method has parameters. + +For this, you can use an `Emitter`. + +[source, java] +---- +import io.smallrye.reactive.messaging.annotations.Channel; +import io.smallrye.reactive.messaging.annotations.Emitter; + +import javax.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +@Path("/prices") +public class PriceResource { + + @Inject @Channel("price-create") Emitter priceEmitter; + + @POST + @Consumes(MediaType.TEXT_PLAIN) + public void addPrice(Double price) { + priceEmitter.send(price); + } +} +---- + +NOTE: The `Emitter` configuration is done the same way as the other stream configuration used by `@Incoming` and `@Outgoing`. +In addition, you can use `@OnOverflow` to configure a back-pressure strategy. + == Going further This guide has shown how you can interact with AMQP using Quarkus. diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 3989e1958465b..20938c3f85355 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -308,6 +308,42 @@ You can build the native executable with: ./mvnw package -Pnative ---- +== Imperative usage + +Sometimes, you need to have an imperative way of sending messages. + +For example, if you need to send a message to a stream, from inside a REST endpoint, when receiving a POST request. +In this case, you cannot use `@Output` because your method has parameters. + +For this, you can use an `Emitter`. + +[source, java] +---- +import io.smallrye.reactive.messaging.annotations.Channel; +import io.smallrye.reactive.messaging.annotations.Emitter; + +import javax.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +@Path("/prices") +public class PriceResource { + + @Inject @Channel("price-create") Emitter priceEmitter; + + @POST + @Consumes(MediaType.TEXT_PLAIN) + public void addPrice(Double price) { + priceEmitter.send(price); + } +} +---- + +NOTE: The `Emitter` configuration is done the same way as the other stream configuration used by `@Incoming` and `@Outgoing`. +In addition, you can use `@OnOverflow` to configure back-pressure strategy. + == Kafka Health Check If you are using the `quarkus-smallrye-health` extension, `quarkus-kafka` can add a readiness health check From 80f8c8b05343d6b4c4e2cc8411fdb1f0fb16d137 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 2 Jan 2020 18:49:06 +0200 Subject: [PATCH 453/602] Properly initialize Maven profile options during bootstrap Fixes: #6383 --- .../resolver/maven/options/BootstrapMavenOptionsParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java index 9b145e2e9772b..2e4f77b429732 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/options/BootstrapMavenOptionsParser.java @@ -20,7 +20,7 @@ public static Map parse(String[] args) { final Map map = new HashMap<>(); put(cmdLine, map, CLIManager.ALTERNATE_USER_SETTINGS); put(cmdLine, map, CLIManager.ALTERNATE_GLOBAL_SETTINGS); - put(cmdLine, map, CLIManager.ACTIVATE_PROFILES); + put(map, String.valueOf(CLIManager.ACTIVATE_PROFILES), cmdLine.getOptionValues(CLIManager.ACTIVATE_PROFILES)); putBoolean(cmdLine, map, CLIManager.SUPRESS_SNAPSHOT_UPDATES); putBoolean(cmdLine, map, CLIManager.UPDATE_SNAPSHOTS); From 7c49b48a31a2c066ec2c8355da6ab43b162a8547 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 3 Jan 2020 14:35:49 +0200 Subject: [PATCH 454/602] Document how to retrieve the active profile --- docs/src/main/asciidoc/config.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 11f37bd2c438e..8f11ad81687a4 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -434,6 +434,13 @@ quarkus.http.port=9090 And then set the `QUARKUS_PROFILE` environment variable to `staging` to activate my profile. +[NOTE] +==== +The proper way to check the active profile programmatically is to use the `getActiveProfile` method of `io.quarkus.runtime.configuration.ProfileManager`. + +Using `@ConfigProperty("quarkus.profile")` will *not* work properly. +==== + === Clearing properties Run time properties which are optional, and which have had values set at build time or which have a default value, From f2223c68b3764f0df3b27ff919a7a068c44db008 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 3 Jan 2020 11:27:51 +0200 Subject: [PATCH 455/602] Create BuildTimeRunTimeDefaultValuesConfigSource only once --- .../configuration/RunTimeConfigurationGenerator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java index 76bbecb559bec..32f7f5458b2c9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -305,7 +305,8 @@ static final class GenerateOperation implements AutoCloseable { // the build time run time visible default values config source cc.getFieldCreator(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE) .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(BTRTDVCS_NEW)); + final ResultHandle buildTimeRunTimeDefaultValuesConfigSource = clinit.newInstance(BTRTDVCS_NEW); + clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, buildTimeRunTimeDefaultValuesConfigSource); // the run time default values config source cc.getFieldCreator(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE) @@ -318,7 +319,7 @@ static final class GenerateOperation implements AutoCloseable { // build time values clinit.writeArrayValue(array, 0, buildTimeConfigSource); // build time defaults - clinit.writeArrayValue(array, 1, clinit.newInstance(BTRTDVCS_NEW)); + clinit.writeArrayValue(array, 1, buildTimeRunTimeDefaultValuesConfigSource); clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, buildTimeBuilder, array); clinitConfig = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, buildTimeBuilder), SmallRyeConfig.class); From 6a0da3ba632ecf210d07113f61e6a49d1c81e6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 3 Jan 2020 13:55:00 +0100 Subject: [PATCH 456/602] Add JSON-B and Jackson serialization to the Kafka guide --- docs/src/main/asciidoc/kafka.adoc | 191 ++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index 20938c3f85355..d50be50dec3a2 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -354,6 +354,197 @@ If enabled, when you access the `/health/ready` endpoint of your application you This behavior can be enabled by setting the `quarkus.kafka.health.enabled` property to `true` in your `application.properties`. You also need to point `quarkus.kafka.bootstrap-servers` to your Kafka cluster. +== JSON serialization + +Quarkus has built-in capabilities to deal with JSON Kafka messages. + +Imagine we have a `Fruit` pojo as follows: + +[source,java] +---- +public class Fruit { + + public String name; + public int price; + + public Fruit() { + } + + public Fruit(String name, int price) { + this.name = name; + this.price = price; + } +} +---- + +And we want to use it to receive messages from Kafka, make some price transformation, and send messages back to Kafka. + +[source,java] +---- +import io.smallrye.reactive.messaging.annotations.Broadcast; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import javax.enterprise.context.ApplicationScoped; + +/** +* A bean consuming data from the "fruit-in" Kafka topic and applying some price conversion. +* The result is pushed to the "fruit-out" stream. +*/ +@ApplicationScoped +public class FruitProcessor { + + private static final double CONVERSION_RATE = 0.88; + + @Incoming("fruit-in") + @Outgoing("fruit-out") + @Broadcast + public double process(Fruit fruit) { + fruit.price = fruit.price * CONVERSION_RATE; + return fruit; + } + +} +---- + +To do this, we will need to setup JSON serialization with JSON-B or Jackson. + +NOTE: With JSON serialization correctly configured, you can also use `Publisher` and `Emitter`. + +=== Serializing via JSON-B + +First, you need to include the `quarkus-jsonb` extension (if you already use the `quarkus-resteasy-jsonb` extension, this is not needed). + +[source, xml] +---- + + io.quarkus + quarkus-jsonb + +---- + +There is an existing `JsonbSerializer` that can be used to serialize all pojos via JSON-B, +but the corresponding deserializer is generic, so it needs to be subclassed. + +So, let's create a `FruitDeserializer` that extends the generic `JsonbDeserializer`. + +[source,java] +---- +package com.acme.fruit.jsonb; + +import io.quarkus.kafka.client.serialization.JsonbDeserializer; + +public class FruitDeserializer extends JsonbDeserializer { + public FruitDeserializer(){ + // pass the class to the parent. + super(Fruit.class); + } +} +---- + +NOTE: If you don't want to create a deserializer for each of your pojo, you can use the generic `io.vertx.kafka.client.serialization.JsonObjectDeserializer` +that will deserialize to a `javax.json.JsonObject`. The corresponding serializer can also be used: `io.vertx.kafka.client.serialization.JsonObjectSerializer`. + +Finally, configure your streams to use the JSON-B serializer and deserializer. + +[source,properties] +---- +# Configure the Kafka source (we read from it) +mp.messaging.incoming.fruit-in.connector=smallrye-kafka +mp.messaging.incoming.fruit-in.topic=fruit-in +mp.messaging.incoming.fruit-in.value.deserializer=com.acme.fruit.jsonb.FruitDeserializer + +# Configure the Kafka sink (we write to it) +mp.messaging.outgoing.fruit-out.connector=smallrye-kafka +mp.messaging.outgoing.fruit-out.topic=fruit-out +mp.messaging.outgoing.fruit-out.value.serializer=io.quarkus.kafka.client.serialization.JsonbSerializer +---- + +Now, your Kafka messages will contain a JSON-B serialized representation of your Fruit pojo. + +=== Serializing via Jackson + +First, you need to include the `quarkus-jackson` extension (if you already use the `quarkus-jackson-jsonb` extension, this is not needed). + +[source, xml] +---- + + io.quarkus + quarkus-jackson + +---- + +There is an existing `ObjectMapperSerializer` that can be used to serialize all pojos via Jackson, +but the corresponding deserializer is generic, so it needs to be subclassed. + +So, let's create a `FruitDeserializer` that extends the `ObjectMapperDeserializer`. + +[source,java] +---- +package com.acme.fruit.jackson; + +import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer; + +public class FruitDeserializer extends ObjectMapperDeserializer { + public FruitDeserializer(){ + // pass the class to the parent. + super(Fruit.class); + } +} +---- + +Finally, configure your streams to use the Jackson serializer and deserializer. + +[source,properties] +---- +# Configure the Kafka source (we read from it) +mp.messaging.incoming.fruit-in.connector=smallrye-kafka +mp.messaging.incoming.fruit-in.topic=fruit-in +mp.messaging.incoming.fruit-in.value.deserializer=com.acme.fruit.jackson.FruitDeserializer + +# Configure the Kafka sink (we write to it) +mp.messaging.outgoing.fruit-out.connector=smallrye-kafka +mp.messaging.outgoing.fruit-out.topic=fruit-out +mp.messaging.outgoing.fruit-out.value.serializer=io.quarkus.kafka.client.serialization.ObjectMapperSerializer +---- + +Now, your Kafka messages will contain a Jackson serialized representation of your Fruit pojo. + +=== Sending JSON Server-Sent Events (SSE) + +If you want RESTEasy to send JSON Server-Sent Events, you need to use the `@SseElementType` annotation to define the content type of the events, +as the method will be annotated with `@Produces(MediaType.SERVER_SENT_EVENTS)`. + +The following example shows how to use SSE from a Kafka topic source. + +[source,java] +---- +import io.smallrye.reactive.messaging.annotations.Channel; +import org.reactivestreams.Publisher; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.jboss.resteasy.annotations.SseElementType; + +@Path("/fruits") +public class PriceResource { + + @Inject + @Channel("fruit-out") Publisher fruits; + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + public Publisher stream() { + return fruits; + } +} +---- + == Going further This guide has shown how you can interact with Kafka using Quarkus. From f2306264b070d7837a05d4a2195e81eb779469c5 Mon Sep 17 00:00:00 2001 From: x80486 Date: Sat, 21 Dec 2019 19:27:31 -0500 Subject: [PATCH 457/602] feat(flyway): integrate flyway's validate functionality into quarkus --- docs/src/main/asciidoc/flyway.adoc | 10 +++++-- .../quarkus/flyway/runtime/FlywayCreator.java | 12 ++++---- .../FlywayDataSourceRuntimeConfig.java | 8 +++++- .../flyway/runtime/FlywayRecorder.java | 8 ++---- .../flyway/runtime/FlywayCreatorTest.java | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/docs/src/main/asciidoc/flyway.adoc b/docs/src/main/asciidoc/flyway.adoc index c2b69a45d9919..08e2b4f113dc1 100644 --- a/docs/src/main/asciidoc/flyway.adoc +++ b/docs/src/main/asciidoc/flyway.adoc @@ -43,9 +43,9 @@ In your `pom.xml`, add the following dependencies: -- -Flyway support relies on the Quarkus datasource config. +Flyway support relies on the Quarkus datasource config. It can be customized for the default datasource as well as for every <>. -First, you need to add the datasource config to the `{config-file}` file +First, you need to add the datasource config to the `{config-file}` file in order to allow Flyway to manage the schema. Also, you can customize the Flyway behaviour by using the following properties: @@ -53,6 +53,10 @@ Also, you can customize the Flyway behaviour by using the following properties: **true** to execute Flyway automatically when the application starts, **false** otherwise. + **default:** false +`quarkus.flyway.validate-on-migrate`:: +**true** to validate the applied migrations against the available ones, **false** otherwise. + +**default:** true + `quarkus.flyway.clean-at-start`:: **true** to execute Flyway clean command automatically when the application starts, **false** otherwise. + **default:** false @@ -174,7 +178,7 @@ public class MigrationService { == Multiple datasources -Flyway can be configured for multiple datasources. +Flyway can be configured for multiple datasources. The Flyway properties are prefixed exactly the same way as the named datasources, for example: [source,properties] diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java index 0f0ea851eca87..b9557c88ac2a6 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayCreator.java @@ -6,6 +6,9 @@ import org.flywaydb.core.api.configuration.FluentConfiguration; class FlywayCreator { + + private static final String[] EMPTY_ARRAY = new String[0]; + private final FlywayDataSourceRuntimeConfig flywayRuntimeConfig; private final FlywayDataSourceBuildTimeConfig flywayBuildTimeConfig; @@ -19,16 +22,15 @@ public Flyway createFlyway(DataSource dataSource) { FluentConfiguration configure = Flyway.configure(); configure.dataSource(dataSource); flywayRuntimeConfig.connectRetries.ifPresent(configure::connectRetries); - flywayRuntimeConfig.schemas.ifPresent(list -> configure.schemas(list.toArray(new String[0]))); + flywayRuntimeConfig.schemas.ifPresent(list -> configure.schemas(list.toArray(EMPTY_ARRAY))); flywayRuntimeConfig.table.ifPresent(configure::table); - configure.locations(flywayBuildTimeConfig.locations.toArray(new String[0])); + configure.locations(flywayBuildTimeConfig.locations.toArray(EMPTY_ARRAY)); flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); - configure.baselineOnMigrate(flywayRuntimeConfig.baselineOnMigrate); + configure.validateOnMigrate(flywayRuntimeConfig.validateOnMigrate); flywayRuntimeConfig.baselineVersion.ifPresent(configure::baselineVersion); flywayRuntimeConfig.baselineDescription.ifPresent(configure::baselineDescription); - return configure.load(); } -} \ No newline at end of file +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java index dbf08839ae78f..4e240be1a9d30 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayDataSourceRuntimeConfig.java @@ -93,4 +93,10 @@ public static final FlywayDataSourceRuntimeConfig defaultConfig() { */ @ConfigItem public Optional baselineDescription = Optional.empty(); -} \ No newline at end of file + + /** + * Whether to automatically call validate when performing a migration. + */ + @ConfigItem + public boolean validateOnMigrate = true; +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index 251a8b144e56f..b6167ec37d340 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -45,12 +45,10 @@ public void doStartActions(FlywayRuntimeConfig config, BeanContainer container) } private void clean(BeanContainer container, AnnotationLiteral qualifier) { - Flyway flyway = container.instance(Flyway.class, qualifier); - flyway.clean(); + container.instance(Flyway.class, qualifier).clean(); } private void migrate(BeanContainer container, AnnotationLiteral qualifier) { - Flyway flyway = container.instance(Flyway.class, qualifier); - flyway.migrate(); + container.instance(Flyway.class, qualifier).migrate(); } -} \ No newline at end of file +} diff --git a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java index 174dc6f5781ce..3731e1b13d233 100644 --- a/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java +++ b/extensions/flyway/runtime/src/test/java/io/quarkus/flyway/runtime/FlywayCreatorTest.java @@ -15,6 +15,9 @@ import org.flywaydb.core.api.configuration.Configuration; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class FlywayCreatorTest { @@ -154,6 +157,24 @@ void testTableOverridden() { assertEquals(runtimeConfig.table.get(), createdFlywayConfig().getTable()); } + @Test + @DisplayName("validate on migrate default matches to true") + void testValidateOnMigrate() { + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(runtimeConfig.validateOnMigrate, createdFlywayConfig().isValidateOnMigrate()); + assertEquals(runtimeConfig.validateOnMigrate, true); + } + + @ParameterizedTest + @MethodSource("validateOnMigrateOverwritten") + @DisplayName("validate on migrate overwritten in configuration") + void testValidateOnMigrateOverwritten(final boolean input, final boolean expected) { + runtimeConfig.validateOnMigrate = input; + creator = new FlywayCreator(runtimeConfig, buildConfig); + assertEquals(createdFlywayConfig().isValidateOnMigrate(), expected); + assertEquals(runtimeConfig.validateOnMigrate, expected); + } + private static List pathList(Location[] locations) { return Stream.of(locations).map(Location::getPath).collect(Collectors.toList()); } @@ -161,4 +182,11 @@ private static List pathList(Location[] locations) { private Configuration createdFlywayConfig() { return creator.createFlyway(null).getConfiguration(); } + + private static Stream validateOnMigrateOverwritten() { + return Stream. builder() + .add(Arguments.arguments(false, false)) + .add(Arguments.arguments(true, true)) + .build(); + } } From c14a6bb095ba81969f013ed0149c2aac3b93dfde Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 18 Dec 2019 15:58:27 +1100 Subject: [PATCH 458/602] Add websockets dev mode test --- .../undertow-websockets/deployment/pom.xml | 5 ++ .../websockets/test/EchoWebSocket.java | 14 +++ .../test/WebsocketDevModeTestCase.java | 87 +++++++++++++++++++ .../common/http/TestHTTPResourceManager.java | 8 +- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/EchoWebSocket.java create mode 100644 extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/WebsocketDevModeTestCase.java diff --git a/extensions/undertow-websockets/deployment/pom.xml b/extensions/undertow-websockets/deployment/pom.xml index df5c0a559bb9a..8addcb1972e56 100644 --- a/extensions/undertow-websockets/deployment/pom.xml +++ b/extensions/undertow-websockets/deployment/pom.xml @@ -26,6 +26,11 @@ io.quarkus quarkus-undertow-websockets + + io.quarkus + quarkus-junit5-internal + test + diff --git a/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/EchoWebSocket.java b/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/EchoWebSocket.java new file mode 100644 index 0000000000000..374dc45f7807a --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/EchoWebSocket.java @@ -0,0 +1,14 @@ +package io.quarkus.undertow.websockets.test; + +import javax.websocket.OnMessage; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/echo") +public class EchoWebSocket { + + @OnMessage + String echo(String msg) { + return msg; + } + +} diff --git a/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/WebsocketDevModeTestCase.java b/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/WebsocketDevModeTestCase.java new file mode 100644 index 0000000000000..af2e5f531cfc7 --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/test/java/io/quarkus/undertow/websockets/test/WebsocketDevModeTestCase.java @@ -0,0 +1,87 @@ +package io.quarkus.undertow.websockets.test; + +import java.net.URI; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.ContainerProvider; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.http.TestHTTPResource; + +/** + * smoke test that websockets work as expected in dev mode + */ +public class WebsocketDevModeTestCase { + + @TestHTTPResource("echo") + URI echoUri; + + @RegisterExtension + public static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClass(EchoWebSocket.class); + } + }); + + @Test + public void testWebsocketHotReplacement() throws Exception { + + LinkedBlockingDeque message = new LinkedBlockingDeque<>(); + Session session = ContainerProvider.getWebSocketContainer().connectToServer(new Endpoint() { + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String s) { + message.add(s); + } + }); + session.getAsyncRemote().sendText("hello"); + } + }, ClientEndpointConfig.Builder.create().build(), echoUri); + + try { + Assertions.assertEquals("hello", message.poll(20, TimeUnit.SECONDS)); + } finally { + session.close(); + } + + test.modifySourceFile(EchoWebSocket.class, (s) -> s.replace("return msg;", "return \"changed:\" + msg;")); + + session = ContainerProvider.getWebSocketContainer().connectToServer(new Endpoint() { + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String s) { + message.add(s); + } + }); + session.getAsyncRemote().sendText("hello"); + } + }, ClientEndpointConfig.Builder.create().build(), echoUri); + + try { + Assertions.assertEquals("changed:hello", message.poll(20, TimeUnit.SECONDS)); + } finally { + session.close(); + } + + } +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java index 3eac43f4ab436..678f878d507ea 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java @@ -12,7 +12,13 @@ public class TestHTTPResourceManager { public static String getUri() { - return ConfigProvider.getConfig().getValue("test.url", String.class); + try { + return ConfigProvider.getConfig().getValue("test.url", String.class); + } catch (IllegalStateException e) { + //massive hack for dev mode tests, dev mode has not started yet + //so we don't have any way to load this correctly from config + return "http://localhost:8080"; + } } public static String getSslUri() { From 955b5f681b96a1c5dea2431cf1bdacdff5eef9ce Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 19 Dec 2019 12:39:50 +0200 Subject: [PATCH 459/602] Make application version and name available as runtime properties Fixes: #6255 Co-authored-by: Guillaume Smet --- .../deployment/steps/ApplicationInfoBuildStep.java | 2 +- .../src/main/java/io/quarkus/runner/RuntimeRunner.java | 1 + .../main/java/io/quarkus/runtime}/ApplicationConfig.java | 8 ++++---- .../maven/src/main/java/io/quarkus/maven/DevMojo.java | 6 ++++++ 4 files changed, 12 insertions(+), 5 deletions(-) rename core/{deployment/src/main/java/io/quarkus/deployment => runtime/src/main/java/io/quarkus/runtime}/ApplicationConfig.java (59%) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationInfoBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationInfoBuildStep.java index 116a48df28e4d..04202643e0055 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationInfoBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationInfoBuildStep.java @@ -1,8 +1,8 @@ package io.quarkus.deployment.steps; -import io.quarkus.deployment.ApplicationConfig; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.runtime.ApplicationConfig; public class ApplicationInfoBuildStep { diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java index cbb42ff10b7fc..1f81788a81db5 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java @@ -97,6 +97,7 @@ public void run() { builder.setRoot(target); builder.setClassLoader(loader); builder.setLaunchMode(launchMode); + builder.setBuildSystemProperties(buildSystemProperties); if (liveReloadState != null) { builder.setLiveReloadState(liveReloadState); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationConfig.java similarity index 59% rename from core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java rename to core/runtime/src/main/java/io/quarkus/runtime/ApplicationConfig.java index e430259613cdc..a8148e17cd7b1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ApplicationConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.deployment; +package io.quarkus.runtime; import java.util.Optional; @@ -6,19 +6,19 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.BUILD_TIME) +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public class ApplicationConfig { /** * The name of the application. - * If not set, defaults to the name of the project. + * If not set, defaults to the name of the project (except for tests where it is not set at all). */ @ConfigItem public Optional name; /** * The version of the application. - * If not set, defaults to the version of the project + * If not set, defaults to the version of the project (except for tests where it is not set at all). */ @ConfigItem public Optional version; diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 4243d6c1e7315..f0ee42197df22 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -490,7 +490,13 @@ void prepare() throws Exception { for (Map.Entry e : System.getProperties().entrySet()) { devModeContext.getSystemProperties().put(e.getKey().toString(), (String) e.getValue()); } + devModeContext.getBuildSystemProperties().putAll((Map) project.getProperties()); + + // this is a minor hack to allow ApplicationConfig to be populated with defaults + devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.name", project.getArtifactId()); + devModeContext.getBuildSystemProperties().putIfAbsent("quarkus.application.version", project.getVersion()); + devModeContext.setSourceEncoding(getSourceEncoding()); devModeContext.setSourceJavaVersion(source); devModeContext.setTargetJvmVersion(target); From 51550b5d27a545ef1956f3a5ca6b7ff43d2e571d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 20 Dec 2019 10:34:06 +0200 Subject: [PATCH 460/602] Add tests for application name and version handling --- .../it/config/ApplicationInfoResource.java | 24 ++++++++++++ .../src/main/resources/application.properties | 5 +++ .../it/main/ApplicationInfoTestCase.java | 19 +++++++++ .../it/ApplicationNameAndVersionTestUtil.java | 39 +++++++++++++++++++ .../java/io/quarkus/maven/it/DevMojoIT.java | 4 ++ .../java/io/quarkus/maven/it/JarRunnerIT.java | 4 ++ .../src/main/java/org/acme/HelloResource.java | 14 ++++++- 7 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 integration-tests/main/src/main/java/io/quarkus/it/config/ApplicationInfoResource.java create mode 100644 integration-tests/main/src/test/java/io/quarkus/it/main/ApplicationInfoTestCase.java create mode 100644 integration-tests/maven/src/test/java/io/quarkus/maven/it/ApplicationNameAndVersionTestUtil.java diff --git a/integration-tests/main/src/main/java/io/quarkus/it/config/ApplicationInfoResource.java b/integration-tests/main/src/main/java/io/quarkus/it/config/ApplicationInfoResource.java new file mode 100644 index 0000000000000..71f66aa1b129c --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/config/ApplicationInfoResource.java @@ -0,0 +1,24 @@ +package io.quarkus.it.config; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Path("/application-info") +public class ApplicationInfoResource { + + @ConfigProperty(name = "quarkus.application.version") + String applicationVersion; + + @ConfigProperty(name = "quarkus.application.name") + String applicationName; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return applicationName + "/" + applicationVersion; + } +} diff --git a/integration-tests/main/src/main/resources/application.properties b/integration-tests/main/src/main/resources/application.properties index 8ef8a9baa88ed..bd8c8310d2502 100644 --- a/integration-tests/main/src/main/resources/application.properties +++ b/integration-tests/main/src/main/resources/application.properties @@ -13,6 +13,11 @@ org.eclipse.microprofile.rest.client.propagateHeaders=header-name # we probably won't be able to test SSL proper so it seems reasonable to set it globally quarkus.ssl.native=false +# in tests 'quarkus.application.name' and `quarkus.application.version` do not get set from the build tool +# here we are testing that override from properties works +quarkus.application.name=main-integration-test +quarkus.application.version=1.0 + quarkus.datasource.url: ${datasource.url} quarkus.datasource.driver: ${datasource.driver} quarkus.datasource.username: ${datasource.username} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/ApplicationInfoTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ApplicationInfoTestCase.java new file mode 100644 index 0000000000000..c5c3bc2769e22 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ApplicationInfoTestCase.java @@ -0,0 +1,19 @@ +package io.quarkus.it.main; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +public class ApplicationInfoTestCase { + + @Test + public void testConfigPropertiesProperlyInjected() { + RestAssured + .when().get("/application-info") + .then().body(is("main-integration-test/1.0")); + } +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/ApplicationNameAndVersionTestUtil.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/ApplicationNameAndVersionTestUtil.java new file mode 100644 index 0000000000000..cfe605add7ba8 --- /dev/null +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/ApplicationNameAndVersionTestUtil.java @@ -0,0 +1,39 @@ +package io.quarkus.maven.it; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +final class ApplicationNameAndVersionTestUtil { + + private ApplicationNameAndVersionTestUtil() { + } + + // we don't use REST Assured because its bundled Groovy version clashes with Maven Invoker's (which is also used in this module) + static void assertApplicationPropertiesSetCorrectly() { + try { + URL url = new URL("http://localhost:8080/app/hello/nameAndVersion"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + // the default Accept header used by HttpURLConnection is not compatible with RESTEasy negotiation as it uses q=.2 + connection.setRequestProperty("Accept", "text/html, *; q=0.2, */*; q=0.2"); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + failApplicationPropertiesSetCorrectly(); + } + try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String output = br.readLine(); + assertThat(output).isEqualTo("acme/1.0-SNAPSHOT"); + } + } catch (IOException e) { + failApplicationPropertiesSetCorrectly(); + } + } + + private static void failApplicationPropertiesSetCorrectly() { + fail("Failed to assert that the application name and version were properly set"); + } +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 88d0601f9fc61..4c853cf9216f4 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -1,5 +1,6 @@ package io.quarkus.maven.it; +import static io.quarkus.maven.it.ApplicationNameAndVersionTestUtil.assertApplicationPropertiesSetCorrectly; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -42,7 +43,10 @@ public void testThatClassAppCanRun() throws MavenInvocationException, IOExceptio //make sure webjars work getHttpResponse("webjars/bootstrap/3.1.0/css/bootstrap.min.css"); + assertThatOutputWorksCorrectly(running.log()); + + assertApplicationPropertiesSetCorrectly(); } @Test diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java index 4d394c7d2f207..807cf5119799b 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -1,5 +1,6 @@ package io.quarkus.maven.it; +import static io.quarkus.maven.it.ApplicationNameAndVersionTestUtil.assertApplicationPropertiesSetCorrectly; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -55,6 +56,9 @@ public void testThatJarRunnerConsoleOutputWorksCorrectly() throws MavenInvocatio String logs = FileUtils.readFileToString(output, "UTF-8"); assertThatOutputWorksCorrectly(logs); + + // test that the application name and version are properly set + assertApplicationPropertiesSetCorrectly(); } finally { process.destroy(); } diff --git a/integration-tests/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java index 94d1aad900334..a82a1d8cbdb5b 100644 --- a/integration-tests/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java +++ b/integration-tests/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java @@ -11,10 +11,15 @@ @Path("/hello") public class HelloResource { - @Inject @ConfigProperty(name = "greeting") String greeting; + @ConfigProperty(name = "quarkus.application.version") + String applicationVersion; + + @ConfigProperty(name = "quarkus.application.name") + String applicationName; + @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { @@ -35,6 +40,13 @@ public String pkg() { return Blah.class.getPackage().getName(); } + @GET + @Path("/nameAndVersion") + @Produces(MediaType.TEXT_PLAIN) + public String nameAndVersion() { + return applicationName + "/" + applicationVersion; + } + public static class Blah { From 50dff9025fafc1e6b38f1632c85552d564a4b4fb Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 20 Dec 2019 14:45:02 +0200 Subject: [PATCH 461/602] Ensure that NativeIT is only run during when using native profile The other tests are only run when this profile is not active in order to avoid running them multiple times (since they are slow as well) Co-authored-by: Guillaume Smet --- ci-templates/stages.yml | 3 +- integration-tests/maven/pom.xml | 46 +++++++++++++++++++ .../io/quarkus/maven/it/AddExtensionIT.java | 1 + .../quarkus/maven/it/CreateProjectMojoIT.java | 1 + .../java/io/quarkus/maven/it/DevMojoIT.java | 1 + .../io/quarkus/maven/it/DisableForNative.java | 14 ++++++ .../io/quarkus/maven/it/EnableForNative.java | 14 ++++++ .../io/quarkus/maven/it/GenerateConfigIT.java | 1 + .../java/io/quarkus/maven/it/JarRunnerIT.java | 1 + .../io/quarkus/maven/it/NativeImageIT.java | 4 +- .../java/io/quarkus/maven/it/PackageIT.java | 1 + .../io/quarkus/maven/it/RemoteDevMojoIT.java | 1 + 12 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 integration-tests/maven/src/test/java/io/quarkus/maven/it/DisableForNative.java create mode 100644 integration-tests/maven/src/test/java/io/quarkus/maven/it/EnableForNative.java diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index af510eea19418..4adae7d9b9687 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -324,8 +324,9 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - timeoutInMinutes: 30 + timeoutInMinutes: 40 modules: + - maven - jackson - jsonb - jsch diff --git a/integration-tests/maven/pom.xml b/integration-tests/maven/pom.xml index 6925cffa4dd71..be6ded0e8833e 100644 --- a/integration-tests/maven/pom.xml +++ b/integration-tests/maven/pom.xml @@ -147,4 +147,50 @@ + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + ${maven.home} + ${settings.localRepository} + ${project.version} + + + true + + + + + + + + diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java index 09ae5218fa24b..73c1d815d2418 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/AddExtensionIT.java @@ -12,6 +12,7 @@ import org.apache.maven.shared.invoker.*; import org.junit.jupiter.api.Test; +@DisableForNative class AddExtensionIT extends QuarkusPlatformAwareMojoTestBase { private static final String QUARKUS_GROUPID = "io.quarkus"; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java index 55713acd02471..46d0851ebb2dd 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java @@ -26,6 +26,7 @@ /** * @author Clement Escoffier */ +@DisableForNative public class CreateProjectMojoIT extends QuarkusPlatformAwareMojoTestBase { private Invoker invoker; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 4c853cf9216f4..4d62c7480a9b4 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -30,6 +30,7 @@ * * mvn install -Dit.test=DevMojoIT#methodName */ +@DisableForNative public class DevMojoIT extends RunAndCheckMojoTestBase { @Test diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DisableForNative.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DisableForNative.java new file mode 100644 index 0000000000000..e3ac8ae2d2b75 --- /dev/null +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DisableForNative.java @@ -0,0 +1,14 @@ +package io.quarkus.maven.it; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@DisabledIfSystemProperty(named = "quarkus.test.native", matches = "true") +public @interface DisableForNative { +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/EnableForNative.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/EnableForNative.java new file mode 100644 index 0000000000000..c650f5b74ce49 --- /dev/null +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/EnableForNative.java @@ -0,0 +1,14 @@ +package io.quarkus.maven.it; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@EnabledIfSystemProperty(named = "quarkus.test.native", matches = "true") +public @interface EnableForNative { +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java index 8f497863575a3..93ef550df8e3b 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +@DisableForNative class GenerateConfigIT extends QuarkusPlatformAwareMojoTestBase { private static final String PROJECT_SOURCE_DIR = "projects/classic"; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java index 807cf5119799b..c6c18fd7dabe8 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -22,6 +22,7 @@ import io.quarkus.maven.it.verifier.RunningInvoker; import io.quarkus.utilities.JavaBinFinder; +@DisableForNative public class JarRunnerIT extends MojoTestBase { @Test diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeImageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeImageIT.java index 424482e4970d7..413fd282d4256 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeImageIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/NativeImageIT.java @@ -20,9 +20,7 @@ import io.quarkus.maven.it.verifier.MavenProcessInvocationResult; import io.quarkus.maven.it.verifier.RunningInvoker; -/** - * - */ +@EnableForNative public class NativeImageIT extends MojoTestBase { /** diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java index 22e0e04e8ce44..523aacd4f74ee 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/PackageIT.java @@ -28,6 +28,7 @@ import io.quarkus.maven.it.verifier.MavenProcessInvocationResult; import io.quarkus.maven.it.verifier.RunningInvoker; +@DisableForNative public class PackageIT extends MojoTestBase { private RunningInvoker running; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java index 7e406857ec62e..c56b3c93fc410 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java @@ -24,6 +24,7 @@ /** * @author Clement Escoffier */ +@DisableForNative public class RemoteDevMojoIT extends RunAndCheckWithAgentMojoTestBase { @Test From 00e604e2da806d2e8830af09f049877d77de8400 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 31 Dec 2019 11:15:04 +0100 Subject: [PATCH 462/602] fix: DynamicProxySupport is not thread safe and Neo4j doesn't work in native mode The issues are fixed by updating GraalVM version to 19.3.0.2 Fixes https://github.com/quarkusio/quarkus/issues/6115 --- bom/runtime/pom.xml | 2 +- .../java/io/quarkus/deployment/pkg/NativeConfig.java | 2 +- .../deployment/pkg/steps/NativeImageBuildStep.java | 11 ++++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 61623bf604429..58a7a6607e797 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -74,7 +74,7 @@ 3.5.2 1.7.1 - 19.3.0 + 19.3.0.2 1.0.0.Final 2.9.10.20191020 1.0.0.Final diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index 3ede83fb7f76e..3858195aa5b71 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -132,7 +132,7 @@ public class NativeConfig { /** * The docker image to use to do the image build */ - @ConfigItem(defaultValue = "quay.io/quarkus/ubi-quarkus-native-image:19.3.0-java8") + @ConfigItem(defaultValue = "quay.io/quarkus/ubi-quarkus-native-image:19.3.0.2-java8") public String builderImage; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index ddd4be32da107..8777a8ff9a319 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -204,11 +204,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa command.add("-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time command.add("-jar"); command.add(runnerJarName); - //dynamic proxy generation is not thread safe - //should be fixed in 19.3.1 - //but we need to add this option back to prevent intermittent failures - //https://github.com/oracle/graal/issues/1927 - command.add("-J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1"); + if (nativeConfig.enableFallbackImages) { command.add("-H:FallbackThreshold=5"); } else { @@ -327,10 +323,11 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa private void checkGraalVMVersion(String version) { log.info("Running Quarkus native-image plugin on " + version); final List obsoleteGraalVmVersions = Arrays.asList("1.0.0", "19.0.", "19.1.", "19.2."); - final boolean vmVersionIsObsolete = obsoleteGraalVmVersions.stream().anyMatch(v -> version.contains(" " + v)); + final boolean vmVersionIsObsolete = version.contains(" 19.3.0 ") + || obsoleteGraalVmVersions.stream().anyMatch(v -> version.contains(" " + v)); if (vmVersionIsObsolete) { throw new IllegalStateException( - "Out of date build of GraalVM detected: " + version + ". Please upgrade to GraalVM 19.3.0."); + "Out of date build of GraalVM detected: " + version + ". Please upgrade to GraalVM 19.3.0.2"); } } From 7f3324162fbbdc98926835af73f59250e63991ce Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Tue, 31 Dec 2019 11:25:56 +0100 Subject: [PATCH 463/602] ci(neo4j): add neo4j tests in CI --- ci-templates/jvm-build-steps.yaml | 7 +++++-- ci-templates/native-build-steps.yaml | 6 +++++- ci-templates/stages.yml | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ci-templates/jvm-build-steps.yaml b/ci-templates/jvm-build-steps.yaml index 0cde1f96232cd..f11f137b9ef4d 100644 --- a/ci-templates/jvm-build-steps.yaml +++ b/ci-templates/jvm-build-steps.yaml @@ -17,7 +17,10 @@ steps: - script: docker run --rm --publish 8000:8000 --name build-dynamodb -d amazon/dynamodb-local:1.11.477 displayName: 'start dynamodb' - + +- script: docker run --rm --publish 7687:7687 --name build-neo4j -e NEO4J_AUTH=neo4j/secret -e NEO4J_dbms_memory_pagecache_size=10M -e NEO4J_dbms_memory_heap_initial__size=10M -d neo4j/neo4j-experimental:4.0.0-rc01 + displayName: 'start neo4j' + - bash: | sudo service mysql stop || true docker run --rm --publish 3306:3306 --name build-mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_RANDOM_ROOT_PASSWORD=true -e MYSQL_DATABASE=hibernate_orm_test -d mysql:5 --skip-ssl @@ -29,5 +32,5 @@ steps: goals: 'install' mavenOptions: $(MAVEN_OPTS) jdkVersionOption: ${{ parameters.jdk }} - options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-mysql -Dtest-dynamodb -Dtest-vault -Dno-format ${{ parameters.extraf }}' + options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-mysql -Dtest-dynamodb -Dtest-vault -Dtest-neo4j -Dno-format ${{ parameters.extraf }}' diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 45773ff6e7e99..b4b3a8efbb45d 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -7,6 +7,7 @@ parameters: dynamodb: false keycloak: false mysql: false + neo4j: false timeoutInMinutes: 80 @@ -39,6 +40,9 @@ jobs: - ${{ if eq(parameters.keycloak, 'true') }}: - script: docker run --rm --publish 8180:8080 --name build-keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e JAVA_OPTS="-server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dkeycloak.profile.feature.upload_scripts=enabled" -d quay.io/keycloak/keycloak:8.0.1 displayName: 'start keycloak' + - ${{ if eq(parameters.neo4j, 'true') }}: + - script: docker run --rm --publish 7687:7687 --name build-neo4j -e NEO4J_AUTH=neo4j/secret -e NEO4J_dbms_memory_pagecache_size=10M -e NEO4J_dbms_memory_heap_initial__size=10M -d neo4j/neo4j-experimental:4.0.0-rc01 + displayName: 'start neo4j' - ${{ if eq(parameters.mysql, 'true') }}: - bash: | sudo service mysql stop || true @@ -50,4 +54,4 @@ jobs: inputs: goals: 'install' mavenOptions: $(MAVEN_OPTS) - options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' + options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dtest-neo4j -Dnative-image.xmx=6g -Dnative -Dno-format' diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 4adae7d9b9687..67d74d42526b0 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -296,6 +296,7 @@ stages: - mongodb-panache - neo4j name: data_4 + neo4j: true - template: native-build-steps.yaml parameters: From 5fbdafaade475deb6217ab7cd5b650ab889f59e8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Jan 2020 13:59:39 +0100 Subject: [PATCH 464/602] Remove substitutions incorporated into GraalVM 19.3.0.2 --- .../graal/BootLoaderSubstitutions.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java deleted file mode 100644 index ab1a20a75257c..0000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/graal/BootLoaderSubstitutions.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.quarkus.runtime.graal; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Enumeration; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.hub.ClassForNameSupport; -import com.oracle.svm.core.jdk.JDK11OrLater; - -@TargetClass(className = "jdk.internal.loader.BootLoader", onlyWith = JDK11OrLater.class) -final class Target_jdk_internal_loader_BootLoader { - - /* - * The following substitution is required to work around a NPE that happened when `BootLoader.loadClassOrNull(name)` was - * called during the Quarkus native integration tests execution. It will be directly fixed in a future GraalVM release - * which is undetermined for now. This substitution should be removed as soon as Quarkus depends on a GraalVM version that - * includes the fix. - */ - @Substitute - private static Class loadClassOrNull(String name) { - return ClassForNameSupport.forNameOrNull(name, false); - } - - /* - * The following substitution is required to work around a NPE that happened when `BootLoader.hasClassPath()` was called - * during the Quarkus native integration tests execution. It was fixed in a GraalVM commit which should be backported to - * 19.3.1 (https://github.com/oracle/graal/commit/68027de). This substitution should be removed as soon as Quarkus depends - * on a GraalVM version that includes that commit. - * See https://github.com/oracle/graal/issues/1966 for more details about the NPE. - */ - @Substitute - private static boolean hasClassPath() { - return true; - } - - @Substitute - private static URL findResource(String mn, String name) { - return ClassLoader.getSystemClassLoader().getResource(name); - } - - @Substitute - private static InputStream findResourceAsStream(String mn, String name) { - return ClassLoader.getSystemClassLoader().getResourceAsStream(name); - } - - @Substitute - private static URL findResource(String name) { - return ClassLoader.getSystemClassLoader().getResource(name); - } - - @Substitute - private static Enumeration findResources(String name) throws IOException { - return ClassLoader.getSystemClassLoader().getResources(name); - } -} From 548f08317be1fd941e475bd57672e3318f90a47d Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 2 Jan 2020 13:39:15 +0100 Subject: [PATCH 465/602] Upgrade Jackson to 2.10.1 --- bom/runtime/pom.xml | 2 +- extensions/kubernetes-client/runtime/pom.xml | 4 ++++ tcks/microprofile-opentracing/base/pom.xml | 10 +++++++++- test-framework/kubernetes-client/pom.xml | 4 ++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 58a7a6607e797..8cb9ab57fc103 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -76,7 +76,7 @@ 19.3.0.2 1.0.0.Final - 2.9.10.20191020 + 2.10.1 1.0.0.Final 3.9 1.13 diff --git a/extensions/kubernetes-client/runtime/pom.xml b/extensions/kubernetes-client/runtime/pom.xml index 78b66d499d4ea..9a352b5b456f6 100644 --- a/extensions/kubernetes-client/runtime/pom.xml +++ b/extensions/kubernetes-client/runtime/pom.xml @@ -34,6 +34,10 @@ javax.annotation javax.annotation-api + + jakarta.xml.bind + jakarta.xml.bind-api + javax.xml.bind jaxb-api diff --git a/tcks/microprofile-opentracing/base/pom.xml b/tcks/microprofile-opentracing/base/pom.xml index 6cd1cd637bd49..3b270caef9b40 100644 --- a/tcks/microprofile-opentracing/base/pom.xml +++ b/tcks/microprofile-opentracing/base/pom.xml @@ -93,8 +93,16 @@ org.jboss.resteasy resteasy-jackson-provider + + jakarta.xml.bind + jakarta.xml.bind-api + + + org.jboss.spec.javax.xml.bind + jboss-jaxb-api_2.3_spec + org.jboss.shrinkwrap.resolver shrinkwrap-resolver-impl-maven @@ -182,4 +190,4 @@ - \ No newline at end of file + diff --git a/test-framework/kubernetes-client/pom.xml b/test-framework/kubernetes-client/pom.xml index d02fe6112316c..1a33c0fb407c6 100644 --- a/test-framework/kubernetes-client/pom.xml +++ b/test-framework/kubernetes-client/pom.xml @@ -30,6 +30,10 @@ javax.annotation javax.annotation-api + + jakarta.xml.bind + jakarta.xml.bind-api + javax.xml.bind jaxb-api From a1e3350c3aabcc2a975e4a756a84f6cc74028da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Mon, 30 Dec 2019 14:51:57 +0100 Subject: [PATCH 466/602] Provides additional configuration options for logging-gelf --- .../io/quarkus/logging/gelf/GelfConfig.java | 31 +++++++++++++++++++ .../logging/gelf/GelfLogHandlerRecorder.java | 9 +++++- .../src/main/resources/application.properties | 5 ++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java index c5690a6c32c7b..59a3b7f9332ba 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java @@ -1,5 +1,8 @@ package io.quarkus.logging.gelf; +import java.util.Optional; +import java.util.logging.Level; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -61,4 +64,32 @@ public class GelfConfig { */ @ConfigItem(defaultValue = "yyyy-MM-dd HH:mm:ss,SSS") public String timestampPattern; + + /** + * The logging-gelf log level. + */ + @ConfigItem(defaultValue = "ALL") + Level level; + + /** + * Name of the Facility. + */ + @ConfigItem(defaultValue = "jboss-logmanager") + String facility; + + /** + * Post additional fields. E.g. `fieldName1=value1,fieldName2=value2`. + */ + @ConfigItem + Optional additionalFields; + + /** + * Type specification for additional and MDC fields. + * Supported types: String, long, Long, double, Double and discover (default if not specified, discover field type on + * parseability). + * E.g. `field=String,field2=double`. + */ + @ConfigItem + Optional additionalFieldsTypes; + } diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java index f309f52dc7865..f4b1213a53a7c 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java @@ -16,7 +16,7 @@ public RuntimeValue> initializeHandler(final GelfConfig config final JBoss7GelfLogHandler handler = new JBoss7GelfLogHandler(); handler.setVersion(config.version); - handler.setFacility("jboss-logmanager"); + handler.setFacility(config.facility); String extractStackTrace = String.valueOf(config.extractStackTrace); if (config.extractStackTrace && config.stackTraceThrowableReference != 0) { extractStackTrace = String.valueOf(config.stackTraceThrowableReference); @@ -26,6 +26,13 @@ public RuntimeValue> initializeHandler(final GelfConfig config handler.setTimestampPattern(config.timestampPattern); handler.setHost(config.host); handler.setPort(config.port); + handler.setLevel(config.level); + if (config.additionalFields.isPresent()) { + handler.setAdditionalFields(config.additionalFields.get()); + } + if (config.additionalFieldsTypes.isPresent()) { + handler.setAdditionalFieldTypes(config.additionalFieldsTypes.get()); + } return new RuntimeValue<>(Optional.of(handler)); } } diff --git a/integration-tests/logging-gelf/src/main/resources/application.properties b/integration-tests/logging-gelf/src/main/resources/application.properties index 1a3bf92b4b2d5..2cd32a41b5b38 100644 --- a/integration-tests/logging-gelf/src/main/resources/application.properties +++ b/integration-tests/logging-gelf/src/main/resources/application.properties @@ -1,4 +1,7 @@ # Configure loggers quarkus.log.handler.gelf.enabled=true quarkus.log.handler.gelf.host=localhost -quarkus.log.handler.gelf.port=12201 \ No newline at end of file +quarkus.log.handler.gelf.port=12201 +quarkus.log.handler.gelf.facility=custom +quarkus.log.handler.gelf.additional-fields=field1=value,field2=1 +quarkus.log.handler.gelf.additional-fields-types=field1=String,field2=long \ No newline at end of file From 4efeae0ce1424da0ef22e64b86cb4994a020e4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Fri, 3 Jan 2020 17:40:48 +0100 Subject: [PATCH 467/602] Configure additional fields with a Map --- .../logging/gelf/AdditionalFieldConfig.java | 24 ++++++++++++++ .../io/quarkus/logging/gelf/GelfConfig.java | 33 ++++++++++--------- .../logging/gelf/GelfLogHandlerRecorder.java | 26 ++++++++++++--- .../src/main/resources/application.properties | 6 ++-- 4 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/AdditionalFieldConfig.java diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/AdditionalFieldConfig.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/AdditionalFieldConfig.java new file mode 100644 index 0000000000000..70a852bcba223 --- /dev/null +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/AdditionalFieldConfig.java @@ -0,0 +1,24 @@ +package io.quarkus.logging.gelf; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Post additional fields. E.g. `fieldName1=value1,fieldName2=value2`. + */ +@ConfigGroup +public class AdditionalFieldConfig { + /** + * Additional field value. + */ + @ConfigItem + public String value; + + /** + * Additional field type specification. + * Supported types: String, long, Long, double, Double and discover. + * Discover is the default if not specified, it discovers field type based on parseability. + */ + @ConfigItem(defaultValue = "discover") + public String type; +} diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java index 59a3b7f9332ba..08ca1cb78bf7d 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java @@ -1,8 +1,10 @@ package io.quarkus.logging.gelf; -import java.util.Optional; +import java.util.Map; import java.util.logging.Level; +import io.quarkus.runtime.annotations.ConfigDocMapKey; +import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -13,7 +15,7 @@ public class GelfConfig { * Determine whether to enable the GELF logging handler */ @ConfigItem - boolean enabled; + public boolean enabled; /** * Hostname/IP-Address of the Logstash/Graylog Host @@ -69,27 +71,26 @@ public class GelfConfig { * The logging-gelf log level. */ @ConfigItem(defaultValue = "ALL") - Level level; + public Level level; /** - * Name of the Facility. + * Name of the facility. */ @ConfigItem(defaultValue = "jboss-logmanager") - String facility; + public String facility; /** - * Post additional fields. E.g. `fieldName1=value1,fieldName2=value2`. - */ - @ConfigItem - Optional additionalFields; - - /** - * Type specification for additional and MDC fields. - * Supported types: String, long, Long, double, Double and discover (default if not specified, discover field type on - * parseability). - * E.g. `field=String,field2=double`. + * Post additional fields. + * You can add static fields to each log event in the following form: + * + *
+     * quarkus.log.handler.gelf.additional-field.field1.value=value1
+     * quarkus.log.handler.gelf.additional-field.field1.type=String
+     * 
*/ @ConfigItem - Optional additionalFieldsTypes; + @ConfigDocMapKey("field-name") + @ConfigDocSection + public Map additionalField; } diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java index f4b1213a53a7c..49530b6506ee6 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java @@ -1,5 +1,6 @@ package io.quarkus.logging.gelf; +import java.util.Map; import java.util.Optional; import java.util.logging.Handler; @@ -27,12 +28,27 @@ public RuntimeValue> initializeHandler(final GelfConfig config handler.setHost(config.host); handler.setPort(config.port); handler.setLevel(config.level); - if (config.additionalFields.isPresent()) { - handler.setAdditionalFields(config.additionalFields.get()); - } - if (config.additionalFieldsTypes.isPresent()) { - handler.setAdditionalFieldTypes(config.additionalFieldsTypes.get()); + + // handle additional fields + if (!config.additionalField.isEmpty()) { + StringBuilder additionalFieldsValue = new StringBuilder(); + StringBuilder additionalFieldsType = new StringBuilder(); + for (Map.Entry additionalField : config.additionalField.entrySet()) { + if (additionalFieldsValue.length() > 0) { + additionalFieldsValue.append(','); + } + additionalFieldsValue.append(additionalField.getKey()).append('=').append(additionalField.getValue().value); + + if (additionalFieldsType.length() > 0) { + additionalFieldsType.append(','); + } + additionalFieldsType.append(additionalField.getKey()).append('=').append(additionalField.getValue().type); + } + + handler.setAdditionalFields(additionalFieldsValue.toString()); + handler.setAdditionalFieldTypes(additionalFieldsType.toString()); } + return new RuntimeValue<>(Optional.of(handler)); } } diff --git a/integration-tests/logging-gelf/src/main/resources/application.properties b/integration-tests/logging-gelf/src/main/resources/application.properties index 2cd32a41b5b38..59759a7e82150 100644 --- a/integration-tests/logging-gelf/src/main/resources/application.properties +++ b/integration-tests/logging-gelf/src/main/resources/application.properties @@ -3,5 +3,7 @@ quarkus.log.handler.gelf.enabled=true quarkus.log.handler.gelf.host=localhost quarkus.log.handler.gelf.port=12201 quarkus.log.handler.gelf.facility=custom -quarkus.log.handler.gelf.additional-fields=field1=value,field2=1 -quarkus.log.handler.gelf.additional-fields-types=field1=String,field2=long \ No newline at end of file +quarkus.log.handler.gelf.additional-field.field1.value=value +quarkus.log.handler.gelf.additional-field.field1.type=String +quarkus.log.handler.gelf.additional-field.field2.value=666 +quarkus.log.handler.gelf.additional-field.field2.type=long \ No newline at end of file From 177818e16e9a0863ec67d9c03285a31b84fe347f Mon Sep 17 00:00:00 2001 From: Bruno Devaux Date: Thu, 2 Jan 2020 23:44:48 +0100 Subject: [PATCH 468/602] enable hot reload on application.yaml --- .../config/yaml/deployment/ConfigYamlProcessor.java | 7 +++++++ .../config/yaml/runtime/ApplicationYamlProvider.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java b/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java index 60b5803880c86..025d1539976ce 100644 --- a/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java +++ b/extensions/config-yaml/deployment/src/main/java/io/quarkus/config/yaml/deployment/ConfigYamlProcessor.java @@ -1,7 +1,9 @@ package io.quarkus.config.yaml.deployment; +import io.quarkus.config.yaml.runtime.ApplicationYamlProvider; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; public final class ConfigYamlProcessor { @@ -9,4 +11,9 @@ public final class ConfigYamlProcessor { public FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.CONFIG_YAML); } + + @BuildStep + HotDeploymentWatchedFileBuildItem watchYamlConfig() { + return new HotDeploymentWatchedFileBuildItem(ApplicationYamlProvider.APPLICATION_YAML); + } } diff --git a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java index cfc7c03f06373..3605f0fe2f8ba 100644 --- a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java +++ b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlProvider.java @@ -23,7 +23,7 @@ */ public final class ApplicationYamlProvider implements ConfigSourceProvider { - static final String APPLICATION_YAML = "application.yaml"; + public static final String APPLICATION_YAML = "application.yaml"; @Override public Iterable getConfigSources(final ClassLoader forClassLoader) { From bbaefedc6b3e75664b2824087b6b63354c48e459 Mon Sep 17 00:00:00 2001 From: Bruno Devaux Date: Sat, 4 Jan 2020 13:50:04 +0100 Subject: [PATCH 469/602] add application.yaml hot reload automated test --- extensions/config-yaml/deployment/pom.xml | 16 +++++++++ .../ApplicationYamlHotDeploymentTest.java | 34 +++++++++++++++++++ .../config/yaml/deployment/FooResource.java | 22 ++++++++++++ .../src/test/resources/application.yaml | 2 ++ 4 files changed, 74 insertions(+) create mode 100644 extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/ApplicationYamlHotDeploymentTest.java create mode 100644 extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/FooResource.java create mode 100644 extensions/config-yaml/deployment/src/test/resources/application.yaml diff --git a/extensions/config-yaml/deployment/pom.xml b/extensions/config-yaml/deployment/pom.xml index 4f1e1ebd315eb..727470f3de722 100644 --- a/extensions/config-yaml/deployment/pom.xml +++ b/extensions/config-yaml/deployment/pom.xml @@ -23,6 +23,22 @@ io.quarkus quarkus-config-yaml
+ + + io.quarkus + quarkus-resteasy-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test +
diff --git a/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/ApplicationYamlHotDeploymentTest.java b/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/ApplicationYamlHotDeploymentTest.java new file mode 100644 index 0000000000000..4f86ed8fd5a1b --- /dev/null +++ b/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/ApplicationYamlHotDeploymentTest.java @@ -0,0 +1,34 @@ +package io.quarkus.config.yaml.deployment; + +import static io.quarkus.config.yaml.runtime.ApplicationYamlProvider.APPLICATION_YAML; +import static org.hamcrest.Matchers.is; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class ApplicationYamlHotDeploymentTest { + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(APPLICATION_YAML) + .addClass(FooResource.class)); + + @Test + public void testConfigReload() { + RestAssured.when().get("/foo").then() + .statusCode(200) + .body(is("AAAA")); + + test.modifyResourceFile(APPLICATION_YAML, s -> s.replace("AAAA", "BBBB")); + + RestAssured.when().get("/foo").then() + .statusCode(200) + .body(is("BBBB")); + } +} diff --git a/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/FooResource.java b/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/FooResource.java new file mode 100644 index 0000000000000..d07c09d18b7e9 --- /dev/null +++ b/extensions/config-yaml/deployment/src/test/java/io/quarkus/config/yaml/deployment/FooResource.java @@ -0,0 +1,22 @@ +package io.quarkus.config.yaml.deployment; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Path("/") +public class FooResource { + + @ConfigProperty(name = "foo.bar") + String fooBar; + + @Path("foo") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String get() { + return fooBar; + } +} diff --git a/extensions/config-yaml/deployment/src/test/resources/application.yaml b/extensions/config-yaml/deployment/src/test/resources/application.yaml new file mode 100644 index 0000000000000..9ef11adb25415 --- /dev/null +++ b/extensions/config-yaml/deployment/src/test/resources/application.yaml @@ -0,0 +1,2 @@ +foo: + bar: AAAA \ No newline at end of file From 57cb57e7a9abd10aad33e22294f6a3a89acea24b Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 20 Dec 2019 12:23:55 +0200 Subject: [PATCH 470/602] Bump RESTEasy to 4.4.2.Final --- bom/runtime/pom.xml | 2 +- docs/src/main/asciidoc/rest-json.adoc | 6 +-- .../deployment/RestClientProcessor.java | 5 --- .../runtime/IncomingHeadersProvider.java | 37 ------------------- ...file.client.header.IncomingHeadersProvider | 1 - 5 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/IncomingHeadersProvider.java delete mode 100644 extensions/rest-client/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 8cb9ab57fc103..51ce5bd477b2b 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -16,7 +16,7 @@ 1.11 2.1.2.Final - 4.4.1.Final + 4.4.2.Final 0.31.0 0.4.1 0.2.3 diff --git a/docs/src/main/asciidoc/rest-json.adoc b/docs/src/main/asciidoc/rest-json.adoc index 9609042b14eed..01da7cc416c5a 100644 --- a/docs/src/main/asciidoc/rest-json.adoc +++ b/docs/src/main/asciidoc/rest-json.adoc @@ -468,9 +468,9 @@ In Quarkus, RESTEasy can either run directly on top of the Vert.x HTTP server, o As a result, certain classes, such as `HttpServletRequest` are not always available for injection. Most use-cases for this particular class are covered by JAX-RS equivalents, except for getting the remote client's IP. RESTEasy comes with a replacement API which you can inject: -https://docs.jboss.org/resteasy/docs/4.4.1.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html[`HttpRequest`], which has the methods -https://docs.jboss.org/resteasy/docs/4.4.1.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html#getRemoteAddress--[`getRemoteAddress()`] -and https://docs.jboss.org/resteasy/docs/4.4.1.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html#getRemoteHost--[`getRemoteHost()`] +https://docs.jboss.org/resteasy/docs/4.4.2.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html[`HttpRequest`], which has the methods +https://docs.jboss.org/resteasy/docs/4.4.2.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html#getRemoteAddress--[`getRemoteAddress()`] +and https://docs.jboss.org/resteasy/docs/4.4.2.Final/javadocs/org/jboss/resteasy/spi/HttpRequest.html#getRemoteHost--[`getRemoteHost()`] to solve this problem. == What's Different from Jakarta EE Development diff --git a/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index c66bc2f82bb16..509e8c22cf34e 100644 --- a/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -64,7 +64,6 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; -import io.quarkus.restclient.runtime.IncomingHeadersProvider; import io.quarkus.restclient.runtime.RestClientBase; import io.quarkus.restclient.runtime.RestClientRecorder; import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem; @@ -164,10 +163,6 @@ void processInterfaces(CombinedIndexBuildItem combinedIndexBuildItem, // Incoming headers // required for the non-arg constructor of DCHFImpl to be included in the native image reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, DefaultClientHeadersFactoryImpl.class.getName())); - serviceProvider - .produce(new ServiceProviderBuildItem( - org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider.class.getName(), - IncomingHeadersProvider.class.getName())); // Register Interface return types for reflection for (Type returnType : returnTypes) { diff --git a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/IncomingHeadersProvider.java b/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/IncomingHeadersProvider.java deleted file mode 100644 index 0097533bbdbb3..0000000000000 --- a/extensions/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/IncomingHeadersProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.quarkus.restclient.runtime; - -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.MultivaluedMap; - -import org.jboss.resteasy.specimpl.UnmodifiableMultivaluedMap; -import org.jboss.resteasy.spi.HttpRequest; -import org.jboss.resteasy.spi.ResteasyProviderFactory; - -/** - * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com - *
- * Date: 2/28/19 - */ -public class IncomingHeadersProvider implements org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider { - public static final UnmodifiableMultivaluedMap EMPTY_MAP = new UnmodifiableMultivaluedMap<>( - new MultivaluedHashMap<>()); - - /** - * @return headers incoming in the JAX-RS request, if any - */ - @Override - public MultivaluedMap getIncomingHeaders() { - MultivaluedMap result = null; - - ResteasyProviderFactory providerFactory = ResteasyProviderFactory.peekInstance(); - if (providerFactory != null) { - HttpRequest request = (HttpRequest) providerFactory.getContextData(HttpRequest.class); - if (request != null) { - result = request.getHttpHeaders().getRequestHeaders(); - } - } - return result == null - ? EMPTY_MAP - : result; - } -} \ No newline at end of file diff --git a/extensions/rest-client/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider b/extensions/rest-client/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider deleted file mode 100644 index e9fcbc61ce3ad..0000000000000 --- a/extensions/rest-client/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.microprofile.client.header.IncomingHeadersProvider +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.restclient.runtime.IncomingHeadersProvider \ No newline at end of file From 3bcad0021bef4b4eb47b968eb96af839186ea733 Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sun, 5 Jan 2020 11:11:30 +0100 Subject: [PATCH 471/602] Typo fix in Neo4j guide --- docs/src/main/asciidoc/neo4j.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 4c1148799cf60..7b1038b3e35c4 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -284,9 +284,9 @@ public CompletionStage create(Fruit fruit) { ) .thenCompose(fn -> fn.singleAsync()) .thenApply(record -> Fruit.from(record.get("f").asNode())) - .thenCompose(persistedFruid -> session.closeAsync().thenApply(signal -> persistedFruid)) - .thenApply(persistedFruid -> Response - .created(URI.create("/fruits/" + persistedFruid.id)) + .thenCompose(persistedFruit -> session.closeAsync().thenApply(signal -> persistedFruit)) + .thenApply(persistedFruit -> Response + .created(URI.create("/fruits/" + persistedFruit.id)) .build() ); } From 23e6d788202a69a7f4d7b1f7097258d6a2343f11 Mon Sep 17 00:00:00 2001 From: Bruno Devaux Date: Sat, 4 Jan 2020 23:04:54 +0100 Subject: [PATCH 472/602] add the ability to include full MDC in logging-gelf extension configuration --- .../src/main/java/io/quarkus/logging/gelf/GelfConfig.java | 5 +++++ .../java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java | 1 + 2 files changed, 6 insertions(+) diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java index 08ca1cb78bf7d..906663f733ff2 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfConfig.java @@ -93,4 +93,9 @@ public class GelfConfig { @ConfigDocSection public Map additionalField; + /** + * Whether to include all fields from the MDC. + */ + @ConfigItem(defaultValue = "false") + public boolean includeFullMdc; } diff --git a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java index 49530b6506ee6..0c845cb03eb05 100644 --- a/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java +++ b/extensions/logging-gelf/runtime/src/main/java/io/quarkus/logging/gelf/GelfLogHandlerRecorder.java @@ -25,6 +25,7 @@ public RuntimeValue> initializeHandler(final GelfConfig config handler.setExtractStackTrace(extractStackTrace); handler.setFilterStackTrace(config.filterStackTrace); handler.setTimestampPattern(config.timestampPattern); + handler.setIncludeFullMdc(config.includeFullMdc); handler.setHost(config.host); handler.setPort(config.port); handler.setLevel(config.level); From 6dd4ee5b764075642809f94b8b69c4345c187c57 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 5 Jan 2020 15:59:42 +0100 Subject: [PATCH 473/602] Upgrade Jackson to 2.10.2 --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 51ce5bd477b2b..1cc552260e75d 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -76,7 +76,7 @@ 19.3.0.2 1.0.0.Final - 2.10.1 + 2.10.2 1.0.0.Final 3.9 1.13 From 46e02ca28c4575824403efabe362c5e0dc16e26f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Sun, 5 Jan 2020 16:35:48 +0100 Subject: [PATCH 474/602] Take into account the web.xml MIME mappings in the Undertow extension Fixes #6399 --- .../undertow/deployment/UndertowBuildStep.java | 8 ++++++++ .../undertow/test/ServletWebXmlTestCase.java | 17 +++++++++++++++-- .../runtime/UndertowDeploymentRecorder.java | 6 ++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index 015f199cf4015..e16605c3b0be1 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -66,6 +66,7 @@ import org.jboss.metadata.web.spec.FiltersMetaData; import org.jboss.metadata.web.spec.HttpMethodConstraintMetaData; import org.jboss.metadata.web.spec.ListenerMetaData; +import org.jboss.metadata.web.spec.MimeMappingMetaData; import org.jboss.metadata.web.spec.MultipartConfigMetaData; import org.jboss.metadata.web.spec.SecurityConstraintMetaData; import org.jboss.metadata.web.spec.ServletMappingMetaData; @@ -463,6 +464,13 @@ public ServletDeploymentManagerBuildItem build(List servlets, } } + // MIME mappings + if (webMetaData.getMimeMappings() != null) { + for (MimeMappingMetaData mimeMapping : webMetaData.getMimeMappings()) { + recorder.addMimeMapping(deployment, mimeMapping.getExtension(), mimeMapping.getMimeType()); + } + } + for (ServletBuildItem servlet : servlets) { String servletClass = servlet.getServletClass(); if (servlet.getLoadOnStartup() == 0) { diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java index 66824b42d28a3..a960bf964f346 100644 --- a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java +++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletWebXmlTestCase.java @@ -3,6 +3,7 @@ import static org.hamcrest.Matchers.is; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; @@ -29,14 +30,20 @@ public class ServletWebXmlTestCase { " \n" + " mapped\n" + " /mapped\n" + - " " + + " \n" + + "\n" + + " \n" + + " wasm\n" + + " application/wasm\n" + + " " + ""; @RegisterExtension static QuarkusUnitTest runner = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(WebXmlServlet.class) - .addAsManifestResource(new StringAsset(WEB_XML), "web.xml")); + .addAsManifestResource(new StringAsset(WEB_XML), "web.xml") + .addAsManifestResource(EmptyAsset.INSTANCE, "resources/test.wasm")); @Test public void testWebXmlServlet() { @@ -45,4 +52,10 @@ public void testWebXmlServlet() { .body(is("web xml servlet")); } + @Test + public void testMimeMapping() { + RestAssured.when().get("/test.wasm").then() + .statusCode(200) + .contentType(is("application/wasm")); + } } diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index d66f8d71f3e48..a1fdf23dd3787 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -65,6 +65,7 @@ import io.undertow.servlet.api.InstanceHandle; import io.undertow.servlet.api.ListenerInfo; import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.MimeMapping; import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.ServletContainer; @@ -310,6 +311,11 @@ public void registerListener(RuntimeValue info, Class listene factory.instanceFactory(listenerClass)))); } + public void addMimeMapping(RuntimeValue info, String extension, + String mimeType) throws Exception { + info.getValue().addMimeMapping(new MimeMapping(extension, mimeType)); + } + public void addServletInitParameter(RuntimeValue info, String name, String value) { info.getValue().addInitParameter(name, value); } From fdec659a6dfc6af4f4515c7d1fc8f6f1e6e732b8 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Sun, 5 Jan 2020 23:10:23 +0100 Subject: [PATCH 475/602] Typo fixes --- .../src/main/java/io/quarkus/deployment/QuarkusConfig.java | 6 +++--- .../quarkus/deployment/builditem/ArchiveRootBuildItem.java | 2 +- .../deployment/pkg/steps/ErrorReplacingProcessReader.java | 6 +++--- .../java/io/quarkus/deployment/util/ClassOutputUtil.java | 2 +- .../src/main/java/io/quarkus/runtime/CleanableExecutor.java | 2 +- .../src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java | 6 +++--- docs/src/main/asciidoc/amazon-lambda-http.adoc | 6 +++--- docs/src/main/asciidoc/azure-functions-http.adoc | 6 +++--- docs/src/main/asciidoc/rest-client-multipart.adoc | 2 +- .../dynamodb/runtime/AwsCredentialsProviderConfig.java | 2 +- .../interceptor/check/AnyDelegatingSecurityCheck.java | 2 +- .../vertx/http/runtime/security/HttpSecurityPolicy.java | 2 +- .../quarkus/arc/processor/ComponentsProviderGenerator.java | 4 ++-- .../src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java | 4 ++-- .../src/main/java/io/quarkus/arc/impl/EventImpl.java | 6 +++--- .../src/main/java/io/quarkus/arc/impl/RequestContext.java | 4 ++-- .../java/io/quarkus/bootstrap/model/AppArtifactKey.java | 2 +- .../io/quarkus/bootstrap/resolver/AppModelResolver.java | 2 +- integration-tests/amazon-lambda-http-resteasy/pom.xml | 4 ++-- integration-tests/amazon-lambda-http/pom.xml | 2 +- .../it/amazon/lambda/AmazonLambdaSimpleTestCase.java | 2 +- integration-tests/elytron-resteasy/pom.xml | 2 +- 22 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java index 9960962fc63cd..fee7c7c85cbb7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java @@ -46,11 +46,11 @@ public static Set getNames(String prefix) { Set props = new HashSet<>(); for (String i : ConfigProvider.getConfig().getPropertyNames()) { if (i.startsWith(prefix)) { - int idex = i.indexOf('.', prefix.length() + 1); - if (idex == -1) { + int index = i.indexOf('.', prefix.length() + 1); + if (index == -1) { props.add(i.substring(prefix.length() + 1)); } else { - props.add(i.substring(prefix.length() + 1, idex)); + props.add(i.substring(prefix.length() + 1, index)); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java index 35a241d2cb8e7..0801469938b0c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java @@ -31,7 +31,7 @@ public ArchiveRootBuildItem(Path archiveLocation, Path archiveRoot, Collection

queue try { String fullName = m.group(1); - int idex = fullName.lastIndexOf('.'); - String clazz = fullName.substring(0, idex); - String method = fullName.substring(idex + 1); + int index = fullName.lastIndexOf('.'); + String clazz = fullName.substring(0, index); + String method = fullName.substring(index + 1); if (reportAnalyzer == null) { reportAnalyzer = new ReportAnalyzer(report.getAbsolutePath()); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ClassOutputUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ClassOutputUtil.java index d89e509a61dbe..80e75a334642f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/ClassOutputUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ClassOutputUtil.java @@ -5,7 +5,7 @@ import java.nio.file.Files; /** - * Utility that dumps bytes from a class to a file - useful for debugging generated clases + * Utility that dumps bytes from a class to a file - useful for debugging generated classes */ public final class ClassOutputUtil { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/CleanableExecutor.java b/core/runtime/src/main/java/io/quarkus/runtime/CleanableExecutor.java index ce7f97342bfd5..f480022a5f07e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/CleanableExecutor.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/CleanableExecutor.java @@ -27,7 +27,7 @@ * * This is only for development mode, it must not be used for production applications. * - * TODO: should this just provide a facacde that simply starts a new thread pool instead? + * TODO: should this just provide a facade that simply starts a new thread pool instead? */ public final class CleanableExecutor implements ExecutorService { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java index 0c921b16197df..caf115e197459 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java @@ -35,9 +35,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { String clazz = className; String method = ""; if (methodName != null) { - int idex = methodName.lastIndexOf('.'); - clazz = methodName.substring(0, idex); - method = methodName.substring(idex + 1); + int index = methodName.lastIndexOf('.'); + clazz = methodName.substring(0, index); + method = methodName.substring(index + 1); } File[] files = reportsDir.listFiles(); diff --git a/docs/src/main/asciidoc/amazon-lambda-http.adoc b/docs/src/main/asciidoc/amazon-lambda-http.adoc index 29bb535bffe24..2838afd4ce17c 100644 --- a/docs/src/main/asciidoc/amazon-lambda-http.adoc +++ b/docs/src/main/asciidoc/amazon-lambda-http.adoc @@ -3,12 +3,12 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// -= Quarkus - Amazon Lambda with Resteasy, Undertow, or Vert.x Web  += Quarkus - Amazon Lambda with RESTEasy, Undertow, or Vert.x Web  :extension-status: preview include::./attributes.adoc[] -The `quarkus-amazon-lambda-http` extension allows you to write microservices with Resteasy (JAX-RS), +The `quarkus-amazon-lambda-http` extension allows you to write microservices with RESTEasy (JAX-RS), Undertow (servlet), or Vert.x Web and make these microservices deployable as an Amazon Lambda using https://aws.amazon.com/api-gateway/[Amazon's API Gateway] and https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html[Amazon's SAM framework]. @@ -193,7 +193,7 @@ you may need to manually manage that encoding. == Examine the POM -If you want to adapt an existing Resteasy, Undertow, or Vert.x Web project to Amazon Lambda, there's a couple +If you want to adapt an existing RESTEasy, Undertow, or Vert.x Web project to Amazon Lambda, there's a couple of things you need to do. Take a look at the generate example project to get an example of what you need to adapt. 1. Include the `quarkus-amazon-lambda-http` extension as a pom dependency diff --git a/docs/src/main/asciidoc/azure-functions-http.adoc b/docs/src/main/asciidoc/azure-functions-http.adoc index 90a2e0a643a82..355141d11fe92 100644 --- a/docs/src/main/asciidoc/azure-functions-http.adoc +++ b/docs/src/main/asciidoc/azure-functions-http.adoc @@ -3,12 +3,12 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// -= Quarkus - Azure Functions (Serverless) with Resteasy, Undertow, or Vert.x Web += Quarkus - Azure Functions (Serverless) with RESTEasy, Undertow, or Vert.x Web :extension-status: preview include::./attributes.adoc[] -The `quarkus-azure-functions-http` extension allows you to write microservices with Resteasy (JAX-RS), +The `quarkus-azure-functions-http` extension allows you to write microservices with RESTEasy (JAX-RS), Undertow (servlet), or Vert.x Web and make these microservices deployable to the Azure Functions runtime. One azure function deployment can represent any number of JAX-RS, servlet, or Vert.x Web endpoints. @@ -96,7 +96,7 @@ https://{appName}.azurewebsites.net/api/vertx/hello == Extension maven dependencies -The sample project includes the Resteasy, Undertow, and Vert.x Web extensions. If you are only using one of those +The sample project includes the RESTEasy, Undertow, and Vert.x Web extensions. If you are only using one of those APIs (i.e. jax-rs only), respectively remove the maven dependency `quarkus-resteasy`, `quarkus-undertow`, and/or `quarkus-vertx-web`. diff --git a/docs/src/main/asciidoc/rest-client-multipart.adoc b/docs/src/main/asciidoc/rest-client-multipart.adoc index 13071d8145050..9f5e2c42af61b 100644 --- a/docs/src/main/asciidoc/rest-client-multipart.adoc +++ b/docs/src/main/asciidoc/rest-client-multipart.adoc @@ -7,7 +7,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] -Resteasy has rich support for the `multipart/*` and `multipart/form-data` mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. `multipart/form-data` is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it. +RESTEasy has rich support for the `multipart/*` and `multipart/form-data` mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. `multipart/form-data` is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it. This guide explains how to use the MicroProfile REST Client with Multipart in order to interact with REST APIs diff --git a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java index c7394909b14b8..82a347729fb56 100644 --- a/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java +++ b/extensions/amazon-dynamodb/runtime/src/main/java/io/quarkus/dynamodb/runtime/AwsCredentialsProviderConfig.java @@ -22,7 +22,7 @@ public class AwsCredentialsProviderConfig { * ** Credential profiles file at the default location (`~/.aws/credentials`) shared by all AWS SDKs and the AWS CLI * ** Credentials delivered through the Amazon EC2 container service if `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable is set and security manager has permission to access the variable. * ** Instance profile credentials delivered through the Amazon EC2 metadata service - * * `static` - the provider that uses the access key and secret access key specified in the `tatic-provider` section of the config. + * * `static` - the provider that uses the access key and secret access key specified in the `static-provider` section of the config. * * `system-property` - it loads credentials from the `aws.accessKeyId`, `aws.secretAccessKey` and `aws.sessionToken` system properties. * * `env-variable` - it loads credentials from the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment variables. * * `profile` - credentials are based on AWS configuration profiles. This loads credentials from diff --git a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java index 138c4c7cda8a8..7019905db9f31 100644 --- a/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java +++ b/extensions/spring-security/runtime/src/main/java/io/quarkus/spring/security/runtime/interceptor/check/AnyDelegatingSecurityCheck.java @@ -7,7 +7,7 @@ import io.quarkus.security.runtime.interceptor.check.SecurityCheck; /** - * A {@link SecurityCheck} where if any of the delagates passes the security check then + * A {@link SecurityCheck} where if any of the delegates passes the security check then * the delegate passes as well */ public class AnyDelegatingSecurityCheck implements SecurityCheck { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityPolicy.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityPolicy.java index 7ea18ab25c608..48a4002f0fa86 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityPolicy.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityPolicy.java @@ -29,7 +29,7 @@ class CheckResult { public static CheckResult PERMIT = new CheckResult(true); /** - * If this check was sucessful + * If this check was successful */ private final boolean permitted; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java index 8c6c60da26b8b..37114454f1c62 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java @@ -151,7 +151,7 @@ private void processBeans(ClassCreator componentsProvider, MethodCreator getComp .collect(Collectors.joining("\n"))); } stuck = true; - // First try to proces beans that are not dependencies + // First try to process beans that are not dependencies stuck = addBeans(beanAdder, beanToInjections, processed, beanIdToBeanHandle, beanToGeneratedName, b -> !isDependency(b, beanToInjections)); if (stuck) { @@ -390,7 +390,7 @@ void addBean(BeanInfo bean, ResultHandle beanIdToBeanHandle, if (bean.isProducerMethod() || bean.isProducerField()) { if (!processedBeans.contains(bean.getDeclaringBean())) { throw new IllegalStateException( - "Declaring bean of a producer bean is not available - most probaly an unsupported circular dependency use case \n - declaring bean: " + "Declaring bean of a producer bean is not available - most probably an unsupported circular dependency use case \n - declaring bean: " + bean.getDeclaringBean() + "\n - producer bean: " + bean); } params.add(addBeansMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 48d6b512d80f8..90f52867f9097 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -300,7 +300,7 @@ public synchronized void shutdown() { try { EventImpl.createNotifier(Object.class, Object.class, beforeDestroyQualifiers, this).notify(toString()); } catch (Exception e) { - LOGGER.warn("An error occured during delivery of the @BeforeDestroyed(ApplicationScoped.class) event", e); + LOGGER.warn("An error occurred during delivery of the @BeforeDestroyed(ApplicationScoped.class) event", e); } // Destroy contexts applicationContext.destroy(); @@ -311,7 +311,7 @@ public synchronized void shutdown() { try { EventImpl.createNotifier(Object.class, Object.class, destroyQualifiers, this).notify(toString()); } catch (Exception e) { - LOGGER.warn("An error occured during delivery of the @Destroyed(ApplicationScoped.class) event", e); + LOGGER.warn("An error occurred during delivery of the @Destroyed(ApplicationScoped.class) event", e); } singletonContext.destroy(); // Clear caches diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java index 211e908129ca2..2789564245f06 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java @@ -108,9 +108,9 @@ private Notifier getNotifier(Class runtimeType) { @Override public Event select(Annotation... qualifiers) { - Set mergerdQualifiers = new HashSet<>(this.qualifiers); - Collections.addAll(mergerdQualifiers, qualifiers); - return new EventImpl(eventType, mergerdQualifiers); + Set mergedQualifiers = new HashSet<>(this.qualifiers); + Collections.addAll(mergedQualifiers, qualifiers); + return new EventImpl(eventType, mergedQualifiers); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java index 46be1893621f5..988aeb8099708 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java @@ -132,7 +132,7 @@ public void destroy() { try { fireIfNotEmpty(beforeDestroyedNotifier); } catch (Exception e) { - LOGGER.warn("An error occured during delivery of the @BeforeDestroyed(RequestScoped.class) event", e); + LOGGER.warn("An error occurred during delivery of the @BeforeDestroyed(RequestScoped.class) event", e); } for (InstanceHandle instance : ctx.values()) { try { @@ -145,7 +145,7 @@ public void destroy() { try { fireIfNotEmpty(destroyedNotifier); } catch (Exception e) { - LOGGER.warn("An error occured during delivery of the @Destroyed(RequestScoped.class) event", e); + LOGGER.warn("An error occurred during delivery of the @Destroyed(RequestScoped.class) event", e); } ctx.clear(); } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java index 02014614a81c9..7891f3012a0a9 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java @@ -14,7 +14,7 @@ public static AppArtifactKey fromString(String str) { protected static String[] split(String str, String[] parts, int fromIndex) { int i = str.lastIndexOf(':', fromIndex - 1); if(i <= 0) { - throw new IllegalArgumentException("GroupId and artifactId separating ':' is abscent or not in the right place in '" + str.substring(0, fromIndex) + "'"); + throw new IllegalArgumentException("GroupId and artifactId separating ':' is absent or not in the right place in '" + str.substring(0, fromIndex) + "'"); } parts[3] = str.substring(i + 1, fromIndex); fromIndex = i; diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolver.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolver.java index e826fb6ac6958..67dd2631fa0c5 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolver.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolver.java @@ -97,7 +97,7 @@ default List resolveUserDependencies(AppArtifact artifact) throws * @param fromVersionIncluded whether the specified lowest version should be included in the range * @param upToVersion the highest version of the range * @param upToVersionIncluded whether the specified highest version should be included in the range - * @return the next version from the specified range or null if the next version is not avaiable + * @return the next version from the specified range or null if the next version is not available * @throws AppModelResolverException in case of a failure */ String getNextVersion(AppArtifact artifact, String fromVersion, boolean fromVersionIncluded, String upToVersion, boolean upToVersionIncluded) throws AppModelResolverException; diff --git a/integration-tests/amazon-lambda-http-resteasy/pom.xml b/integration-tests/amazon-lambda-http-resteasy/pom.xml index b51e592e0ea16..2a4e80fe432e2 100644 --- a/integration-tests/amazon-lambda-http-resteasy/pom.xml +++ b/integration-tests/amazon-lambda-http-resteasy/pom.xml @@ -11,8 +11,8 @@ 4.0.0 quarkus-integration-test-amazon-lambda-http-resteasy - Quarkus - Integration Tests - Amazon Lambda HTTP Resteasy - Test with Resteasy Standalone and Amazon Lambda HTTP + Quarkus - Integration Tests - Amazon Lambda HTTP RESTEasy + Test with RESTEasy Standalone and Amazon Lambda HTTP io.quarkus diff --git a/integration-tests/amazon-lambda-http/pom.xml b/integration-tests/amazon-lambda-http/pom.xml index 87128b9250dc6..c864171a4a51a 100644 --- a/integration-tests/amazon-lambda-http/pom.xml +++ b/integration-tests/amazon-lambda-http/pom.xml @@ -12,7 +12,7 @@ quarkus-integration-test-amazon-lambda-http Quarkus - Integration Tests - Amazon Lambda HTTP - Module that contains Amazon Lambda related tests for Resteasy + Module that contains Amazon Lambda related tests for RESTEasy io.quarkus diff --git a/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java b/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java index 0999db1a52df5..0dc4dcf7c2c4d 100644 --- a/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java +++ b/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java @@ -11,7 +11,7 @@ public class AmazonLambdaSimpleTestCase { @Test - public void testSimpleLambdaSucess() throws Exception { + public void testSimpleLambdaSuccess() throws Exception { InputObject in = new InputObject(); in.setGreeting("Hello"); in.setName("Stu"); diff --git a/integration-tests/elytron-resteasy/pom.xml b/integration-tests/elytron-resteasy/pom.xml index 86c42a9db1504..cb26af28a77f5 100644 --- a/integration-tests/elytron-resteasy/pom.xml +++ b/integration-tests/elytron-resteasy/pom.xml @@ -13,7 +13,7 @@ quarkus-integration-test-elytron-resteasy - Quarkus - Integration Tests - Elytron Resteasy + Quarkus - Integration Tests - Elytron RESTEasy From 22e31e2eb00a0d7e4a364a638b2dceeb45418cea Mon Sep 17 00:00:00 2001 From: Mark Little Date: Sat, 4 Jan 2020 11:57:18 +0000 Subject: [PATCH 476/602] Improved STM text Will make further changes once I've modified the quickstart code itself. https://github.com/quarkusio/quarkusio.github.io/issues/411 https://github.com/quarkusio/quarkusio.github.io/pull/417 --- .../software-transactional-memory.adoc | 182 +++++++++++++----- 1 file changed, 136 insertions(+), 46 deletions(-) diff --git a/docs/src/main/asciidoc/software-transactional-memory.adoc b/docs/src/main/asciidoc/software-transactional-memory.adoc index 2ece8de4bcbf7..cd77b95685e06 100644 --- a/docs/src/main/asciidoc/software-transactional-memory.adoc +++ b/docs/src/main/asciidoc/software-transactional-memory.adoc @@ -8,22 +8,79 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] :extension-status: preview -Quarkus supports the Software Transactional Memory (STM) implementation provided by the -Narayana open source project. Narayana STM allows a program to group object accesses into -a transaction such that other transactions either see all of the changes at once or they -see none of them. - -STM offers an approach to developing transactional applications in a highly concurrent -environment with some of the same characteristics of ACID (Atomicity, Consistency, -Isolation and Durability) transactions. - -To use Narayana STM you must define which objects you would like to be transactional using java -annotations. Please refer to the https://narayana.io/docs/project/index.html#d0e16066[Narayana STM manual] -and the https://narayana.io//docs/project/index.html#d0e16133[STM annotations guide] for more details. +Software Transactional Memory (STM) has been around in research environments since the late +1990's and has relatively recently started to appear in products and various programming +languages. We won't go into all of the details behind STM but the interested reader could look at https://groups.csail.mit.edu/tds/papers/Shavit/ShavitTouitou-podc95.pdf[this paper]. +However, suffice it to say that STM offers an approach to developing transactional applications in a highly +concurrent environment with some of the same characteristics of ACID transactions, which you've probably already used +through JTA. Importantly though, the Durability property is relaxed (removed) within STM implementations, +or at least made optional. This is not the situation with JTA, where state changes are made durable +to a relational database which supports https://pubs.opengroup.org/onlinepubs/009680699/toc.pdf[the X/Open XA +standard]. + +Note, the STM implementation provided by Quarkus is based on the https://narayana.io/docs/project/index.html#d0e16066[Narayana STM] implementation. This document isn't meant to be a replacement for that project's documentation so you may want +to look at that for more detail. However, we will try to focus more on how you can combine some of the key capabilities +into Quarkus when developing Kubernetes native applications and microservices. + +== Why use STM with Quarkus? + +Now you may still be asking yourself "Why STM instead of JTA?" or "What are the benefits +to STM that I don't get from JTA?" Let's try to answer those or similar questions, with +a particular focus on why we think they're great for Quarkus, microservices and Kubernetes +native applications. So in no specific order ... + +* The goal of STM is to simplify object reads and writes from multiple threads/protect +state from concurrent updates. The Quarkus STM implementation will safely manage any conflicts between +these threads using whatever isolation model has been chosen to protect that specific state +instance (object in the case of Quarkus). In Quarkus STM, there are two isolation implementations, +pessimistic (the default), which would cause conflicting threads to be blocked until the original +has completed its updates (committed or aborted the transaction); then there's the optimistic +approach which allows all of the threads to proceed and checks for conflicts at commit time, where +one or more of the threads may be forced to abort if there have been conflicting updates. + +* STM objects have state but it doesn't need to be persistent (durable). In fact the +default behaviour is for objects managed within transactional memory to be volatile, such that +if the service or microservice within which they are being used crashes or is spawned elsewhere, e.g., +by a scheduler, all state in memory is lost and the objects start from scratch. But surely you get this and more +with JTA (and a suitable transactional datastore) and don't need to worry about restarting your application? +Not quite. There's a trade-off here: we're doing away +with persistent state and the overhead of reading from and then writing (and sync-ing) to the datastore during each +transaction. This makes updates to (volatile) state very fast but you still get the benefits of atomic updates +across multiple STM objects (e.g., objects your team wrote then calling objects you inherited from another team and requiring +them to make all-or-nothing updates), as well as consistency +and isolation in the presence of concurrent threads/users (common in distributed microservices architectures). +Furthermore, not all stateful applications need to be durable - even when JTA transactions are used, it tends to be the +exception and not the rule. And as you'll see later, because applications can optionally start and control transactions, it's possible to build microservices which can undo state changes and try alternative paths. + +* Another benefit of STM is composability and modularity. You can write concurrent Quarkus objects/services that +can be easily composed with any other services built using STM, without exposing the details of how the objects/services +are implemented. As we discussed earlier, this ability to compose objects you wrote with those other teams may have +written weeks, months or years earlier, and have A, C and I properties can be hugely beneficial. Furthermore, some +STM implementations, including the one Quarkus uses, support nested transactions and these allow changes made within +the context of a nested (sub) transaction to later be rolled back by the parent transaction. + +* Although the default for STM object state is volatile, it is possible to configure the STM implementation +such that an object's state is durable. Although it's possible to configure Narayana such that different +backend datastores can be used, including relational databases, the default is the local operating system +file system, which means you don't need to configure anything else with Quarkus such as a database. + +* Many STM implementations allow "plain old language objects" to be made STM-aware with little or no changes to +the application code. You can build, test and deploy applications without wanting them to be STM-aware and +then later add those capabilities if they become necessary and without much development overhead at all. + +== Building STM applications There is also a fully worked example in the quickstarts which you may access by cloning the Git repository: `git clone {quickstarts-clone-url}`, or by downloading an {quickstarts-archive-url}[archive]. -Look for the `software-transactional-memory-quickstart` example. +Look for the `software-transactional-memory-quickstart` example. This will help to understand how you +can build STM-aware applications with Quarkus. However, before we do so there are a few basic concepts +which we need to cover. + +Note, as you will see, STM in Quarkus relies on a number of annotations to define behaviours. The lack +of these annotations causes sensible defaults to be assumed but it is important for the developer to +understand what these may be. Please refer to the https://narayana.io/docs/project/index.html#d0e16066[Narayana STM manual] +and the https://narayana.io//docs/project/index.html#d0e16133[STM annotations guide] for more details on +all of the annotations Narayana STM provides. include::./status-include.adoc[] @@ -43,12 +100,17 @@ To use the extension include it as a dependency in your application pom: -- -Now you may use the STM library just like you would normally use it. But briefly, the process is: +== Defining STM-aware classes -== Defining Transactional Objects +In order for the STM subsytem to have knowledge about which classes are to be managed within the context +of transactional memory it is necessary to provide a minimal level of instrumentation. This occurs by +categorising STM-aware and STM-unaware classes through an interface boundary; specifically all STM-aware objects +must be instances of classes which inherit from interfaces that themselves have been annotated to identify them +as STM-aware. Any other objects (and their classes) which do not follow this rule will not be managed by the +STM subsystem and hence any of their state changes will not be rolled back, for example. -Transactional objects must implement an interface. Add the `org.jboss.stm.annotations.Transactional` annotation to the -interfaces that you wish to be managed by a transactional container. For example +The specific annotation that STM-aware application interfaces must use is `org.jboss.stm.annotations.Transactional`. +For example: [source,java] -- @@ -59,15 +121,48 @@ public interface FlightService { } -- -Unless specified using other annotations, all public methods of implementations of this object will be assumed to modify the state of the object. -Please refer to the Narayana guide for details of how to exert finer grained control over the transactional behaviour of objects that implement -interfaces marked with the `@Transactional` annotation. +Classes which implement this interface are able to use additional annotations from Narayana to tell the STM +subsystem about things such as whether a method will modify the state of the object, or what state variables +within the class should be managed transactionally, e.g., some instance variables may not need to be rolled back +if a transaction aborts. As mentioned earlier, if those annotations are not present then defaults are chosen to +guarantee safety, such as assuming all methods will modify state. + +[source,java] +-- +public class FlightServiceImpl implements FlightService { + @ReadLock + public int getNumberOfBookings() { ... } + public void makeBooking(String details) {...} + + @NotState + private int timesCalled; +} +-- + +For example, by using the `@ReadLock` annotation on the `getNumberOfBookings` method, we are able to tell the +STM subsystem that no state modifications will occur in this object when it is used in the transactional +memory. Also, the `@NotState` annotation tells the system to ignore `timesCalled` when transactions commit or +abort, so this value only changes due to application code. + +Please refer to the Narayana guide for details of how to exert finer grained control over the transactional +behaviour of objects that implement interfaces marked with the `@Transactional` annotation. == Creating STM objects -The STM library needs to be told which objects it should be managing by providing a container for the transactional memory. -The default container (`org.jboss.stm.Container`) provides support for volatile objects that cannot be shared between JVMs -(although other constructors do allow for other models): +The STM subsystem needs to be told about which objects it should be managing. The Quarkus (aka Narayana) STM implementation +does this by providing containers of transactional memory within which these object instances reside. Until an object +is placed within one of these STM containers it cannot be managed within transactions and any state changes will +not possess the A, C, I (or even D) properties. + +Note, the term "container" was defined within the STM implementation years before Linux containers came along. It may +be confusing to use especially in a Kubernetes native environment such as Quarkus, but hopefully +the reader can do the mental mapping. + +The default STM container (`org.jboss.stm.Container`) provides support for volatile objects that can only be shared between +threads in the same microservice/JVM instance. When a STM-aware object is placed into the container it returns a handle +through which that object should then be used in the future. It is important to use this handle as continuing to access +the object through the original reference will not allow the STM subsystem to track access and manage state and +concurrency control. [source,java] -- @@ -82,25 +177,30 @@ The default container (`org.jboss.stm.Container`) provides support for volatile <1> You need to tell each Container about the type of objects for which it will be responsible. In this example it will be instances that implement the FlightService interface. -<2> Then you create an instance that implements FlightService. You cannot use it directly at this stage because - its operations aren't being monitored by the Container. -<3> To obtain a managed instance, pass the instance to the `container` to obtain a reference through which you - will be able perform transactional operations. This reference can be used safely from multiple threads - (provided that each thread uses it in a transaction context - see the next section. +<2> Then you create an instance that implements `FlightService`. You should not use it directly at this stage because + access to it is not being managed by the STM subsystem. +<3> To obtain a managed instance, pass the original object to the STM `container` which then returns a reference + through which you will be able perform transactional operations. This reference can be used safely from multiple threads. == Defining transaction boundaries -STM objects must be accessed within a transaction (otherwise they will behave just like any other java object). -You can define the transaction boundary in two ways: +Once an object is placed within an STM container the application developer can manage the scope of transactions +within which it is used. There are some annotations which can be applied to the STM-aware class to have the +container automatically create a transaction whenever a specific method is invoked. === Declarative approach -The easiest, but less flexible, way to define your transaction boundaries is to place an `@NestedTopLevel` or `@Nested` annotation on the interface. -Then when a method of your transactional object is invoked a new transaction will be created for the duration of the method call. +If the `@NestedTopLevel` or `@Nested` annotation is placed on a method signature then the STM container will +start a new transaction when that method is invoked and attempt to commit it when the method returns. If there is +a transaction already associated with the calling thread then each of these annotations behaves slightly differently: +the former annotation will always create a new top-level transaction within which the method will execute, so the enclosing +transaction does not behave as a parent, i.e., the nested top-level transaction will commit or abort independently; the +latter annotation will create a transaction with is properly nested within the calling transaction, i.e., that +transaction acts as the parent of this newly created transaction. -=== API approach +=== Programmatic approach -The more flexible approach is to manually start a transaction before accessing methods of transactional objects: +The application can programmatically start a transaction before accessing the methods of STM objects: [source,java] -- @@ -133,19 +233,9 @@ aa.begin(); <2> provides a number of features for managing conflicting behaviour and these are covered in the Narayana STM manual). <6> Programmatically decide to abort the transaction which means that the changes made by the flight and taxi services are discarded. -== Concurrency behaviour - -The goal of STM is to simplify object reads and writes from multiple threads. -Threads are responsible for starting their own transactions before accessing -a transactional object. The STM library will safely manage any conflicts between -these threads. For example, if the access mode is pessimistic (the default), -and a thread enters a transactional method then other threads may be blocked -until the first thread leaves the method. This blocking behaviour may be modified -using suitable STM annotations. Optimistic concurrency is also supported: in -this mode conflicts are checked only at commit time and a thread may be forced -to abort if another thread has made conflicting updates. +== Distributed transactions -Remark: sharing a transaction between multiple threads is possible but is currently +Sharing a transaction between multiple services is possible but is currently an advanced use case only and the Narayana documentation should be consulted if this behaviour is required. In particular, STM does not yet support the features described in the link:context-propagation[Context Propagation guide]. From eb32f81fe43f48ea51c886ce3c0be727d8d1e598 Mon Sep 17 00:00:00 2001 From: "Michael J. Simons" Date: Sun, 5 Jan 2020 19:31:09 +0100 Subject: [PATCH 477/602] Improve the Neo4j guide. The Neo4j driver has been updated to 4.0.0 final and Neo4j 4.0 can be used from Docker, the preview flag can now be removed. The final driver revealed some errors in the existing guide: The reading of the transactional result of a transactional function needs to happen inside the same transaction. Therefor the guide needs a fix so that the composition of the future extracting the node happens in the transaction, not outside. --- docs/src/main/asciidoc/neo4j.adoc | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/docs/src/main/asciidoc/neo4j.adoc b/docs/src/main/asciidoc/neo4j.adoc index 7b1038b3e35c4..87169cdfbf1ae 100644 --- a/docs/src/main/asciidoc/neo4j.adoc +++ b/docs/src/main/asciidoc/neo4j.adoc @@ -4,7 +4,7 @@ and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// = Quarkus - Neo4j -:neo4j_version: 3.5.13 +:neo4j_version: 4.0.0 include::./attributes.adoc[] :extension-status: preview @@ -36,12 +36,6 @@ with online backup and high availability extensions licensed under a closed-sour include::./status-include.adoc[] -[WARNING] -==== -It is based on an alpha version of the Neo4j driver. -Hence the reason for it to be _preview_ -==== - == Programming model The driver and thus the Quarkus extension support three different programming models: @@ -279,10 +273,10 @@ It uses transaction functions of the driver: public CompletionStage create(Fruit fruit) { AsyncSession session = driver.asyncSession(); return session - .writeTransactionAsync(tx -> - tx.runAsync("CREATE (f:Fruit {name: $name}) RETURN f", Values.parameters("name", fruit.name)) + .writeTransactionAsync(tx -> tx + .runAsync("CREATE (f:Fruit {name: $name}) RETURN f", Values.parameters("name", fruit.name)) + .thenCompose(fn -> fn.singleAsync()) ) - .thenCompose(fn -> fn.singleAsync()) .thenApply(record -> Fruit.from(record.get("f").asNode())) .thenCompose(persistedFruit -> session.closeAsync().thenApply(signal -> persistedFruit)) .thenApply(persistedFruit -> Response @@ -321,10 +315,10 @@ We also add some exception handling, in case the resource is called with an inva public CompletionStage getSingle(@PathParam("id") Long id) { AsyncSession session = driver.asyncSession(); return session - .readTransactionAsync(tx -> - tx.runAsync("MATCH (f:Fruit) WHERE id(f) = $id RETURN f", Values.parameters("id", id)) + .readTransactionAsync(tx -> tx + .runAsync("MATCH (f:Fruit) WHERE id(f) = $id RETURN f", Values.parameters("id", id)) + .thenCompose(fn -> fn.singleAsync()) ) - .thenCompose(fn -> fn.singleAsync()) .handle((record, exception) -> { if(exception != null) { Throwable source = exception; @@ -368,10 +362,10 @@ public CompletionStage delete(@PathParam("id") Long id) { AsyncSession session = driver.asyncSession(); return session - .writeTransactionAsync(tx -> - tx.runAsync("MATCH (f:Fruit) WHERE id(f) = $id DELETE f", Values.parameters("id", id)) + .writeTransactionAsync(tx -> tx + .runAsync("MATCH (f:Fruit) WHERE id(f) = $id DELETE f", Values.parameters("id", id)) + .thenCompose(fn -> fn.consumeAsync()) // <1> ) - .thenCompose(fn -> fn.consumeAsync()) // <1> .thenCompose(response -> session.closeAsync()) .thenApply(signal -> Response.noContent().build()); } From 614e134d60bd0e1f1a34ddea3a41d73e3daa883f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 6 Jan 2020 12:05:37 +0200 Subject: [PATCH 478/602] Fix Kubernetes manifest port handling This is needed because of the config changes that went in 1.1. One thing that needed to be removed because of those changes was the warning we used to produce about potential mismatch between the port in the Kubernetes manifests and the port set at runtime Fixes: #6408 --- .../http/deployment/VertxHttpProcessor.java | 9 +++------ .../src/main/resources/application.properties | 1 + .../verify.groovy | 6 ++++++ .../kubernetes/src/it/kubernetes/verify.groovy | 16 +++++++++++++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 3f79ed6ee306c..1675ea182ef63 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -4,7 +4,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.OptionalInt; import java.util.stream.Collectors; import org.eclipse.microprofile.config.ConfigProvider; @@ -76,11 +75,9 @@ void filterMultipleVertxInstancesWarning(LaunchModeBuildItem launchModeBuildItem } @BuildStep(onlyIf = IsNormal.class) - @Record(value = ExecutionTime.RUNTIME_INIT, optional = true) - public KubernetesPortBuildItem kubernetes(HttpConfiguration config, VertxHttpRecorder recorder) { - int port = ConfigProvider.getConfig().getValue("quarkus.http.port", OptionalInt.class).orElse(8080); - recorder.warnIfPortChanged(config, port); - return new KubernetesPortBuildItem(config.port, "http"); + public KubernetesPortBuildItem kubernetes() { + int port = ConfigProvider.getConfig().getOptionalValue("quarkus.http.port", Integer.class).orElse(8080); + return new KubernetesPortBuildItem(port, "http"); } @BuildStep diff --git a/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/src/main/resources/application.properties b/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/src/main/resources/application.properties index 86ba7dc838782..7babcc8e187b0 100644 --- a/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/src/main/resources/application.properties +++ b/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/src/main/resources/application.properties @@ -1,4 +1,5 @@ # Configuration file +quarkus.http.port=9090 kubernetes.name=test-it kubernetes.labels[0].key=foo kubernetes.labels[0].value=bar diff --git a/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/verify.groovy b/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/verify.groovy index 8d12bbbbd4e59..8e3716401652f 100644 --- a/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/verify.groovy +++ b/integration-tests/kubernetes/src/it/kubernetes-with-application-properties/verify.groovy @@ -29,3 +29,9 @@ assert env.value == "SOMEVALUE" //Check the image assert deployment.spec.template.spec.containers[0].image == "quay.io/grp/kubernetes-with-application-properties:0.1-SNAPSHOT" +assert deployment.spec.template.spec.containers[0].ports[0].containerPort == 9090 + +//Check the Service +Service service = list.items.find{r -> r.kind == "Service"} +assert service != null +assert service.spec.ports[0].port == 9090 diff --git a/integration-tests/kubernetes/src/it/kubernetes/verify.groovy b/integration-tests/kubernetes/src/it/kubernetes/verify.groovy index d918d7f033a2c..241d0675ad0fc 100644 --- a/integration-tests/kubernetes/src/it/kubernetes/verify.groovy +++ b/integration-tests/kubernetes/src/it/kubernetes/verify.groovy @@ -1,6 +1,7 @@ import io.dekorate.utils.Serialization import io.dekorate.deps.kubernetes.api.model.KubernetesList import io.dekorate.deps.kubernetes.api.model.apps.Deployment; +import io.dekorate.deps.kubernetes.api.model.Service; //Check that file exits String base = basedir @@ -11,7 +12,20 @@ assert kubernetesYml.exists() KubernetesList list = Serialization.unmarshal(kubernetesYml.text, KubernetesList.class) assert list != null Deployment deployment = list.items.find{r -> r.kind == "Deployment"} +Service service = list.items.find{r -> r.kind == "Service"} -//Check that ti contains a Deployment named after the project +//Check that it contains a Deployment named after the project assert deployment != null assert deployment.metadata.name == "kubernetes" + +//Check the port of the Deployment +assert deployment.spec.template.spec.containers[0].ports[0].containerPort == 8080 + +//Check that it contains a Service named after the project +assert service != null +assert service.metadata.name == "kubernetes" + +//Check the port of the Service +assert service.spec.ports[0].port == 8080 + + From b413df9ccf55fdb98f45b48f0903e89724bf878b Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 6 Jan 2020 17:28:07 +0100 Subject: [PATCH 479/602] Make quarkus.log.sentry.dsn required only when sentry is enabled --- .../sentry/SentryLoggerCustomTest.java | 5 +++- .../sentry/SentryLoggerDisabledTest.java | 30 +++++++++++++++++++ .../logging/sentry/SentryLoggerTest.java | 8 ++--- .../logging/sentry/SentryLoggerWrongTest.java | 28 +++++++++++++++++ ...lication-sentry-logger-disabled.properties | 0 ...application-sentry-logger-wrong.properties | 1 + .../quarkus/logging/sentry/SentryConfig.java | 2 +- .../sentry/SentryHandlerValueFactory.java | 7 ++++- 8 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerDisabledTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerWrongTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-disabled.properties create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-wrong.properties diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java index 7e5a01186309a..c1864feb9389a 100644 --- a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; import io.sentry.jvmti.FrameCache; public class SentryLoggerCustomTest { @@ -19,7 +20,9 @@ public class SentryLoggerCustomTest { @Test public void sentryLoggerCustomTest() { - assertThat(getSentryHandler().getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); + final SentryHandler sentryHandler = getSentryHandler(); + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isTrue(); } diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerDisabledTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerDisabledTest.java new file mode 100644 index 0000000000000..1164b779aadd7 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerDisabledTest.java @@ -0,0 +1,30 @@ +package io.quarkus.logging.sentry; + +import static io.quarkus.logging.sentry.SentryLoggerTest.getSentryHandler; +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; + +public class SentryLoggerDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-disabled.properties"); + + @Test + public void sentryLoggerDisabledTest() { + final SentryHandler sentryHandler = getSentryHandler(); + assertThat(sentryHandler).isNull(); + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java index a43b82ca08301..37ced7acdd330 100644 --- a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java @@ -27,7 +27,9 @@ public class SentryLoggerTest { @Test public void sentryLoggerDefaultTest() { - assertThat(getSentryHandler().getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); + final SentryHandler sentryHandler = getSentryHandler(); + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isFalse(); } @@ -42,9 +44,7 @@ public static SentryHandler getSentryHandler() { Handler handler = Arrays.stream(delayedHandler.getHandlers()) .filter(h -> (h instanceof SentryHandler)) .findFirst().orElse(null); - SentryHandler sentryHandler = (SentryHandler) handler; - assertThat(sentryHandler).isNotNull(); - return sentryHandler; + return (SentryHandler) handler; } @AfterAll diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerWrongTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerWrongTest.java new file mode 100644 index 0000000000000..7eaca71ae83be --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerWrongTest.java @@ -0,0 +1,28 @@ +package io.quarkus.logging.sentry; + +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class SentryLoggerWrongTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-wrong.properties") + .setExpectedException(ConfigurationException.class); + + @Test + public void sentryLoggerWrongTest() { + //Exception is expected + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-disabled.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-disabled.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-wrong.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-wrong.properties new file mode 100644 index 0000000000000..d2c579d84ad2c --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-wrong.properties @@ -0,0 +1 @@ +quarkus.log.sentry=true \ No newline at end of file diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java index 0ecbfa9905129..3bba67dc35165 100644 --- a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java @@ -27,7 +27,7 @@ public class SentryConfig { * your project’s DSN in the “Client Keys” section of your “Project Settings” in Sentry. */ @ConfigItem - public String dsn; + public Optional dsn; /** * The sentry log level. diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java index 14b0eac807988..f70ec5dd378dc 100644 --- a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java @@ -7,6 +7,7 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.configuration.ConfigurationException; import io.sentry.Sentry; import io.sentry.SentryOptions; import io.sentry.config.Lookup; @@ -20,12 +21,16 @@ public RuntimeValue> create(final SentryConfig config) { if (!config.enable) { return new RuntimeValue<>(Optional.empty()); } + if (!config.dsn.isPresent()) { + throw new ConfigurationException( + "Configuration key \"quarkus.log.sentry.dsn\" is required when Sentry is enabled, but its value is empty/missing"); + } if (!config.inAppPackages.isPresent()) { LOG.warn( "No 'quarkus.sentry.in-app-packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry. See https://quarkus.io/guides/logging-sentry#in-app-packages"); } final SentryConfigProvider provider = new SentryConfigProvider(config); - Sentry.init(SentryOptions.from(new Lookup(provider, provider), config.dsn)); + Sentry.init(SentryOptions.from(new Lookup(provider, provider), config.dsn.get())); SentryHandler handler = new SentryHandler(); handler.setLevel(config.level); return new RuntimeValue<>(Optional.of(handler)); From 86e89e23de39702bf01c1790c7ecb2d3c70d36ce Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 6 Jan 2020 21:46:03 +0100 Subject: [PATCH 480/602] Check that there is a pluginOptions child before iterating on it Fixes #6428 --- devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index f0ee42197df22..7b41179947a64 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -684,8 +684,10 @@ private void setKotlinSpecificFlags(DevModeContext devModeContext) { Xpp3Dom compilerPluginConfiguration = (Xpp3Dom) kotlinMavenPlugin.getConfiguration(); if (compilerPluginConfiguration != null) { Xpp3Dom compilerPluginArgsConfiguration = compilerPluginConfiguration.getChild("pluginOptions"); - for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { - options.add(argConfiguration.getValue()); + if (compilerPluginArgsConfiguration != null) { + for (Xpp3Dom argConfiguration : compilerPluginArgsConfiguration.getChildren()) { + options.add(argConfiguration.getValue()); + } } } devModeContext.setCompilerPluginsOptions(options); From d2a6adf84cfbc8c0718f6e900e6601e1414a1021 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2020 20:32:04 +0000 Subject: [PATCH 481/602] Bump flyway-core from 6.1.3 to 6.1.4 Bumps [flyway-core](https://github.com/flyway/flyway) from 6.1.3 to 6.1.4. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-6.1.3...flyway-6.1.4) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 1cc552260e75d..41597206d0ddc 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -147,7 +147,7 @@ 1.6.5 1.0.4 5.5.1.201910021850-r - 6.1.3 + 6.1.4 1.0.5 4.0.0 3.10.2 From db2ca54e3e4394a5a99b71d13047779d52840269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferraz=20Campos=20Florentino?= Date: Mon, 6 Jan 2020 21:45:40 -0300 Subject: [PATCH 482/602] Fix Logger.getLogger example parameter --- docs/src/main/asciidoc/centralized-log-management.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/centralized-log-management.adoc b/docs/src/main/asciidoc/centralized-log-management.adoc index 9f2c3ff615c8d..50c97c66ed1e0 100644 --- a/docs/src/main/asciidoc/centralized-log-management.adoc +++ b/docs/src/main/asciidoc/centralized-log-management.adoc @@ -46,7 +46,7 @@ import org.jboss.logging.Logger; @Path("/gelf-logging") @ApplicationScoped public class GelfLoggingResource { - private static final Logger LOG = Logger.getLogger(GelfLogHandlerResource.class); + private static final Logger LOG = Logger.getLogger(GelfLoggingResource.class); @GET public void log() { From cbd4cbadb30fc1e16c42364f803d7beddec7581d Mon Sep 17 00:00:00 2001 From: Matthias Harter Date: Tue, 24 Dec 2019 16:18:13 +0100 Subject: [PATCH 483/602] Add SSL port in server start log message --- .../quarkus/vertx/http/runtime/VertxHttpRecorder.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index 69568666ea191..8f2b894db8059 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -296,9 +296,14 @@ public void handle(AsyncResult event) { throw new RuntimeException("Unable to start HTTP server", e); } - // TODO log proper message - Timing.setHttpServer(String.format( - "Listening on: http://%s:%s", httpServerOptions.getHost(), httpServerOptions.getPort())); + String serverListeningMessage = String.format( + "Listening on: http://%s:%s", httpServerOptions.getHost(), httpServerOptions.getPort()); + + if (sslConfig != null) { + serverListeningMessage = serverListeningMessage + + String.format(" and https://%s:%s", sslConfig.getHost(), sslConfig.getPort()); + } + Timing.setHttpServer(serverListeningMessage); } /** From cecedefae049a557305191d68952d8ea3547de1b Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Mon, 6 Jan 2020 14:44:29 +0200 Subject: [PATCH 484/602] doc (#6303): Fix table structure and alignment. --- docs/src/main/asciidoc/kubernetes.adoc | 298 ++++++++++++------------- 1 file changed, 149 insertions(+), 149 deletions(-) diff --git a/docs/src/main/asciidoc/kubernetes.adoc b/docs/src/main/asciidoc/kubernetes.adoc index 988b54df1912e..d8d5953972b03 100644 --- a/docs/src/main/asciidoc/kubernetes.adoc +++ b/docs/src/main/asciidoc/kubernetes.adoc @@ -178,7 +178,7 @@ The docker registry and the user of the docker image can be specified, with the [source] ---- kubernetes.group=myUser -kubernetes.registry=http://my.docker-registry.net +docker.registry=http://my.docker-registry.net ---- Note: These options used to be `quarkus.kubernetes.docker.registry` and `quarkus.kubernetes.group` respectively. @@ -209,39 +209,38 @@ The table below describe all the available configuration options. .Kubernetes |==== -| Property | Type | Description | Default Value -| kubernetes.group | String | | -| kubernetes.name | String | | -| kubernetes.version | String | | -| kubernetes.init-containers | Container[] | | -| kubernetes.labels | Label[] | | -| kubernetes.annotations | Annotation[] | | -| kubernetes.env-vars | Env[] | | -| kubernetes.working-dir | String | | -| kubernetes.command | String[] | | -| kubernetes.arguments | String[] | | -| kubernetes.replicas | int | | 1 -| kubernetes.service-account | String | | -| kubernetes.host | String | | -| kubernetes.ports | Port[] | | -| kubernetes.service-type | ServiceType | | ClusterIP -| kubernetes.pvc-volumes | PersistentVolumeClaimVolume[] | | -| kubernetes.secret-volumes | SecretVolume[] | | -| kubernetes.config-map-volumes | ConfigMapVolume[] | | -| kubernetes.git-repo-volumes | GitRepoVolume[] | | -| kubernetes.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | -| kubernetes.azure-disk-volumes | AzureDiskVolume[] | | -| kubernetes.azure-file-volumes | AzureFileVolume[] | | -| kubernetes.mounts | Mount[] | | -| kubernetes.image-pull-policy | ImagePullPolicy | | IfNotPresent -| kubernetes.image-pull-secrets | String[] | | -| kubernetes.liveness-probe | Probe | | -| kubernetes.readiness-probe | Probe | | -| kubernetes.sidecars | Container[] | | -| kubernetes.expose | boolean | | false -| kubernetes.docker-file | String | | Dockerfile -| kubernetes.registry | String | | -| kubernetes.auto-deploy-enabled | boolean | | false +| Property | Type | Description | Default Value +| kubernetes.group | String | | +| kubernetes.name | String | | +| kubernetes.version | String | | +| kubernetes.init-containers | Container[] | | +| kubernetes.labels | Label[] | | +| kubernetes.annotations | Annotation[] | | +| kubernetes.env-vars | Env[] | | +| kubernetes.working-dir | String | | +| kubernetes.command | String[] | | +| kubernetes.arguments | String[] | | +| kubernetes.replicas | int | | 1 +| kubernetes.service-account | String | | +| kubernetes.host | String | | +| kubernetes.ports | Port[] | | +| kubernetes.service-type | ServiceType | | ClusterIP +| kubernetes.pvc-volumes | PersistentVolumeClaimVolume[] | | +| kubernetes.secret-volumes | SecretVolume[] | | +| kubernetes.config-map-volumes | ConfigMapVolume[] | | +| kubernetes.git-repo-volumes | GitRepoVolume[] | | +| kubernetes.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | +| kubernetes.azure-disk-volumes | AzureDiskVolume[] | | +| kubernetes.azure-file-volumes | AzureFileVolume[] | | +| kubernetes.mounts | Mount[] | | +| kubernetes.image-pull-policy | ImagePullPolicy | | IfNotPresent +| kubernetes.image-pull-secrets | String[] | | +| kubernetes.liveness-probe | Probe | | ( see Probe ) +| kubernetes.readiness-probe | Probe | | ( see Probe ) +| kubernetes.sidecars | Container[] | | +| kubernetes.expose | boolean | | false +| kubernetes.headless | boolean | | false +| kubernetes.auto-deploy-enabled | boolean | | false |==== Properties that use non standard types, can be referenced by expanding the property. @@ -287,40 +286,39 @@ Below you will find tables describing all available types. .Probe |==== -| Property | Type | Description | Default Value -|---------------------+--------+-------------+--------------- -| http-action-path | String | | -| exec-action | String | | +| Property | Type | Description | Default Value +| http-action-path | String | | +| exec-action | String | | | tcp-socket-action | String | | | initial-delay-seconds | int | | 0 -| period-seconds | int | | 30 -| timeout-seconds | int | | 10 +| period-seconds | int | | 30 +| timeout-seconds | int | | 10 |==== .Port |==== -| Property | Type | Description | Default Value -| name | String | | -| container-port | int | | -| hostPort | int | | 0 -| path | String | | / -| protocol | Protocol | | TCP +| Property | Type | Description | Default Value +| name | String | | +| container-port | int | | +| host-port | int | | 0 +| path | String | | / +| protocol | Protocol | | TCP |==== .Container |==== -| Property | Type | Description | Default Value -| image | String | | -| name | String | | -| env-vars | Env[] | | -| working-dir | String | | -| command | String[] | | -| arguments | String[] | | -| ports | Port[] | | -| mounts | Mount[] | | +| Property | Type | Description | Default Value +| image | String | | +| name | String | | +| env-vars | Env[] | | +| working-dir | String | | +| command | String[] | | +| arguments | String[] | | +| ports | Port[] | | +| mounts | Mount[] | | | image-pull-policy | ImagePullPolicy | | IfNotPresent -| liveness-probe | Probe | | -| readiness-probe | Probe | | +| liveness-probe | Probe | | +| readiness-probe | Probe | | |==== @@ -328,39 +326,39 @@ Below you will find tables describing all available types. .Mount |==== -| Property | Type | Description | Default Value -| name | String | | -| path | String | | +| Property | Type | Description | Default Value +| name | String | | +| path | String | | | sub-path | String | | | read-only | boolean | | false |==== .ConfigMapVolume |==== -| Property | Type | Description | Default Value -| volume-name | String | | +| Property | Type | Description | Default Value +| volume-name | String | | | config-map-name | String | | -| default-mode | int | | 384 -| optional | boolean | | false +| default-mode | int | | 384 +| optional | boolean | | false |==== .SecretVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | | secret-name | String | | | default-mode | int | | 384 -| optional | boolean | | false +| optional | boolean | | false |==== .AzureDiskVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | | disk-name | String | | | disk-uri | String | | -| kind | String | | Managed +| kind | String | | Managed | caching-mode | String | | ReadWrite | fs-type | String | | ext4 | read-only | boolean | | false @@ -368,33 +366,33 @@ Below you will find tables describing all available types. .AwsElasticBlockStoreVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | | volume-id | String | | -| partition | int | | +| partition | int | | | fs-type | String | | ext4 | read-only | boolean | | false |==== .GitRepoVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | -| repository | String | | -| directory | String | | -| revision | String | | +| repository | String | | +| directory | String | | +| revision | String | | |==== .PersistentVolumeClaimVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | | claim-name | String | | | read-only | boolean | | false |==== .AzureFileVolume |==== -| Property | Type | Description | Default Value +| Property | Type | Description | Default Value | volume-name | String | | | share-name | String | | | secret-name | String | | @@ -405,11 +403,11 @@ Below you will find tables describing all available types. .Docker |==== -| Property | Type | Description | Default Value -| docker-file | String | | Dockerfile -| registry | String | | | -| auto-push-enabled | boolean | | false -| auto-build-enabled | boolean | | false +| Property | Type | Description | Default Value +| docker.docker-file | String | | Dockerfile +| docker.registry | String | | +| docker.auto-push-enabled | boolean | | false +| docker.auto-build-enabled | boolean | | false |==== === OpenShift support @@ -432,48 +430,51 @@ The OpenShift resources can be customized in a similar approach with Kubernetes. .Openshift |==== -| Property | Type | Description | Default Value -| openshift.group | String | | -| openshift.name | String | | -| openshift.version | String | | -| openshift.init-containers | Container[] | | -| openshift.labels | Label[] | | -| openshift.annotations | Annotation[] | | -| openshift.env-vars | Env[] | | -| openshift.working-dir | String | | -| openshift.command | String[] | | -| openshift.arguments | String[] | | -| openshift.replicas | int | | 1 -| openshift.service-account | String | | -| openshift.host | String | | -| openshift.ports | Port[] | | -| openshift.service-type | ServiceType | | ClusterIP -| openshift.pvc-volumes | PersistentVolumeClaimVolume[] | | -| openshift.secret-volumes | SecretVolume[] | | -| openshift.config-map-volumes | ConfigMapVolume[] | | -| openshift.git-repo-volumes | GitRepoVolume[] | | -| openshift.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | -| openshift.azure-disk-volumes | AzureDiskVolume[] | | -| openshift.azure-file-volumes | AzureFileVolume[] | | -| openshift.mounts | Mount[] | | -| openshift.image-pull-policy | ImagePullPolicy | | IfNotPresent -| openshift.image-pull-secrets | String[] | | -| openshift.liveness-probe | Probe | | ( see Probe ) -| openshift.readiness-probe | Probe | | ( see Probe ) -| openshift.sidecars | Container[] | | -| openshift.expose | boolean | | false -| openshift.auto-deploy-enabled | boolean | | false +| Property | Type | Description | Default Value +| openshift.group | String | | +| openshift.name | String | | +| openshift.version | String | | +| openshift.init-containers | Container[] | | +| openshift.labels | Label[] | | +| openshift.annotations | Annotation[] | | +| openshift.env-vars | Env[] | | +| openshift.working-dir | String | | +| openshift.command | String[] | | +| openshift.arguments | String[] | | +| openshift.replicas | int | | 1 +| openshift.service-account | String | | +| openshift.host | String | | +| openshift.ports | Port[] | | +| openshift.service-type | ServiceType | | ClusterIP +| openshift.pvc-volumes | PersistentVolumeClaimVolume[] | | +| openshift.secret-volumes | SecretVolume[] | | +| openshift.config-map-volumes | ConfigMapVolume[] | | +| openshift.git-repo-volumes | GitRepoVolume[] | | +| openshift.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | +| openshift.azure-disk-volumes | AzureDiskVolume[] | | +| openshift.azure-file-volumes | AzureFileVolume[] | | +| openshift.mounts | Mount[] | | +| openshift.image-pull-policy | ImagePullPolicy | | IfNotPresent +| openshift.image-pull-secrets | String[] | | +| openshift.liveness-probe | Probe | | ( see Probe ) +| openshift.readiness-probe | Probe | | ( see Probe ) +| openshift.sidecars | Container[] | | +| openshift.expose | boolean | | false +| openshift.headless | boolean | | false +| openshift.auto-deploy-enabled | boolean | | false |==== .S2i |==== -| Property | Type | Description | Default Value -| s2i.enabled | boolean | | true -| s2i.registry | String | | -| s2i.builder-image | String | | fabric8/s2i-java:2.3 -| s2i.build-env-vars | Env[] | | -| s2i.auto-push-enabled | boolean | | false -| s2i.auto-build-enabled | boolean | | false +| Property | Type | Description | Default Value +| s2i.enabled | boolean | | true +| s2i.docker-file | String | | Dockerfile +| s2i.registry | String | | +| s2i.builder-image | String | | fabric8/s2i-java:2.3 +| s2i.build-env-vars | Env[] | | +| s2i.auto-push-enabled | boolean | | false +| s2i.auto-build-enabled | boolean | | false +| s2i.auto-deploy-enabled | boolean | | false |==== === Knative @@ -530,33 +531,32 @@ The generated service can be customized using the following properties: .Knative |==== -| Property | Type | Description | Default Value -| knative.group | String | | -| knative.name | String | | -| knative.version | String | | -| knative.labels | Label[] | | -| knative.annotations | Annotation[] | | -| knative.env-vars | Env[] | | -| knative.working-dir | String | | -| knative.command | String[] | | -| knative.arguments | String[] | | -| knative.service-account | String | | -| knative.host | String | | -| knative.ports | Port[] | | -| knative.service-type | ServiceType | | ClusterIP -| knative.pvc-volumes | PersistentVolumeClaimVolume[] | | -| knative.secret-volumes | SecretVolume[] | | -| knative.config-map-volumes | ConfigMapVolume[] | | -| knative.git-repo-volumes | GitRepoVolume[] | | -| knative.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | -| knative.azure-disk-volumes | AzureDiskVolume[] | | -| knative.azure-file-volumes | AzureFileVolume[] | | -| knative.mounts | Mount[] | | -| knative.image-pull-policy | ImagePullPolicy | | IfNotPresent -| knative.image-pull-secrets | String[] | | -| knative.liveness-probe | Probe | | ( see Probe ) -| knative.readiness-probe | Probe | | ( see Probe ) -| knative.sidecars | Container[] | | -| knative.expose | boolean | | false -| knative.auto-deploy-enabled | boolean | | false -|==== \ No newline at end of file +| Property | Type | Description | Default Value +| knative.group | String | | +| knative.name | String | | +| knative.version | String | | +| knative.labels | Label[] | | +| knative.annotations | Annotation[] | | +| knative.env-vars | Env[] | | +| knative.working-dir | String | | +| knative.command | String[] | | +| knative.arguments | String[] | | +| knative.service-account | String | | +| knative.host | String | | +| knative.ports | Port[] | | +| knative.service-type | ServiceType | | ClusterIP +| knative.pvc-volumes | PersistentVolumeClaimVolume[] | | +| knative.secret-volumes | SecretVolume[] | | +| knative.config-map-volumes | ConfigMapVolume[] | | +| knative.git-repo-volumes | GitRepoVolume[] | | +| knative.aws-elastic-block-store-volumes | AwsElasticBlockStoreVolume[] | | +| knative.azure-disk-volumes | AzureDiskVolume[] | | +| knative.azure-file-volumes | AzureFileVolume[] | | +| knative.mounts | Mount[] | | +| knative.image-pull-policy | ImagePullPolicy | | IfNotPresent +| knative.image-pull-secrets | String[] | | +| knative.liveness-probe | Probe | | ( see Probe ) +| knative.readiness-probe | Probe | | ( see Probe ) +| knative.sidecars | Container[] | | +| knative.expose | boolean | | false +|==== From 1e353ec9b6a83c411f530cf68e1f6b9efd1842d7 Mon Sep 17 00:00:00 2001 From: kdnakt Date: Wed, 8 Jan 2020 00:29:11 +0900 Subject: [PATCH 485/602] docs: fix link --- docs/src/main/asciidoc/mailer.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index 9dcb2b8c4e663..b5510fd691a3b 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -310,7 +310,7 @@ By default both the mailer and Gmail default to `XOAUTH2` which requires registe == Using SSL with native executables Note that if you enable SSL for the mailer and you want to build a native executable, you will need to enable the SSL support. -Please refer to the native-and-ssl[Using SSL With Native Executables] guide for more information. +Please refer to the link:native-and-ssl[Using SSL With Native Executables] guide for more information. == Using the underlying Vert.x Mail Client From 5eb7bd28f879348734c7527cde9738dab39a3607 Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 7 Jan 2020 14:22:46 +0100 Subject: [PATCH 486/602] Extending smoke test for SR context propagation integration. --- .../quarkus/context/test/ContextEndpoint.java | 76 ++++++++++++++++++- ...java => SimpleContextPropagationTest.java} | 45 +++++++++-- .../test/customContext/CustomContext.java | 22 ++++++ .../customContext/CustomContextProvider.java | 46 +++++++++++ .../test/customContext/CustomContextTest.java | 48 ++++++++++++ 5 files changed, 225 insertions(+), 12 deletions(-) rename extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/{ContextUnitTest.java => SimpleContextPropagationTest.java} (67%) create mode 100644 extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContext.java create mode 100644 extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextProvider.java create mode 100644 extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextTest.java diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextEndpoint.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextEndpoint.java index f10a430828518..c6d91bfffcf08 100644 --- a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextEndpoint.java +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextEndpoint.java @@ -57,6 +57,17 @@ public CompletionStage resteasyTest(@Context UriInfo uriInfo) { }); } + @GET + @Path("/resteasy-tc") + public CompletionStage resteasyThreadContextTest(@Context UriInfo uriInfo) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + CompletableFuture ret = allTc.withContextCapture(CompletableFuture.completedFuture("OK")); + return ret.thenApplyAsync(text -> { + uriInfo.getAbsolutePath(); + return text; + }, executor); + } + @GET @Path("/servlet") public CompletionStage servletTest(@Context UriInfo uriInfo) { @@ -68,13 +79,12 @@ public CompletionStage servletTest(@Context UriInfo uriInfo) { } @GET - @Path("/thread-context") - public CompletionStage threadContextTest(@Context UriInfo uriInfo) { + @Path("/servlet-tc") + public CompletionStage servletThreadContextTest(@Context UriInfo uriInfo) { ExecutorService executor = Executors.newSingleThreadExecutor(); - CompletableFuture ret = allTc.withContextCapture(CompletableFuture.completedFuture("OK")); return ret.thenApplyAsync(text -> { - uriInfo.getAbsolutePath(); + servletRequest.getContentType(); return text; }, executor); } @@ -93,6 +103,22 @@ public CompletionStage arcTest() { }); } + @GET + @Path("/arc-tc") + public CompletionStage arcThreadContextTest() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + Assert.assertTrue(Arc.container().instance(RequestBean.class).isAvailable()); + RequestBean instance = Arc.container().instance(RequestBean.class).get(); + String previousValue = instance.callMe(); + CompletableFuture ret = allTc.withContextCapture(CompletableFuture.completedFuture("OK")); + return ret.thenApplyAsync(text -> { + RequestBean instance2 = Arc.container().instance(RequestBean.class).get(); + Assertions.assertEquals(previousValue, instance2.callMe()); + return text; + }, executor); + } + @GET @Path("/noarc") public CompletionStage noarcTest() { @@ -108,6 +134,22 @@ public CompletionStage noarcTest() { }); } + @GET + @Path("/noarc-tc") + public CompletionStage noarcThreadContextTest() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ThreadContext tc = ThreadContext.builder().cleared(ThreadContext.CDI).build(); + Assert.assertTrue(Arc.container().instance(RequestBean.class).isAvailable()); + RequestBean instance = Arc.container().instance(RequestBean.class).get(); + String previousValue = instance.callMe(); + CompletableFuture ret = tc.withContextCapture(CompletableFuture.completedFuture("OK")); + return ret.thenApplyAsync(text -> { + RequestBean instance2 = Arc.container().instance(RequestBean.class).get(); + Assertions.assertNotEquals(previousValue, instance2.callMe()); + return text; + }, executor); + } + @Inject TransactionalBean txBean; @@ -136,6 +178,32 @@ public CompletionStage transactionTest() throws SystemException { }); } + @Transactional + @GET + @Path("/transaction-tc") + public CompletionStage transactionThreadContextTest() throws SystemException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + CompletableFuture ret = allTc.withContextCapture(CompletableFuture.completedFuture("OK")); + + ContextEntity entity = new ContextEntity(); + entity.name = "Stef"; + entity.persist(); + Transaction t1 = Panache.getTransactionManager().getTransaction(); + Assertions.assertNotNull(t1); + + return ret.thenApplyAsync(text -> { + Assertions.assertEquals(1, ContextEntity.count()); + Transaction t2; + try { + t2 = Panache.getTransactionManager().getTransaction(); + } catch (SystemException e) { + throw new RuntimeException(e); + } + Assertions.assertEquals(t1, t2); + return text; + }, executor); + } + @Transactional @GET @Path("/transaction2") diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextUnitTest.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/SimpleContextPropagationTest.java similarity index 67% rename from extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextUnitTest.java rename to extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/SimpleContextPropagationTest.java index 8430cf9be192b..3d2be96a7836b 100644 --- a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/ContextUnitTest.java +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/SimpleContextPropagationTest.java @@ -12,7 +12,12 @@ import io.quarkus.test.QuarkusUnitTest; import io.restassured.RestAssured; -public class ContextUnitTest { +/** + * Tests simple context propagation of basic contexts (Arc, RestEasy, server, transaction) via either + * {@link org.eclipse.microprofile.context.ManagedExecutor} (ME) or + * {@link org.eclipse.microprofile.context.ThreadContext} (TC). + */ +public class SimpleContextPropagationTest { private static Class[] testClasses = { ContextEndpoint.class, RequestBean.class, ContextEntity.class, TestResources.class, CompletionExceptionMapper.class, TransactionalBean.class @@ -25,37 +30,55 @@ public class ContextUnitTest { .addAsResource("application.properties")); @Test() - public void testRESTEasyContextPropagation() { + public void testRESTEasyMEContextPropagation() { RestAssured.when().get("/context/resteasy").then() .statusCode(Response.Status.OK.getStatusCode()); } @Test() - public void testServletContextPropagation() { + public void testRESTEasyTCContextPropagation() { + RestAssured.when().get("/context/resteasy-tc").then() + .statusCode(Response.Status.OK.getStatusCode()); + } + + @Test() + public void testServletMEContextPropagation() { RestAssured.when().get("/context/servlet").then() .statusCode(Response.Status.OK.getStatusCode()); } @Test() - public void testThreadContextPropagation() { - RestAssured.when().get("/context/thread-context").then() + public void testServletTCContextPropagation() { + RestAssured.when().get("/context/servlet-tc").then() .statusCode(Response.Status.OK.getStatusCode()); } @Test() - public void testArcContextPropagation() { + public void testArcMEContextPropagation() { RestAssured.when().get("/context/arc").then() .statusCode(Response.Status.OK.getStatusCode()); } @Test() - public void testArcContextPropagationDisabled() { + public void testArcTCContextPropagation() { + RestAssured.when().get("/context/arc-tc").then() + .statusCode(Response.Status.OK.getStatusCode()); + } + + @Test() + public void testArcMEContextPropagationDisabled() { RestAssured.when().get("/context/noarc").then() .statusCode(Response.Status.OK.getStatusCode()); } @Test() - public void testTransactionContextPropagation() { + public void testArcTCContextPropagationDisabled() { + RestAssured.when().get("/context/noarc-tc").then() + .statusCode(Response.Status.OK.getStatusCode()); + } + + @Test() + public void testTransactionMEContextPropagation() { RestAssured.when().get("/context/transaction").then() .statusCode(Response.Status.OK.getStatusCode()); RestAssured.when().get("/context/transaction2").then() @@ -66,6 +89,12 @@ public void testTransactionContextPropagation() { .statusCode(Response.Status.OK.getStatusCode()); } + @Test + public void testTransactionTCContextPropagation() { + RestAssured.when().get("/context/transaction-tc").then() + .statusCode(Response.Status.OK.getStatusCode()); + } + @Test() public void testTransactionNewContextPropagation() { RestAssured.when().get("/context/transaction-new").then() diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContext.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContext.java new file mode 100644 index 0000000000000..f7400ba15f8ec --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContext.java @@ -0,0 +1,22 @@ +package io.quarkus.context.test.customContext; + +/** + * Custom context for test purposes. + * Store some value (String) in a thread local variable. + */ +public class CustomContext { + + public static final String NAME = "MyContext"; + private static ThreadLocal context = ThreadLocal.withInitial(() -> ""); + + private CustomContext() { + } + + public static String get() { + return context.get(); + } + + public static void set(String label) { + context.set(label); + } +} diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextProvider.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextProvider.java new file mode 100644 index 0000000000000..7147782f03119 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextProvider.java @@ -0,0 +1,46 @@ +package io.quarkus.context.test.customContext; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.microprofile.context.spi.ThreadContextController; +import org.eclipse.microprofile.context.spi.ThreadContextProvider; +import org.eclipse.microprofile.context.spi.ThreadContextSnapshot; + +/** + * Simple context provider for {@link CustomContext} + */ +public class CustomContextProvider implements ThreadContextProvider { + + public ThreadContextSnapshot clearedContext(Map props) { + return snapshot(""); + } + + public ThreadContextSnapshot currentContext(Map props) { + return snapshot(CustomContext.get()); + } + + public String getThreadContextType() { + return CustomContext.NAME; + } + + private ThreadContextSnapshot snapshot(String label) { + return () -> { + String labelToRestore = CustomContext.get(); + AtomicBoolean restored = new AtomicBoolean(); + + // Construct an instance that restores the previous context that was on the thread + // prior to applying the specified 'label' as the new context. + ThreadContextController contextRestorer = () -> { + if (restored.compareAndSet(false, true)) { + CustomContext.set(labelToRestore); + } else { + throw new IllegalStateException(); + } + }; + + CustomContext.set(label); + return contextRestorer; + }; + } +} diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextTest.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextTest.java new file mode 100644 index 0000000000000..d2f026b4fb1f8 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/context/test/customContext/CustomContextTest.java @@ -0,0 +1,48 @@ +package io.quarkus.context.test.customContext; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.inject.Inject; + +import org.eclipse.microprofile.context.ThreadContext; +import org.eclipse.microprofile.context.spi.ThreadContextProvider; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that custom context can be declared and is propagated. + * Note that default TC (and ME) propagate ALL contexts. + */ +public class CustomContextTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(CustomContextTest.class, CustomContext.class, CustomContextProvider.class) + .addAsServiceProvider(ThreadContextProvider.class, CustomContextProvider.class)); + + @Inject + ThreadContext tc; + + @Test + public void testCustomContextPropagation() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + // set something to custom context + CustomContext.set("foo"); + + CompletableFuture ret = tc.withContextCapture(CompletableFuture.completedFuture("void")); + CompletableFuture cfs = ret.thenApplyAsync(text -> { + Assertions.assertEquals("foo", CustomContext.get()); + return null; + }, executor); + cfs.get(); + } +} From 8b72095a75b1c68f126dcf97dd05ae98091956df Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Tue, 7 Jan 2020 19:08:13 +0100 Subject: [PATCH 487/602] Docs typo fixes --- docs/src/main/asciidoc/amazon-lambda-http.adoc | 2 +- docs/src/main/asciidoc/config.adoc | 4 ++-- docs/src/main/asciidoc/context-propagation.adoc | 2 +- docs/src/main/asciidoc/gradle-tooling.adoc | 4 ++-- docs/src/main/asciidoc/maven-tooling.adoc | 4 ++-- docs/src/main/asciidoc/security.adoc | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/main/asciidoc/amazon-lambda-http.adoc b/docs/src/main/asciidoc/amazon-lambda-http.adoc index 2838afd4ce17c..1a1215fa7488e 100644 --- a/docs/src/main/asciidoc/amazon-lambda-http.adoc +++ b/docs/src/main/asciidoc/amazon-lambda-http.adoc @@ -198,7 +198,7 @@ of things you need to do. Take a look at the generate example project to get an 1. Include the `quarkus-amazon-lambda-http` extension as a pom dependency 2. Configure Quarkus build an `uber-jar` -3. If you are doing a native GraalVM build, Amazon requires you to rename your executable to `bootstrap` and zip it up. Notice that the `pom.xml` uses the `maven-assemby-plugin` to perform this requirement. +3. If you are doing a native GraalVM build, Amazon requires you to rename your executable to `bootstrap` and zip it up. Notice that the `pom.xml` uses the `maven-assembly-plugin` to perform this requirement. == Examine sam.yaml diff --git a/docs/src/main/asciidoc/config.adoc b/docs/src/main/asciidoc/config.adoc index 8f11ad81687a4..67ddec34d31e8 100644 --- a/docs/src/main/asciidoc/config.adoc +++ b/docs/src/main/asciidoc/config.adoc @@ -621,7 +621,7 @@ quarkus: === Profile dependent configuration -Providing profile depedent configuration with YAML is done like with properties. +Providing profile dependent configuration with YAML is done like with properties. Just add the `%profile` wrapped in quotation marks before defining the key-value pairs: [source,yaml] @@ -637,7 +637,7 @@ Just add the `%profile` wrapped in quotation marks before defining the key-value === Configuration key conflicts -The Microprofile Configuration specification defines configuration keys as an arbitrary `.`-delimited string. +The MicroProfile Configuration specification defines configuration keys as an arbitrary `.`-delimited string. However, structured formats like YAML naively only support a subset of the possible configuration namespace. For example, consider the two configuration properties `quarkus.http.cors` and `quarkus.http.cors.methods`. One property is the prefix of another, so it may not be immediately evident how to specify both keys in your YAML configuration. diff --git a/docs/src/main/asciidoc/context-propagation.adoc b/docs/src/main/asciidoc/context-propagation.adoc index b18d8002a3989..39fe446e03569 100644 --- a/docs/src/main/asciidoc/context-propagation.adoc +++ b/docs/src/main/asciidoc/context-propagation.adoc @@ -18,7 +18,7 @@ If you write reactive/async code, you have to cut your work into a pipeline of c as well as `ThreadLocal` variables stop working, because your reactive code gets executed in another thread, after the caller ran its `finally` block. -link:https://github.com/eclipse/microprofile-context-propagation[Microprofile Context Propagation] was made to +link:https://github.com/eclipse/microprofile-context-propagation[MicroProfile Context Propagation] was made to make those Quarkus extensions work properly in reactive/async settings. It works by capturing those contextual values that used to be in thread-locals, and restoring them when your code is called. diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index 045394c4e8a05..a7dd013d1e3de 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -233,9 +233,9 @@ In IntelliJ: In a separated terminal or in the embedded terminal, run `./gradlew quarkusDev`. Enjoy! -**Apache Netbeans** +**Apache NetBeans** -In Netbeans: +In NetBeans: 1. Select `File -> Open Project` 2. Select the project root diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index da65d7f8a297e..fd79a46fda63a 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -266,9 +266,9 @@ In IntelliJ: In a separated terminal or in the embedded terminal, run `./mvnw compile quarkus:dev`. Enjoy! -**Apache Netbeans** +**Apache NetBeans** -In Netbeans: +In NetBeans: 1. Select `File -> Open Project` 2. Select the project root diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 30ff0ff77c99a..2bb02bd08efff 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -29,7 +29,7 @@ in order for Quarkus to know how to find the authentication information to check |Provides support for OAuth2 flows using Elytron. This extension will likely be deprecated soon and replaced by a reactive Vert.x version. |link:security-jwt[quarkus-smallrye-jwt] -|A Microprofile JWT implementation that provides support for authenticating using Json Web Tokens. This also allows you to inject the token and claims into the application as per the MP JWT spec. +|A MicroProfile JWT implementation that provides support for authenticating using Json Web Tokens. This also allows you to inject the token and claims into the application as per the MP JWT spec. |link:security-openid-connect[quarkus-oidc] |Provides support for authenticating via an OpenID Connect provider such as Keycloak. From e5657c546377cf0889d573834f3fd97c69941e8d Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 7 Jan 2020 16:11:29 +0100 Subject: [PATCH 488/602] Arc should only emit once log msg per missing class in index. --- .../quarkus/arc/processor/IndexClassLookupUtils.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/IndexClassLookupUtils.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/IndexClassLookupUtils.java index 161e4fddb2cbf..b05604d0386a1 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/IndexClassLookupUtils.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/IndexClassLookupUtils.java @@ -1,5 +1,7 @@ package io.quarkus.arc.processor; +import java.util.HashSet; +import java.util.Set; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; @@ -9,6 +11,9 @@ final class IndexClassLookupUtils { private static final Logger LOGGER = Logger.getLogger(IndexClassLookupUtils.class); + // set of already encountered and logged DotNames that are missing in the index + private static Set alreadyKnown = new HashSet<>(); + private IndexClassLookupUtils() { } @@ -31,10 +36,11 @@ private static ClassInfo lookupClassInIndex(IndexView index, DotName dotName, bo throw new IllegalArgumentException("Cannot lookup class, provided Jandex Index was null."); } ClassInfo info = index.getClassByName(dotName); - if (info == null && withLogging) { - // class not in index, log warning as this may cause the application to blow up or behave weirdly + if (info == null && withLogging && !alreadyKnown.contains(dotName)) { + // class not in index, log info as this may cause the application to blow up or behave weirdly LOGGER.info("Class for name: " + dotName + " was not found in Jandex index. Please ensure the class " + "is part of the index."); + alreadyKnown.add(dotName); } return info; } From 29d88293fe717fc98c8a96b8c8436137d9f211f8 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 6 Jan 2020 20:43:10 +0100 Subject: [PATCH 489/602] ArC - optimize CreationalContext - do not always store all dependent instances in CC - also introduce io.quarkus.arc.InjectableInstance - resolves #5281 --- .../quarkus/arc/processor/BeanGenerator.java | 24 ++++- .../io/quarkus/arc/processor/BuiltinBean.java | 70 +++++++------- .../io/quarkus/arc/processor/DotNames.java | 2 + .../arc/processor/MethodDescriptors.java | 4 + .../arc/processor/ObserverGenerator.java | 2 +- .../io/quarkus/arc/InjectableInstance.java | 27 ++++++ .../io/quarkus/arc/impl/ArcContainerImpl.java | 10 +- .../io/quarkus/arc/impl/BeanManagerImpl.java | 2 +- .../arc/impl/CreationalContextImpl.java | 4 + .../quarkus/arc/impl/InstanceHandleImpl.java | 23 +++-- .../io/quarkus/arc/impl/InstanceImpl.java | 55 ++++++++--- .../DependentCreationalContextTest.java | 91 +++++++++++++++++++ .../test/instance/InjectableInstanceTest.java | 73 +++++++++++++++ 13 files changed, 323 insertions(+), 64 deletions(-) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/dependent/DependentCreationalContextTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/InjectableInstanceTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 6a1ab91a75642..88e81c11029d9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -589,7 +589,7 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be if (BuiltinScope.DEPENDENT.is(injectionPoint.getResolvedBean().getScope()) && (injectionPoint.getResolvedBean() .getAllInjectionPoints().stream() - .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(ip.getRequiredType().name())) + .anyMatch(ip -> BuiltinBean.INJECTION_POINT.hasRawTypeDotName(ip.getRequiredType().name())) || injectionPoint.getResolvedBean().isSynthetic())) { // Injection point resolves to a dependent bean that injects InjectionPoint metadata and so we need to wrap the injectable // reference provider @@ -1417,6 +1417,28 @@ protected void implementGet(BeanInfo bean, ClassCreator beanCreator, String prov MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), get.getThis(), get.getMethodParam(0)); + + // We can optimize if: + // 1) class bean - has no @PreDestroy interceptor and there is no @PreDestroy callback + // 2) producer - there is no disposal method + boolean canBeOptimized = false; + if (bean.isClassBean()) { + canBeOptimized = bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty() + && Beans.getCallbacks(bean.getTarget().get().asClass(), + DotNames.PRE_DESTROY, + bean.getDeployment().getIndex()).isEmpty(); + } else if (bean.isProducerMethod() || bean.isProducerField()) { + canBeOptimized = bean.getDisposer() == null; + } + + if (canBeOptimized) { + // If there is no dependency in the creational context we don't have to store the instance in the CreationalContext + ResultHandle creationalContext = get.checkCast(get.getMethodParam(0), CreationalContextImpl.class); + get.ifNonZero( + get.invokeVirtualMethod(MethodDescriptors.CREATIONAL_CTX_HAS_DEPENDENT_INSTANCES, creationalContext)) + .falseBranch().returnValue(instance); + } + // CreationalContextImpl.addDependencyToParent(this,instance,ctx) get.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_ADD_DEP_TO_PARENT, get.getThis(), instance, get.getMethodParam(0)); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java index 2a39a099983e9..00d742a83861a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java @@ -32,7 +32,7 @@ */ enum BuiltinBean { - INSTANCE(DotNames.INSTANCE, ctx -> { + INSTANCE(ctx -> { ResultHandle qualifiers = BeanGenerator.collectQualifiers(ctx.classOutput, ctx.clazzCreator, ctx.beanDeployment, ctx.constructor, ctx.injectionPoint, @@ -53,8 +53,8 @@ enum BuiltinBean { FieldDescriptor.of(ctx.clazzCreator.getClassName(), ctx.providerName, Supplier.class.getName()), ctx.constructor.getThis(), instanceProviderSupplier); - }, BuiltinBean::isInstanceInjectionPoint), - INJECTION_POINT(DotNames.INJECTION_POINT, ctx -> { + }, DotNames.INSTANCE, DotNames.PROVIDER, DotNames.INJECTABLE_INSTANCE), + INJECTION_POINT(ctx -> { // this.injectionPointProvider1 = () -> new InjectionPointProvider(); ResultHandle injectionPointProvider = ctx.constructor.newInstance( MethodDescriptor.ofConstructor(InjectionPointProvider.class)); @@ -65,8 +65,8 @@ enum BuiltinBean { Supplier.class.getName()), ctx.constructor.getThis(), injectionPointProviderSupplier); - }), - BEAN(DotNames.BEAN, ctx -> { + }, DotNames.INJECTION_POINT), + BEAN(ctx -> { // this.beanProvider1 = () -> new BeanMetadataProvider<>(); if (ctx.targetInfo.kind() != InjectionTargetInfo.TargetKind.BEAN) { throw new IllegalStateException("Invalid injection target info: " + ctx.targetInfo); @@ -83,8 +83,8 @@ enum BuiltinBean { beanProviderSupplier); }, ip -> { return isCdiAndRawTypeMatches(ip, DotNames.BEAN) && ip.hasDefaultedQualifier(); - }), - INTERCEPTED_BEAN(DotNames.BEAN, ctx -> { + }, DotNames.BEAN), + INTERCEPTED_BEAN(ctx -> { if (!(ctx.targetInfo instanceof InterceptorInfo)) { throw new IllegalStateException("Invalid injection target info: " + ctx.targetInfo); } @@ -102,8 +102,8 @@ enum BuiltinBean { return isCdiAndRawTypeMatches(ip, DotNames.BEAN) && !ip.hasDefaultedQualifier() && ip.getRequiredQualifiers().size() == 1 && ip.getRequiredQualifiers().iterator().next().name().equals(DotNames.INTERCEPTED); - }), - BEAN_MANAGER(DotNames.BEAN_MANAGER, ctx -> { + }, DotNames.BEAN), + BEAN_MANAGER(ctx -> { ResultHandle beanManagerProvider = ctx.constructor.newInstance( MethodDescriptor.ofConstructor(BeanManagerProvider.class)); ResultHandle injectionPointProviderSupplier = ctx.constructor.newInstance( @@ -113,8 +113,8 @@ enum BuiltinBean { Supplier.class.getName()), ctx.constructor.getThis(), injectionPointProviderSupplier); - }), - EVENT(DotNames.EVENT, ctx -> { + }, DotNames.BEAN_MANAGER), + EVENT(ctx -> { ResultHandle qualifiers = ctx.constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); if (!ctx.injectionPoint.getRequiredQualifiers().isEmpty()) { @@ -147,8 +147,8 @@ enum BuiltinBean { FieldDescriptor.of(ctx.clazzCreator.getClassName(), ctx.providerName, Supplier.class.getName()), ctx.constructor.getThis(), eventProviderSupplier); - }), - RESOURCE(DotNames.OBJECT, ctx -> { + }, DotNames.EVENT), + RESOURCE(ctx -> { ResultHandle annotations = ctx.constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); // For a resource field the required qualifiers contain all annotations declared on the field @@ -173,23 +173,23 @@ enum BuiltinBean { FieldDescriptor.of(ctx.clazzCreator.getClassName(), ctx.providerName, Supplier.class.getName()), ctx.constructor.getThis(), resourceProviderSupplier); - }, ip -> ip.getKind() == InjectionPointKind.RESOURCE), - EVENT_METADATA(DotNames.EVENT_METADATA, ctx -> { - }), + }, ip -> ip.getKind() == InjectionPointKind.RESOURCE, DotNames.OBJECT), + EVENT_METADATA(ctx -> { + }, DotNames.EVENT_METADATA), ; - private final DotName rawTypeDotName; + private final DotName[] rawTypeDotNames; private final Generator generator; private final Predicate matcher; - BuiltinBean(DotName rawTypeDotName, Generator generator) { - this(rawTypeDotName, generator, ip -> isCdiAndRawTypeMatches(ip, rawTypeDotName)); + BuiltinBean(Generator generator, DotName... rawTypeDotNames) { + this(generator, ip -> isCdiAndRawTypeMatches(ip, rawTypeDotNames), rawTypeDotNames); } - BuiltinBean(DotName rawTypeDotName, Generator generator, Predicate matcher) { - this.rawTypeDotName = rawTypeDotName; + BuiltinBean(Generator generator, Predicate matcher, DotName... rawTypeDotNames) { + this.rawTypeDotNames = rawTypeDotNames; this.generator = generator; this.matcher = matcher; } @@ -198,8 +198,17 @@ boolean matches(InjectionPointInfo injectionPoint) { return matcher.test(injectionPoint); } - DotName getRawTypeDotName() { - return rawTypeDotName; + DotName[] getRawTypeDotNames() { + return rawTypeDotNames; + } + + boolean hasRawTypeDotName(DotName name) { + for (DotName rawTypeDotName : rawTypeDotNames) { + if (rawTypeDotName.equals(name)) { + return true; + } + } + return false; } Generator getGenerator() { @@ -251,19 +260,16 @@ interface Generator { } - private static boolean isCdiAndRawTypeMatches(InjectionPointInfo injectionPoint, DotName rawTypeDotName) { + private static boolean isCdiAndRawTypeMatches(InjectionPointInfo injectionPoint, DotName... rawTypeDotNames) { if (injectionPoint.getKind() != InjectionPointKind.CDI) { return false; } - return rawTypeDotName.equals(injectionPoint.getRequiredType().name()); - } - - private static boolean isInstanceInjectionPoint(InjectionPointInfo injectionPoint) { - if (injectionPoint.getKind() != InjectionPointKind.CDI) { - return false; + for (DotName rawTypeDotName : rawTypeDotNames) { + if (rawTypeDotName.equals(injectionPoint.getRequiredType().name())) { + return true; + } } - return DotNames.INSTANCE.equals(injectionPoint.getRequiredType().name()) - || DotNames.PROVIDER.equals(injectionPoint.getRequiredType().name()); + return false; } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java index b0112a6e08935..c586104c6b17f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DotNames.java @@ -2,6 +2,7 @@ import io.quarkus.arc.AlternativePriority; import io.quarkus.arc.DefaultBean; +import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.impl.ComputingCache; import java.util.Optional; import javax.annotation.PostConstruct; @@ -55,6 +56,7 @@ public final class DotNames { public static final DotName POST_CONSTRUCT = create(PostConstruct.class); public static final DotName PRE_DESTROY = create(PreDestroy.class); public static final DotName INSTANCE = create(Instance.class); + public static final DotName INJECTABLE_INSTANCE = create(InjectableInstance.class); public static final DotName PROVIDER = create(Provider.class); public static final DotName INJECTION_POINT = create(InjectionPoint.class); public static final DotName INTERCEPTOR = create(Interceptor.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index cfbc7c2a10472..e639496002ce3 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -186,6 +186,10 @@ final class MethodDescriptors { SubclassMethodMetadata.class, List.class, Method.class, Set.class); + static final MethodDescriptor CREATIONAL_CTX_HAS_DEPENDENT_INSTANCES = MethodDescriptor.ofMethod( + CreationalContextImpl.class, + "hasDependentInstances", boolean.class); + private MethodDescriptors() { } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java index 5ddcdfac8989b..1f55126c692fc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverGenerator.java @@ -336,7 +336,7 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC annotationLiterals, observer)); } else { if (injectionPoint.getResolvedBean().getAllInjectionPoints().stream() - .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(ip.getRequiredType().name()))) { + .anyMatch(ip -> BuiltinBean.INJECTION_POINT.hasRawTypeDotName(ip.getRequiredType().name()))) { // IMPL NOTE: Injection point resolves to a dependent bean that injects InjectionPoint metadata and so we need to wrap the injectable // reference provider ResultHandle requiredQualifiersHandle = BeanGenerator.collectQualifiers(classOutput, observerCreator, diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java new file mode 100644 index 0000000000000..b59c1238a48fd --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java @@ -0,0 +1,27 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import javax.enterprise.inject.Instance; +import javax.enterprise.util.TypeLiteral; + +/** + * Enhanced version of {@link Instance}. + * + * @param + */ +public interface InjectableInstance extends Instance { + + InstanceHandle getHandle(); + + Iterable> handles(); + + @Override + InjectableInstance select(Annotation... qualifiers); + + @Override + InjectableInstance select(Class subtype, Annotation... qualifiers); + + @Override + InjectableInstance select(TypeLiteral subtype, Annotation... qualifiers); + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 90f52867f9097..4f1f9878d692f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -30,6 +30,7 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; @@ -338,8 +339,8 @@ private InstanceHandle instanceHandle(Type type, Annotation... qualifiers return beanInstanceHandle(getBean(type, qualifiers), null); } - InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext, - boolean resetCurrentInjectionPoint) { + static InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext, + boolean resetCurrentInjectionPoint, Consumer destroyLogic) { if (bean != null) { if (parentContext == null && Dependent.class.equals(bean.getScope())) { parentContext = new CreationalContextImpl<>(null); @@ -352,7 +353,8 @@ InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalConte } try { - return new InstanceHandleImpl(bean, bean.get(creationalContext), creationalContext, parentContext); + return new InstanceHandleImpl(bean, bean.get(creationalContext), creationalContext, parentContext, + destroyLogic); } finally { if (resetCurrentInjectionPoint) { InjectionPointProvider.set(prev); @@ -364,7 +366,7 @@ InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalConte } InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { - return beanInstanceHandle(bean, parentContext, true); + return beanInstanceHandle(bean, parentContext, true, null); } @SuppressWarnings("unchecked") diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java index 0ae5c456cbeab..59f9e1ca2338f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/BeanManagerImpl.java @@ -77,7 +77,7 @@ public Object getInjectableReference(InjectionPoint ij, CreationalContext ctx InjectableBean bean = (InjectableBean) resolve(beans); InjectionPoint prev = InjectionPointProvider.set(ij); try { - return ArcContainerImpl.instance().beanInstanceHandle(bean, (CreationalContextImpl) ctx, false).get(); + return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) ctx, false, null).get(); } finally { InjectionPointProvider.set(prev); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CreationalContextImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CreationalContextImpl.java index 48f287bd64fe5..dbab8d8c4ab8f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CreationalContextImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/CreationalContextImpl.java @@ -40,6 +40,10 @@ public void addDependentInstance(InstanceHandle instanceHandle) { dependentInstances.add(instanceHandle); } + public boolean hasDependentInstances() { + return !dependentInstances.isEmpty(); + } + void destroyDependentInstance(Object dependentInstance) { synchronized (dependentInstances) { for (Iterator> iterator = dependentInstances.iterator(); iterator.hasNext();) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java index a2fb2cf56090b..a5ba3dadcce69 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java @@ -4,6 +4,7 @@ import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InstanceHandle; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; @@ -20,29 +21,27 @@ public static final InstanceHandle unavailable() { return (InstanceHandle) UNAVAILABLE; } - static final InstanceHandleImpl UNAVAILABLE = new InstanceHandleImpl(null, null, null, null); + static final InstanceHandleImpl UNAVAILABLE = new InstanceHandleImpl(null, null, null, null, null); private final InjectableBean bean; - private final T instance; - private final CreationalContext creationalContext; - private final CreationalContext parentCreationalContext; - private final AtomicBoolean destroyed; + private final Consumer destroyLogic; InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext) { - this(bean, instance, creationalContext, null); + this(bean, instance, creationalContext, null, null); } InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext, - CreationalContext parentCreationalContext) { + CreationalContext parentCreationalContext, Consumer destroyLogic) { this.bean = bean; this.instance = instance; this.creationalContext = creationalContext; this.parentCreationalContext = parentCreationalContext; this.destroyed = new AtomicBoolean(false); + this.destroyLogic = destroyLogic; } @Override @@ -61,10 +60,14 @@ public InjectableBean getBean() { @Override public void destroy() { if (instance != null && destroyed.compareAndSet(false, true)) { - if (bean.getScope().equals(Dependent.class)) { - destroyInternal(); + if (destroyLogic != null) { + destroyLogic.accept(instance); } else { - Arc.container().getActiveContext(bean.getScope()).destroy(bean); + if (bean.getScope().equals(Dependent.class)) { + destroyInternal(); + } else { + Arc.container().getActiveContext(bean.getScope()).destroy(bean); + } } } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java index c718a8b397161..18804b2f30b7b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceImpl.java @@ -4,6 +4,8 @@ import io.quarkus.arc.ClientProxy; import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.impl.CurrentInjectionPointProvider.InjectionPointImpl; import java.lang.annotation.Annotation; import java.lang.reflect.Member; @@ -17,7 +19,6 @@ import java.util.Set; import javax.enterprise.context.ContextNotActiveException; import javax.enterprise.inject.AmbiguousResolutionException; -import javax.enterprise.inject.Instance; import javax.enterprise.inject.UnsatisfiedResolutionException; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.util.TypeLiteral; @@ -27,7 +28,7 @@ * * @author Martin Kouba */ -class InstanceImpl implements Instance { +public class InstanceImpl implements InjectableInstance { private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[] {}; @@ -75,35 +76,27 @@ public Iterator iterator() { return new InstanceIterator(beans()); } - @SuppressWarnings("unchecked") @Override public T get() { - Set> beans = beans(); - if (beans.isEmpty()) { - throw new UnsatisfiedResolutionException( - "No bean found for required type [" + requiredType + "] and qualifiers [" + requiredQualifiers + "]"); - } else if (beans.size() > 1) { - throw new AmbiguousResolutionException("Beans: " + beans.toString()); - } - return getBeanInstance((InjectableBean) beans.iterator().next()); + return getBeanInstance(bean()); } @Override - public Instance select(Annotation... qualifiers) { + public InjectableInstance select(Annotation... qualifiers) { Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); return new InstanceImpl<>(this, requiredType, newQualifiers); } @Override - public Instance select(Class subtype, Annotation... qualifiers) { + public InjectableInstance select(Class subtype, Annotation... qualifiers) { Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); return new InstanceImpl<>(this, subtype, newQualifiers); } @Override - public Instance select(TypeLiteral subtype, Annotation... qualifiers) { + public InjectableInstance select(TypeLiteral subtype, Annotation... qualifiers) { Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); return new InstanceImpl<>(this, subtype.getType(), newQualifiers); @@ -120,7 +113,7 @@ public boolean isAmbiguous() { } @Override - public void destroy(T instance) { + public void destroy(Object instance) { Objects.requireNonNull(instance); if (instance instanceof ClientProxy) { ClientProxy proxy = (ClientProxy) instance; @@ -135,6 +128,38 @@ public void destroy(T instance) { } } + @Override + public InstanceHandle getHandle() { + return getHandle(bean()); + } + + @SuppressWarnings("unchecked") + @Override + public Iterable> handles() { + return (Iterable>) beans().stream().map(this::getHandle).iterator(); + } + + @SuppressWarnings("unchecked") + private InstanceHandle getHandle(InjectableBean bean) { + return ArcContainerImpl.beanInstanceHandle(bean, (CreationalContextImpl) creationalContext, true, this::destroy); + } + + @SuppressWarnings("unchecked") + private InjectableBean bean() { + Set> beans = beans(); + if (beans.isEmpty()) { + throw new UnsatisfiedResolutionException( + "No bean found for required type [" + requiredType + "] and qualifiers [" + requiredQualifiers + "]"); + } else if (beans.size() > 1) { + throw new AmbiguousResolutionException("Beans: " + beans.toString()); + } + return (InjectableBean) beans.iterator().next(); + } + + public boolean hasDependentInstances() { + return creationalContext.hasDependentInstances(); + } + void destroy() { creationalContext.release(); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/dependent/DependentCreationalContextTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/dependent/DependentCreationalContextTest.java new file mode 100644 index 0000000000000..00525509dff12 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/dependent/DependentCreationalContextTest.java @@ -0,0 +1,91 @@ +package io.quarkus.arc.test.contexts.dependent; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.impl.InstanceImpl; +import io.quarkus.arc.test.ArcTestContainer; +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class DependentCreationalContextTest { + + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(NoPreDestroy.class, HasDestroy.class, HasDependency.class, + ProducerNoDisposer.class, ProducerWithDisposer.class, String.class, Boolean.class); + + @Test + public void testCreationalContextOptimization() { + InstanceImpl instance = (InstanceImpl) Arc.container().beanManager().createInstance(); + assertBeanType(instance, NoPreDestroy.class, false); + assertBeanType(instance, HasDestroy.class, true); + assertBeanType(instance, HasDependency.class, true); + // ProducerNoDisposer + assertBeanType(instance, boolean.class, false); + // ProducerWithDisposer + assertBeanType(instance, String.class, true); + } + + void assertBeanType(InstanceImpl instance, Class beanType, boolean shouldBeStored) { + T bean = instance.select(beanType).get(); + assertNotNull(bean); + if (shouldBeStored) { + assertTrue(instance.hasDependentInstances()); + } else { + assertFalse(instance.hasDependentInstances()); + } + instance.destroy(bean); + } + + @Dependent + static class NoPreDestroy { + + } + + @Dependent + static class HasDestroy { + + @PreDestroy + void destroy() { + } + + } + + @Dependent + static class HasDependency { + + @Inject + HasDestroy dep; + + } + + @Dependent + static class ProducerNoDisposer { + + @Produces + Boolean ping() { + return true; + } + + } + + @Dependent + static class ProducerWithDisposer { + + @Produces + String ping() { + return "ok"; + } + + void dispose(@Disposes String ping) { + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/InjectableInstanceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/InjectableInstanceTest.java new file mode 100644 index 0000000000000..6ef02cca94c7d --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/InjectableInstanceTest.java @@ -0,0 +1,73 @@ +package io.quarkus.arc.test.instance; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.impl.InstanceImpl; +import io.quarkus.arc.test.ArcTestContainer; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class InjectableInstanceTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Alpha.class, Washcloth.class); + + @Test + public void testDestroy() { + assertFalse(Washcloth.DESTROYED.get()); + + Arc.container().instance(Alpha.class).get().doSomething(); + assertTrue(Washcloth.DESTROYED.get()); + } + + @Singleton + static class Alpha { + + @Inject + InjectableInstance instance; + + void doSomething() { + try (InstanceHandle handle = instance.getHandle()) { + InjectableBean bean = handle.getBean(); + assertNotNull(bean); + assertEquals(Dependent.class, bean.getScope()); + handle.get().wash(); + + // Washcloth has @PreDestroy - the dependent instance should be there + assertTrue(((InstanceImpl) instance).hasDependentInstances()); + } + + // InstanceHandle.destroy() should remove the instance from the CC of the Instance + assertFalse(((InstanceImpl) instance).hasDependentInstances()); + } + + } + + @Dependent + static class Washcloth { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(false); + + void wash() { + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + + } + +} From 66ad1f5ad724fe0dfa5f9d455a0240b781ae7b95 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 7 Jan 2020 15:31:51 +0100 Subject: [PATCH 490/602] InstanceHandle#destroy - throw ContextNotActiveException if appropriate --- .../java/io/quarkus/arc/impl/InstanceHandleImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java index a5ba3dadcce69..bd832bc8bee10 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InstanceHandleImpl.java @@ -2,9 +2,11 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableContext; import io.quarkus.arc.InstanceHandle; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import javax.enterprise.context.ContextNotActiveException; import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; @@ -66,7 +68,12 @@ public void destroy() { if (bean.getScope().equals(Dependent.class)) { destroyInternal(); } else { - Arc.container().getActiveContext(bean.getScope()).destroy(bean); + InjectableContext context = Arc.container().getActiveContext(bean.getScope()); + if (context == null) { + throw new ContextNotActiveException( + "Cannot destroy instance of " + bean + " - no active context found for: " + bean.getScope()); + } + context.destroy(bean); } } } From a18a1b7d3643c3776009fe5fc012f4432f9099fc Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Tue, 7 Jan 2020 21:53:09 +0100 Subject: [PATCH 491/602] Remove unused Vert.x GraalVM substitution --- .../core/runtime/graal/JdkSubstitutions.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/JdkSubstitutions.java b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/JdkSubstitutions.java index a6413dbfde94e..4e030b0393148 100644 --- a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/JdkSubstitutions.java +++ b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/graal/JdkSubstitutions.java @@ -1,9 +1,7 @@ package io.quarkus.vertx.core.runtime.graal; -import java.io.FileDescriptor; import java.io.IOException; import java.net.URL; -import java.util.function.BooleanSupplier; import java.util.function.Function; import com.oracle.svm.core.annotate.Alias; @@ -76,23 +74,6 @@ final class Target_jdk_internal_loader_URLClassPath { } -@TargetClass(className = "sun.nio.ch.DatagramChannelImpl", onlyWith = GraalVersion19_0.class) -final class Target_sun_nio_ch_DatagramChannelImpl { - - @Substitute - private static void disconnect0(FileDescriptor fd, boolean isIPv6) - throws IOException { - throw new RuntimeException("Unimplemented: sun.nio.ch.DatagramChannelImpl.disconnect0(FileDescriptor, boolean)"); - } -} - -final class GraalVersion19_0 implements BooleanSupplier { - public boolean getAsBoolean() { - final String version = System.getProperty("org.graalvm.version"); - return version.startsWith("19.0."); - } -} - final class Package_jdk_internal_loader implements Function { private static final JDK8OrEarlier JDK_8_OR_EARLIER = new JDK8OrEarlier(); From eacd929e1af0f0acbfeea74054df51f9428ebb0d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2020 20:39:57 +0000 Subject: [PATCH 492/602] Bump mariadb-java-client from 2.5.2 to 2.5.3 Bumps [mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 2.5.2 to 2.5.3. - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/2.5.2...2.5.3) Signed-off-by: dependabot-preview[bot] --- bom/runtime/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 41597206d0ddc..497c4d8faea76 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -108,7 +108,7 @@ 2.3.2 1.4.197 42.2.9 - 2.5.2 + 2.5.3 8.0.18 7.2.1.jre8 10.14.2.0 From 8da2c643e378027c5db9fce849a8faefe1c1f260 Mon Sep 17 00:00:00 2001 From: Pavol Loffay Date: Thu, 19 Dec 2019 17:26:50 +0100 Subject: [PATCH 493/602] =?UTF-8?q?Put=20trace=20context=20into=20MDC=20?= =?UTF-8?q?=F0=9F=8E=85=F0=9F=8E=84=F0=9F=8E=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pavol Loffay --- .../quarkus/jaeger/runtime/JaegerConfig.java | 6 +++ .../runtime/JaegerDeploymentRecorder.java | 2 + .../io/quarkus/jaeger/runtime/MDCScope.java | 48 +++++++++++++++++++ .../jaeger/runtime/MDCScopeManager.java | 24 ++++++++++ .../jaeger/runtime/QuarkusJaegerTracer.java | 16 ++++++- 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScope.java create mode 100644 extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScopeManager.java diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java index 92f3a22ae15ac..8c8cc2f78b9fe 100644 --- a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerConfig.java @@ -111,4 +111,10 @@ public class JaegerConfig { @ConfigItem public Optional senderFactory; + /** + * Whether the trace context should be logged. + */ + @ConfigItem(defaultValue = "true") + public Boolean logTraceContext; + } diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentRecorder.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentRecorder.java index 6fd354b1711b3..63e8f83b77b6d 100644 --- a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentRecorder.java +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentRecorder.java @@ -60,6 +60,8 @@ private void initTracerConfig(JaegerConfig jaeger) { initTracerProperty("JAEGER_TAGS", jaeger.tags, tags -> tags.toString()); initTracerProperty("JAEGER_PROPAGATION", jaeger.propagation, format -> format.toString()); initTracerProperty("JAEGER_SENDER_FACTORY", jaeger.senderFactory, sender -> sender); + initTracerProperty(QuarkusJaegerTracer.LOG_TRACE_CONTEXT, Optional.of(jaeger.logTraceContext), + logTraceContext -> logTraceContext.toString()); } private void initTracerProperty(String property, Optional value, Function accessor) { diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScope.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScope.java new file mode 100644 index 0000000000000..e2e8c65c8867f --- /dev/null +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScope.java @@ -0,0 +1,48 @@ +package io.quarkus.jaeger.runtime; + +import org.jboss.logging.MDC; + +import io.jaegertracing.internal.JaegerSpanContext; +import io.opentracing.Scope; +import io.opentracing.Span; + +/** + * Scope that sets span context into MDC. + */ +public class MDCScope implements Scope { + + /** + * MDC keys + */ + private static final String TRACE_ID = "traceId"; + private static final String SPAN_ID = "spanId"; + private static final String SAMPLED = "sampled"; + + private final Scope wrapped; + + public MDCScope(Scope scope) { + this.wrapped = scope; + if (scope.span().context() instanceof JaegerSpanContext) { + putContext((JaegerSpanContext) scope.span().context()); + } + } + + @Override + public void close() { + wrapped.close(); + MDC.remove(TRACE_ID); + MDC.remove(SPAN_ID); + MDC.remove(SAMPLED); + } + + @Override + public Span span() { + return wrapped.span(); + } + + protected void putContext(JaegerSpanContext spanContext) { + MDC.put(TRACE_ID, spanContext.getTraceId()); + MDC.put(SPAN_ID, String.format("%16x", spanContext.getSpanId())); + MDC.put(SAMPLED, Boolean.toString(spanContext.isSampled())); + } +} diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScopeManager.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScopeManager.java new file mode 100644 index 0000000000000..3c1cedef03266 --- /dev/null +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/MDCScopeManager.java @@ -0,0 +1,24 @@ +package io.quarkus.jaeger.runtime; + +import io.opentracing.Scope; +import io.opentracing.ScopeManager; +import io.opentracing.Span; + +public class MDCScopeManager implements ScopeManager { + + private final ScopeManager wrapped; + + public MDCScopeManager(ScopeManager scopeManager) { + this.wrapped = scopeManager; + } + + @Override + public Scope activate(Span span, boolean finishSpanOnClose) { + return new MDCScope(wrapped.activate(span, finishSpanOnClose)); + } + + @Override + public Scope active() { + return wrapped.active(); + } +} diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/QuarkusJaegerTracer.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/QuarkusJaegerTracer.java index dc5938ef173b9..37811ff44fc74 100644 --- a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/QuarkusJaegerTracer.java +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/QuarkusJaegerTracer.java @@ -6,9 +6,11 @@ import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; +import io.opentracing.util.ThreadLocalScopeManager; public class QuarkusJaegerTracer implements Tracer { + static final String LOG_TRACE_CONTEXT = "JAEGER_LOG_TRACE_CONTEXT"; private static volatile Tracer tracer; @Override @@ -21,13 +23,25 @@ private static Tracer tracer() { synchronized (QuarkusJaegerTracer.class) { if (tracer == null) { tracer = Configuration.fromEnv() - .withMetricsFactory(new QuarkusJaegerMetricsFactory()).getTracer(); + .withMetricsFactory(new QuarkusJaegerMetricsFactory()) + .getTracerBuilder() + .withScopeManager(getScopeManager()) + .build(); } } } return tracer; } + private static ScopeManager getScopeManager() { + ScopeManager scopeManager = new ThreadLocalScopeManager(); + String logTraceContext = System.getProperty(LOG_TRACE_CONTEXT); + if ("true".equals(logTraceContext)) { + scopeManager = new MDCScopeManager(scopeManager); + } + return scopeManager; + } + @Override public SpanBuilder buildSpan(String operationName) { return tracer().buildSpan(operationName); From 3219b1043842eb50b1aa555a4e1642908651d0e2 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 8 Jan 2020 08:22:49 +0100 Subject: [PATCH 494/602] Remove useless version from the reactive * client poms --- extensions/reactive-mysql-client/deployment/pom.xml | 1 - extensions/reactive-pg-client/deployment/pom.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/extensions/reactive-mysql-client/deployment/pom.xml b/extensions/reactive-mysql-client/deployment/pom.xml index 7cc1e4a204669..88633381e87c9 100644 --- a/extensions/reactive-mysql-client/deployment/pom.xml +++ b/extensions/reactive-mysql-client/deployment/pom.xml @@ -39,7 +39,6 @@ io.quarkus quarkus-reactive-mysql-client - ${project.version} diff --git a/extensions/reactive-pg-client/deployment/pom.xml b/extensions/reactive-pg-client/deployment/pom.xml index 2952748f83cd6..ce8f22e7364d8 100644 --- a/extensions/reactive-pg-client/deployment/pom.xml +++ b/extensions/reactive-pg-client/deployment/pom.xml @@ -23,7 +23,6 @@ io.quarkus quarkus-reactive-pg-client - ${project.version} From 5a135f76937ab6cc4da9f01a1e9cf94ec8c44712 Mon Sep 17 00:00:00 2001 From: Irena Kezic Date: Fri, 3 Jan 2020 23:31:20 +0100 Subject: [PATCH 495/602] Add Hibernate Validator dev mode tests --- .../hibernate-validator/deployment/pom.xml | 10 ++ .../test/devmode/ClassLevelConstraint.java | 23 ++++ .../test/devmode/ClassLevelValidator.java | 11 ++ .../test/devmode/DependentTestBean.java | 10 ++ .../DevModeConstraintValidationTest.java | 130 ++++++++++++++++++ .../test/devmode/DevModeTestResource.java | 33 +++++ .../validator/test/devmode/NewConstraint.java | 23 ++++ .../validator/test/devmode/NewTestBean.java | 8 ++ .../validator/test/devmode/NewValidator.java | 11 ++ .../validator/test/devmode/TestBean.java | 16 +++ 10 files changed, 275 insertions(+) create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelConstraint.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelValidator.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DependentTestBean.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeConstraintValidationTest.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeTestResource.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewConstraint.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewTestBean.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewValidator.java create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/TestBean.java diff --git a/extensions/hibernate-validator/deployment/pom.xml b/extensions/hibernate-validator/deployment/pom.xml index 31861e0f2485a..8ce58a436e719 100644 --- a/extensions/hibernate-validator/deployment/pom.xml +++ b/extensions/hibernate-validator/deployment/pom.xml @@ -41,6 +41,16 @@ assertj-core test + + io.quarkus + quarkus-resteasy-jsonb-deployment + test + + + io.rest-assured + rest-assured + test + diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelConstraint.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelConstraint.java new file mode 100644 index 0000000000000..d2a626c8c7e79 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelConstraint.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = { ClassLevelValidator.class }) +@Documented +public @interface ClassLevelConstraint { + + String message() default "My class constraint message"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelValidator.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelValidator.java new file mode 100644 index 0000000000000..60bee5bd083ee --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/ClassLevelValidator.java @@ -0,0 +1,11 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class ClassLevelValidator implements ConstraintValidator { + @Override + public boolean isValid(TestBean bean, ConstraintValidatorContext context) { + return false; + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DependentTestBean.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DependentTestBean.java new file mode 100644 index 0000000000000..2557dcada50f0 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DependentTestBean.java @@ -0,0 +1,10 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import javax.enterprise.context.Dependent; + +@Dependent +public class DependentTestBean { + public String testMethod(/* */ String message) { + return message; + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeConstraintValidationTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeConstraintValidationTest.java new file mode 100644 index 0000000000000..7c43a10fbc555 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeConstraintValidationTest.java @@ -0,0 +1,130 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import static org.hamcrest.Matchers.containsString; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class DevModeConstraintValidationTest { + + @RegisterExtension + static final QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(TestBean.class, + DevModeTestResource.class, ClassLevelConstraint.class, ClassLevelValidator.class, DependentTestBean.class)); + + @Test + public void testClassConstraintHotReplacement() { + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("ok")); + + TEST.modifySourceFile("TestBean.java", + s -> s.replace("// ", "@io.quarkus.hibernate.validator.test.devmode.ClassLevelConstraint")); + + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("My class constraint message")); + } + + @Test + public void testPropertyConstraintHotReplacement() { + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("ok")); + + TEST.modifySourceFile("TestBean.java", s -> s.replace("// ", + "@javax.validation.constraints.NotNull(message=\"My property message\")")); + + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("My property message")); + } + + @Test + public void testMethodConstraintHotReplacement() { + + RestAssured.given() + .when() + .get("/test/mymessage") + .then() + .body(containsString("mymessage")); + + TEST.modifySourceFile("DependentTestBean.java", s -> s.replace("/* */", + "@javax.validation.constraints.Size(max=1, message=\"My method message\")")); + + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .get("/test/mymessage") + .then() + .body(containsString("My method message")); + } + + @Test + public void testNewBeanHotReplacement() { + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("ok")); + + TEST.addSourceFile(NewTestBean.class); + TEST.modifySourceFile("DevModeTestResource.java", s -> s.replace("@Valid TestBean", + "@Valid NewTestBean")); + + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("My new bean message")); + } + + @Test + public void testNewConstraintHotReplacement() { + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("ok")); + + TEST.addSourceFile(NewConstraint.class); + TEST.addSourceFile(NewValidator.class); + TEST.modifySourceFile("TestBean.java", s -> s.replace("// ", + "@NewConstraint")); + + RestAssured.given() + .header("Content-Type", "application/json") + .when() + .body("{}") + .post("/test/validate") + .then() + .body(containsString("My new constraint message")); + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeTestResource.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeTestResource.java new file mode 100644 index 0000000000000..3b2377ee66239 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/DevModeTestResource.java @@ -0,0 +1,33 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import javax.inject.Inject; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/test") +public class DevModeTestResource { + + @Inject + DependentTestBean bean; + + @POST + @Path("/validate") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) + public String validateBean(@Valid TestBean testBean) { + return "ok"; + } + + @GET + @Path("/{message}") + @Produces(MediaType.TEXT_PLAIN) + public String validateCDIBean(@PathParam("message") String message) { + return bean.testMethod(message); + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewConstraint.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewConstraint.java new file mode 100644 index 0000000000000..1f5b8c815ecc0 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewConstraint.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = { NewValidator.class }) +@Documented +public @interface NewConstraint { + + String message() default "My new constraint message"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewTestBean.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewTestBean.java new file mode 100644 index 0000000000000..fed21ad884cd4 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewTestBean.java @@ -0,0 +1,8 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import javax.validation.constraints.NotNull; + +public class NewTestBean { + @NotNull(message = "My new bean message") + public String name; +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewValidator.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewValidator.java new file mode 100644 index 0000000000000..118ab4ce0cd40 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/NewValidator.java @@ -0,0 +1,11 @@ +package io.quarkus.hibernate.validator.test.devmode; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class NewValidator implements ConstraintValidator { + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return false; + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/TestBean.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/TestBean.java new file mode 100644 index 0000000000000..ddd1ec5b1ce88 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/devmode/TestBean.java @@ -0,0 +1,16 @@ +package io.quarkus.hibernate.validator.test.devmode; + +// +public class TestBean { + + public String name; + + // + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From bc0c016d53337508ebd99f32f9f277303005d887 Mon Sep 17 00:00:00 2001 From: dbaert Date: Mon, 6 Jan 2020 10:10:34 +0100 Subject: [PATCH 496/602] Add possibility to override the logging configuration of each handler on category level A --- .../runtime/logging/CategoryConfig.java | 16 +++++ .../io/quarkus/runtime/logging/LogConfig.java | 27 +++++++ .../runtime/logging/LoggingSetupRecorder.java | 72 ++++++++++++++++--- ...gory-configured-handlers-output.properties | 20 ++++++ ...alid-configured-handlers-output.properties | 15 ++++ ...ndlerInvalidDueToMultipleHandlersTest.java | 25 +++++++ .../CategoryConfiguredHandlerTest.java | 61 ++++++++++++++++ docs/src/main/asciidoc/logging.adoc | 21 ++++++ .../io/quarkus/maven/it/GenerateConfigIT.java | 8 +-- 9 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 core/test-extension/deployment/src/main/resources/application-category-configured-handlers-output.properties create mode 100644 core/test-extension/deployment/src/main/resources/application-category-invalid-configured-handlers-output.properties create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerInvalidDueToMultipleHandlersTest.java create mode 100644 core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java index 60c5c4f78f917..a566601ae9905 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/CategoryConfig.java @@ -1,5 +1,8 @@ package io.quarkus.runtime.logging; +import java.util.List; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -11,4 +14,17 @@ public class CategoryConfig { */ @ConfigItem(defaultValue = "inherit") String level; + + /** + * The names of the handlers to link to this category. + */ + @ConfigItem + Optional> handlers; + + /** + * Specify whether or not this logger should send its output to its parent Logger + */ + @ConfigItem(defaultValue = "true") + boolean useParentHandlers; + } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java index 8303b39a9ce3e..fc18133917f23 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java @@ -38,6 +38,33 @@ public final class LogConfig { @ConfigDocSection public Map categories; + /** + * Console handlers. + *

+ * The named console handlers configured here can be linked on one or more categories. + */ + @ConfigItem(name = "handler.console") + @ConfigDocSection + public Map consoleHandlers; + + /** + * File handlers. + *

+ * The named file handlers configured here can be linked on one or more categories. + */ + @ConfigItem(name = "handler.file") + @ConfigDocSection + public Map fileHandlers; + + /** + * Syslog handlers. + *

+ * The named syslog handlers configured here can be linked on one or more categories. + */ + @ConfigItem(name = "handler.syslog") + @ConfigDocSection + public Map syslogHandlers; + /** * Console logging. *

diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index 422ecc7c6590d..0864e8ba04e17 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -6,6 +6,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -73,21 +74,15 @@ public void initializeLogging(LogConfig config, final List entry : categories.entrySet()) { - final String name = entry.getKey(); - final Logger logger = logContext.getLogger(name); - final CategoryConfig categoryConfig = entry.getValue(); - if (!"inherit".equals(categoryConfig.level)) { - logger.setLevelName(categoryConfig.level); - } - } + + ErrorManager errorManager = new OnlyOnceErrorManager(); final Map filters = config.filters; List filterElements = new ArrayList<>(filters.size()); for (Entry entry : filters.entrySet()) { filterElements.add(new LogCleanupFilterElement(entry.getKey(), entry.getValue().ifStartsWith)); } + final ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); - ErrorManager errorManager = new OnlyOnceErrorManager(); if (config.console.enable) { final Handler consoleHandler = configureConsoleHandler(config.console, errorManager, filterElements, @@ -107,6 +102,21 @@ public void initializeLogging(LogConfig config, final List namedHandlers = createNamedHandlers(config, possibleFormatters, errorManager, filterElements); + + for (Map.Entry entry : categories.entrySet()) { + final String name = entry.getKey(); + final Logger categoryLogger = logContext.getLogger(name); + final CategoryConfig categoryConfig = entry.getValue(); + if (!"inherit".equals(categoryConfig.level)) { + categoryLogger.setLevelName(categoryConfig.level); + } + categoryLogger.setUseParentHandlers(categoryConfig.useParentHandlers); + if (categoryConfig.handlers.isPresent()) { + addNamedHandlersToCategory(categoryConfig, namedHandlers, categoryLogger, errorManager); + } + } + for (RuntimeValue> additionalHandler : additionalHandlers) { final Optional optional = additionalHandler.getValue(); if (optional.isPresent()) { @@ -121,6 +131,49 @@ public void initializeLogging(LogConfig config, final List createNamedHandlers(LogConfig config, + List>> possibleFormatters, ErrorManager errorManager, + List filterElements) { + Map namedHandlers = new HashMap<>(); + for (Entry consoleConfigEntry : config.consoleHandlers.entrySet()) { + final Handler consoleHandler = configureConsoleHandler(consoleConfigEntry.getValue(), errorManager, filterElements, + possibleFormatters); + addToNamedHandlers(namedHandlers, consoleHandler, consoleConfigEntry.getKey()); + } + for (Entry fileConfigEntry : config.fileHandlers.entrySet()) { + final Handler fileHandler = configureFileHandler(fileConfigEntry.getValue(), errorManager, filterElements); + addToNamedHandlers(namedHandlers, fileHandler, fileConfigEntry.getKey()); + } + for (Entry sysLogConfigEntry : config.syslogHandlers.entrySet()) { + final Handler syslogHandler = configureSyslogHandler(sysLogConfigEntry.getValue(), errorManager, filterElements); + if (syslogHandler != null) { + addToNamedHandlers(namedHandlers, syslogHandler, sysLogConfigEntry.getKey()); + } + } + return namedHandlers; + } + + private static void addToNamedHandlers(Map namedHandlers, Handler handler, String handlerName) { + if (namedHandlers.containsKey(handlerName)) { + throw new RuntimeException(String.format("Only one handler can be configured with the same name '%s'", + handlerName)); + } + namedHandlers.put(handlerName, handler); + } + + private void addNamedHandlersToCategory(CategoryConfig categoryConfig, Map namedHandlers, + Logger categoryLogger, + ErrorManager errorManager) { + for (String categoryNamedHandler : categoryConfig.handlers.get()) { + if (namedHandlers.get(categoryNamedHandler) != null) { + categoryLogger.addHandler(namedHandlers.get(categoryNamedHandler)); + } else { + errorManager.error(String.format("Handler with name '%s' is linked to a category but not configured.", + categoryNamedHandler), null, ErrorManager.GENERIC_FAILURE); + } + } + } + public void initializeLoggingForImageBuild() { if (ImageInfo.inImageBuildtimeCode()) { final ConsoleHandler handler = new ConsoleHandler(new PatternFormatter( @@ -259,4 +312,5 @@ private static AsyncHandler createAsyncHandler(AsyncConfig asyncConfig, Level le asyncHandler.setLevel(level); return asyncHandler; } + } diff --git a/core/test-extension/deployment/src/main/resources/application-category-configured-handlers-output.properties b/core/test-extension/deployment/src/main/resources/application-category-configured-handlers-output.properties new file mode 100644 index 0000000000000..c260065dd29ab --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-category-configured-handlers-output.properties @@ -0,0 +1,20 @@ +quarkus.log.level=INFO +quarkus.log.console.enable=true +quarkus.log.console.level=WARNING +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n +# Configure a named handler that logs to console +quarkus.log.handler.console."STRUCTURED_LOGGING".enable=true +quarkus.log.handler.console."STRUCTURED_LOGGING".format=%e%n +# Configure a named handler that logs to file +quarkus.log.handler.file."STRUCTURED_LOGGING_FILE".enable=true +quarkus.log.handler.file."STRUCTURED_LOGGING_FILE".format=%e%n +# Configure the category and link the two named handlers to it +quarkus.log.category."io.quarkus.category".level=INFO +quarkus.log.category."io.quarkus.category".handlers=STRUCTURED_LOGGING,STRUCTURED_LOGGING_FILE +# Configure other category and also link one named handler to it +quarkus.log.category."io.quarkus.othercategory".level=INFO +quarkus.log.category."io.quarkus.othercategory".handlers=STRUCTURED_LOGGING +# Configure other category and without linked named handlers +quarkus.log.category."io.quarkus.anothercategory".level=INFO +# Resource path to DSAPublicKey base64 encoded bytes +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/main/resources/application-category-invalid-configured-handlers-output.properties b/core/test-extension/deployment/src/main/resources/application-category-invalid-configured-handlers-output.properties new file mode 100644 index 0000000000000..03c850103ebb4 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-category-invalid-configured-handlers-output.properties @@ -0,0 +1,15 @@ +quarkus.log.level=INFO +quarkus.log.console.enable=true +quarkus.log.console.level=WARNING +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n +# Configure a named handler that logs to console +quarkus.log.handler.console."STRUCTURED_LOGGING".enable=true +quarkus.log.handler.console."STRUCTURED_LOGGING".format=%e%n +# Configure a named handler that logs to file but uses the same name which is not allowed +quarkus.log.handler.file."STRUCTURED_LOGGING".enable=true +quarkus.log.handler.file."STRUCTURED_LOGGING".format=%e%n +# Configure the category and link the two named handlers to it +quarkus.log.category."io.quarkus.category".level=INFO +quarkus.log.category."io.quarkus.category".handlers=STRUCTURED_LOGGING +# Resource path to DSAPublicKey base64 encoded bytes +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerInvalidDueToMultipleHandlersTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerInvalidDueToMultipleHandlersTest.java new file mode 100644 index 0000000000000..337ba7611d80f --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerInvalidDueToMultipleHandlersTest.java @@ -0,0 +1,25 @@ +package io.quarkus.logging; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class CategoryConfiguredHandlerInvalidDueToMultipleHandlersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setExpectedException(RuntimeException.class) + .withConfigurationResource("application-category-invalid-configured-handlers-output.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); + + @Test + public void consoleOutputTest() { + Assertions.fail(); + } + +} diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java new file mode 100644 index 0000000000000..873b17a21350c --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/logging/CategoryConfiguredHandlerTest.java @@ -0,0 +1,61 @@ +package io.quarkus.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.*; + +import org.jboss.logmanager.formatters.PatternFormatter; +import org.jboss.logmanager.handlers.ConsoleHandler; +import org.jboss.logmanager.handlers.DelayedHandler; +import org.jboss.logmanager.handlers.FileHandler; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; + +public class CategoryConfiguredHandlerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-category-configured-handlers-output.properties") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsManifestResource("application.properties", "microprofile-config.properties")); + + @Test + public void consoleOutputTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()).filter(h -> (h instanceof ConsoleHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.WARNING); + + Logger categoryLogger = logManager.getLogger("io.quarkus.category"); + assertThat(categoryLogger).isNotNull(); + assertThat(categoryLogger.getHandlers()).hasSize(2).extracting("class").containsExactlyInAnyOrder(ConsoleHandler.class, + FileHandler.class); + + Logger otherCategoryLogger = logManager.getLogger("io.quarkus.othercategory"); + assertThat(otherCategoryLogger).isNotNull(); + assertThat(otherCategoryLogger.getHandlers()).hasSize(1).extracting("class") + .containsExactlyInAnyOrder(ConsoleHandler.class); + + Logger anotherCategoryLogger = logManager.getLogger("io.quarkus.anothercategory"); + assertThat(anotherCategoryLogger).isNotNull(); + assertThat(anotherCategoryLogger.getHandlers()).isEmpty(); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(PatternFormatter.class); + PatternFormatter patternFormatter = (PatternFormatter) formatter; + assertThat(patternFormatter.getPattern()).isEqualTo("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n"); + } + +} diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 23000e377ba2e..d22eb94267da7 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -20,11 +20,15 @@ include::{generated-dir}/config/quarkus-log-logging-log-config.adoc[opts=optiona Logging is done on a per-category basis. Each category can be independently configured. A configuration which applies to a category will also apply to all sub-categories of that category, unless there is a more specific matching sub-category configuration. +For every category the same settings that are configured on ( console / file / syslog ) apply. +These can also be overridden by attaching a one or more named handlers to a category. See example in <> [cols="".level|INFO footnote:[Some extensions may define customized default log levels for certain categories, in order to reduce log noise by default. Setting the log level in configuration will override any extension-defined log levels.]|The level to use to configure the category named ``. The quotes are necessary. +|quarkus.log.category."".useParentHandlers|true|Specify whether or not this logger should send its output to its parent logger. +|quarkus.log.category."".handlers=[]|empty footnote:[By default the configured category gets the same handlers attached as the one on the root logger.]|The names of the handlers that you want to attach to a specific category. |=== NOTE: The quotes shown in the property name are required as categories normally contain '.' which must @@ -154,6 +158,23 @@ quarkus.log.category."io.quarkus.smallrye.jwt".level=TRACE quarkus.log.category."io.undertow.request.security".level=TRACE ---- +[#category-named-handlers-example] +.Named handlers attached to a category +[source, properties] +---- +# Send output to the console +quarkus.log.file.path=/tmp/trace.log +quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n +# Configure a named handler that logs to console +quarkus.log.handler.console."STRUCTURED_LOGGING".format=%e%n +# Configure a named handler that logs to file +quarkus.log.handler.file."STRUCTURED_LOGGING_FILE".enable=true +quarkus.log.handler.file."STRUCTURED_LOGGING_FILE".format=%e%n +# Configure the category and link the two named handlers to it +quarkus.log.category."io.quarkus.category".level=INFO +quarkus.log.category."io.quarkus.category".handlers=STRUCTURED_LOGGING,STRUCTURED_LOGGING_FILE +---- + == Supported Logging APIs Applications and components may use any of the following APIs for logging, and the logs will be merged: diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java index 93ef550df8e3b..228d356a5c5d1 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/GenerateConfigIT.java @@ -36,16 +36,16 @@ void testAddExtensionWithASingleExtension() throws MavenInvocationException, IOE generateConfig("test.properties"); String file = loadFile("test.properties"); - Assertions.assertTrue(file.contains("#quarkus.log.file.enable")); - Assertions.assertTrue(file.contains("If file logging should be enabled")); + Assertions.assertTrue(file.contains("#quarkus.log.level")); + Assertions.assertTrue(file.contains("The default log level")); Assertions.assertTrue(file.contains("#quarkus.thread-pool.growth-resistance=0")); Assertions.assertTrue(file.contains("The executor growth resistance")); generateConfig("application.properties"); //the existing file should not add properties that already exist file = loadFile("application.properties"); - Assertions.assertTrue(file.contains("quarkus.log.file.enable=false")); - Assertions.assertFalse(file.contains("If file logging should be enabled")); + Assertions.assertTrue(file.contains("quarkus.log.level=INFO")); + Assertions.assertFalse(file.contains("The default log level")); Assertions.assertTrue(file.contains("#quarkus.thread-pool.growth-resistance=0")); Assertions.assertTrue(file.contains("The executor growth resistance")); } From bf6c89cf4e58b018577c3611d3e22a7223764618 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 8 Jan 2020 16:46:57 +1100 Subject: [PATCH 497/602] Allow JAX-RS mapping in web.xml Fixes #6426 --- .../ResteasyServerCommonProcessor.java | 18 ++++++-- .../ResteasyServletMappingBuildItem.java | 19 ++++++++ .../deployment/ResteasyServletProcessor.java | 28 +++++++++++- .../deployment/UndertowBuildStep.java | 36 +++++++++------- .../runtime/UndertowDeploymentRecorder.java | 4 +- .../ServletContextPathBuildItem.java | 0 .../it/undertow/elytron/RootResource.java | 35 +++++++++++++++ .../src/main/resources/META-INF/web.xml | 5 ++- .../it/undertow/elytron/BaseAuthRestTest.java | 43 +++++++++++++++++++ 9 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServletMappingBuildItem.java rename extensions/undertow/{deployment => spi}/src/main/java/io/quarkus/undertow/deployment/ServletContextPathBuildItem.java (100%) create mode 100644 integration-tests/elytron-undertow/src/main/java/io/quarkus/it/undertow/elytron/RootResource.java create mode 100644 integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthRestTest.java diff --git a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index 31cee2d8639bc..b0cb191db3f9b 100755 --- a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; import java.util.stream.Collectors; @@ -161,7 +162,8 @@ public void build( List deploymentCustomizers, JaxrsProvidersToRegisterBuildItem jaxrsProvidersToRegisterBuildItem, CombinedIndexBuildItem combinedIndexBuildItem, - BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) throws Exception { + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, + Optional resteasyServletMappingBuildItem) throws Exception { IndexView index = combinedIndexBuildItem.getIndex(); resource.produce(new NativeImageResourceBuildItem("META-INF/services/javax.ws.rs.client.ClientBuilder")); @@ -196,8 +198,18 @@ public void build( path = applicationPath.value().asString(); appClass = applicationPath.target().asClass().name().toString(); } else { - path = resteasyConfig.path; - appClass = null; + if (resteasyServletMappingBuildItem.isPresent()) { + if (resteasyServletMappingBuildItem.get().getPath().endsWith("/*")) { + path = resteasyServletMappingBuildItem.get().getPath().substring(0, + resteasyServletMappingBuildItem.get().getPath().length() - 1); + } else { + path = resteasyServletMappingBuildItem.get().getPath(); + } + appClass = null; + } else { + path = resteasyConfig.path; + appClass = null; + } } Map scannedResources = new HashMap<>(); diff --git a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServletMappingBuildItem.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServletMappingBuildItem.java new file mode 100644 index 0000000000000..c317682f9f29a --- /dev/null +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServletMappingBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.resteasy.server.common.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item that represents a path mapping from web.xml + */ +public final class ResteasyServletMappingBuildItem extends SimpleBuildItem { + + private final String path; + + public ResteasyServletMappingBuildItem(String path) { + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java index 4b1ed974f01fe..3f93f440d37ca 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyServletProcessor.java @@ -11,6 +11,7 @@ import javax.ws.rs.core.Application; import org.jboss.logging.Logger; +import org.jboss.metadata.web.spec.ServletMappingMetaData; import org.jboss.resteasy.microprofile.config.FilterConfigSourceImpl; import org.jboss.resteasy.microprofile.config.ServletConfigSourceImpl; import org.jboss.resteasy.microprofile.config.ServletContextConfigSourceImpl; @@ -27,9 +28,12 @@ import io.quarkus.resteasy.runtime.ExceptionMapperRecorder; import io.quarkus.resteasy.runtime.ResteasyFilter; import io.quarkus.resteasy.server.common.deployment.ResteasyServerConfigBuildItem; +import io.quarkus.resteasy.server.common.deployment.ResteasyServletMappingBuildItem; import io.quarkus.undertow.deployment.FilterBuildItem; import io.quarkus.undertow.deployment.ServletBuildItem; +import io.quarkus.undertow.deployment.ServletContextPathBuildItem; import io.quarkus.undertow.deployment.ServletInitParamBuildItem; +import io.quarkus.undertow.deployment.WebMetadataBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; /** @@ -51,6 +55,23 @@ public void jaxrsConfig(Optional resteasyServerCo } } + @BuildStep + public ResteasyServletMappingBuildItem webXmlMapping(Optional webMetadataBuildItem) { + if (webMetadataBuildItem.isPresent()) { + List servletMappings = webMetadataBuildItem.get().getWebMetaData().getServletMappings(); + if (servletMappings != null) { + for (ServletMappingMetaData mapping : servletMappings) { + if (JAVAX_WS_RS_APPLICATION.equals(mapping.getServletName())) { + if (!mapping.getUrlPatterns().isEmpty()) { + return new ResteasyServletMappingBuildItem(mapping.getUrlPatterns().iterator().next()); + } + } + } + } + } + return null; + } + @BuildStep public void build( Capabilities capabilities, @@ -60,6 +81,7 @@ public void build( BuildProducer servlet, BuildProducer reflectiveClass, BuildProducer servletInitParameters, + Optional servletContextPathBuildItem, ResteasyInjectionReadyBuildItem resteasyInjectionReady) throws Exception { if (!capabilities.isCapabilityPresent(Capabilities.SERVLET)) { return; @@ -87,7 +109,8 @@ public void build( } for (Entry initParameter : resteasyServerConfig.get().getInitParameters().entrySet()) { - servletInitParameters.produce(new ServletInitParamBuildItem(initParameter.getKey(), initParameter.getValue())); + servletInitParameters + .produce(new ServletInitParamBuildItem(initParameter.getKey(), initParameter.getValue())); } } } @@ -101,6 +124,9 @@ void addServletsToExceptionMapper(List servlets, ExceptionMapp private String getMappingPath(String path) { String mappingPath; + if (path.endsWith("/*")) { + return path; + } if (path.endsWith("/")) { mappingPath = path + "*"; } else { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java index e16605c3b0be1..878f664a47ef7 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildStep.java @@ -267,6 +267,25 @@ public List servletContainerInitializer( return ret; } + @BuildStep() + public ServletContextPathBuildItem contextPath( + ServletConfig servletConfig, + WebMetadataBuildItem webMetadataBuildItem) { + String contextPath; + if (servletConfig.contextPath.isPresent()) { + if (!servletConfig.contextPath.get().startsWith("/")) { + contextPath = "/" + servletConfig.contextPath; + } else { + contextPath = servletConfig.contextPath.get(); + } + } else if (webMetadataBuildItem.getWebMetaData().getDefaultContextPath() != null) { + contextPath = webMetadataBuildItem.getWebMetaData().getDefaultContextPath(); + } else { + contextPath = "/"; + } + return new ServletContextPathBuildItem(contextPath); + } + @Record(STATIC_INIT) @BuildStep() public ServletDeploymentManagerBuildItem build(List servlets, @@ -278,14 +297,13 @@ public ServletDeploymentManagerBuildItem build(List servlets, UndertowDeploymentRecorder recorder, RecorderContext context, List extensions, BeanContainerBuildItem bc, - BuildProducer servletContextPathBuildItemBuildProducer, + ServletContextPathBuildItem servletContextPathBuildItem, WebMetadataBuildItem webMetadataBuildItem, BuildProducer substitutions, Consumer reflectiveClasses, LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdownContext, KnownPathsBuildItem knownPaths, - ServletConfig servletConfig, HttpBuildTimeConfig httpBuildTimeConfig) throws Exception { ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder(ServletSecurityInfo.class, @@ -296,20 +314,8 @@ public ServletDeploymentManagerBuildItem build(List servlets, WebMetaData webMetaData = webMetadataBuildItem.getWebMetaData(); final IndexView index = combinedIndexBuildItem.getIndex(); processAnnotations(index, webMetaData); - String contextPath; - if (servletConfig.contextPath.isPresent()) { - if (!servletConfig.contextPath.get().startsWith("/")) { - contextPath = "/" + servletConfig.contextPath; - } else { - contextPath = servletConfig.contextPath.get(); - } - } else if (webMetadataBuildItem.getWebMetaData().getDefaultContextPath() != null) { - contextPath = webMetadataBuildItem.getWebMetaData().getDefaultContextPath(); - } else { - contextPath = "/"; - } - servletContextPathBuildItemBuildProducer.produce(new ServletContextPathBuildItem(contextPath)); + String contextPath = servletContextPathBuildItem.getServletContextPath(); RuntimeValue deployment = recorder.createDeployment("test", knownPaths.knownFiles, knownPaths.knownDirectories, launchMode.getLaunchMode(), shutdownContext, contextPath, httpBuildTimeConfig.rootPath); diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index a1fdf23dd3787..bd8cab35937f6 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -245,7 +245,9 @@ public void addServletInitParam(RuntimeValue info, String name, Str public void addServletMapping(RuntimeValue info, String name, String mapping) throws Exception { ServletInfo sv = info.getValue().getServlets().get(name); - sv.addMapping(mapping); + if (sv != null) { + sv.addMapping(mapping); + } } public void setMultipartConfig(RuntimeValue sref, String location, long fileSize, long maxRequestSize, diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextPathBuildItem.java b/extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/ServletContextPathBuildItem.java similarity index 100% rename from extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextPathBuildItem.java rename to extensions/undertow/spi/src/main/java/io/quarkus/undertow/deployment/ServletContextPathBuildItem.java diff --git a/integration-tests/elytron-undertow/src/main/java/io/quarkus/it/undertow/elytron/RootResource.java b/integration-tests/elytron-undertow/src/main/java/io/quarkus/it/undertow/elytron/RootResource.java new file mode 100644 index 0000000000000..0f14f12cf336a --- /dev/null +++ b/integration-tests/elytron-undertow/src/main/java/io/quarkus/it/undertow/elytron/RootResource.java @@ -0,0 +1,35 @@ +package io.quarkus.it.undertow.elytron; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; + +@Path("/rest") +public class RootResource { + + @POST + @Consumes(MediaType.TEXT_PLAIN) + public String posts(String data, @Context SecurityContext sec) { + if (data == null) { + throw new RuntimeException("No post data"); + } + if (sec.getUserPrincipal().getName() == null) { + throw new RuntimeException("Failed to get user principal"); + } + return "post success"; + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String approval(@Context SecurityContext sec) { + if (sec.getUserPrincipal().getName() == null) { + throw new RuntimeException("Failed to get user principal"); + } + return "get success"; + } +} diff --git a/integration-tests/elytron-undertow/src/main/resources/META-INF/web.xml b/integration-tests/elytron-undertow/src/main/resources/META-INF/web.xml index 36c6fd5b09ed4..33a8ec5f2053e 100644 --- a/integration-tests/elytron-undertow/src/main/resources/META-INF/web.xml +++ b/integration-tests/elytron-undertow/src/main/resources/META-INF/web.xml @@ -5,7 +5,10 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="false"> - + + javax.ws.rs.core.Application + /mapped/* + diff --git a/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthRestTest.java b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthRestTest.java new file mode 100644 index 0000000000000..adf40cd938599 --- /dev/null +++ b/integration-tests/elytron-undertow/src/test/java/io/quarkus/it/undertow/elytron/BaseAuthRestTest.java @@ -0,0 +1,43 @@ +package io.quarkus.it.undertow.elytron; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +class BaseAuthRestTest { + + @Test + @RepeatedTest(100) + void testPost() { + // This is a regression test in that we had a problem where the Vert.x request was not paused + // before the authentication filters ran and the post message was thrown away by Vert.x because + // RESTEasy hadn't registered its request handlers yet. + given() + .header("Authorization", "Basic am9objpqb2hu") + .body("Bill") + .contentType(ContentType.TEXT) + .when() + .post("/foo/mapped/rest") + .then() + .statusCode(200) + .body(is("post success")); + } + + @Test + void testGet() { + given() + .header("Authorization", "Basic am9objpqb2hu") + .when() + .get("/foo/mapped/rest") + .then() + .statusCode(200) + .body(is("get success")); + } + +} From 5c434c42ab058d269f46ed879ae7c63b1b0f68a7 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 7 Jan 2020 13:03:49 +0100 Subject: [PATCH 498/602] Qute - support @TemplateExtension declared on a class - also update docs and add some tests - resolves #6438 --- docs/src/main/asciidoc/qute-reference.adoc | 56 ++++++++++-- .../qute/deployment/QuteProcessor.java | 57 +++++++++--- .../deployment/ReflectionResolverTest.java | 2 - .../TemplateExtensionMethodsTest.java | 90 +++++++++++++++++++ .../java/io/quarkus/qute/EvaluatorImpl.java | 9 ++ .../quarkus/qute/ReflectionValueResolver.java | 8 +- .../io/quarkus/qute/TemplateExtension.java | 16 +++- .../generator/ExtensionMethodGenerator.java | 60 ++++++++----- .../generator/ValueResolverGenerator.java | 3 +- 9 files changed, 247 insertions(+), 54 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index bdc22a6d99bce..1446fd78f4012 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -538,10 +538,25 @@ NOTE: A value resolver is also generated for all types used in parameter declara [[template_extension_methods]] === Template Extension Methods -A value resolver is automatically generated for a template extension method annotated with `@TemplateExtension`. -The method must be static, must not return `void` and must accept at least one parameter. -The class of the first parameter is used to match the base object and the method name is used to match the property name. +Extension methods can be used to extend the data classes with new functionality. +For example, it is possible to add "computed properties" and "virtual methods". +A value resolver is automatically generated for a method annotated with `@TemplateExtension`. +If declared on a class a value resolver is generated for every non-private method declared on the class. +Methods that do not meet the following requirements are ignored. +A template extension method: + +* must be static, +* must not return `void`, +* must accept at least one parameter. + +The class of the first parameter is always used to match the base object. +The method name is used to match the property name by default. +However, it is possible to specify the matching name with `TemplateExtension#matchName()`. + +NOTE: A special constant - `ANY` - may be used to specify that the extension method matches any name. In that case, the method must declare at least two parameters and the second parameter must be a string. + +.Extension Method Example [source,java] ---- package org.acme; @@ -555,25 +570,48 @@ class Item { } } +@TemplateExtension class MyExtensions { - @TemplateExtension static BigDecimal discountedPrice(Item item) { <1> return item.getPrice().multiply(new BigDecimal("0.9")); } } ---- -<1> The method matches `Item.class` and `discountedPrice` property name. +<1> This method matches an expression with base object of the type `Item.class` and the `discountedPrice` property name. This template extension method makes it possible to render the following template: [source,html] ---- -{#each items} <1> - {it.discountedPrice} -{/each} +{item.discountedPrice} <1> ---- -<1> `items` is resolved to a list of `org.acme.Item` instances. +<1> `item` is resolved to an instance of `org.acme.Item`. + +==== Method Parameters + +An extension method may accept multiple parameters. +The first parameter is always used to pass the base object, ie. `org.acme.Item` in the previous example. +Other parameters are resolved when rendering the template and passed to the extension method. + +.Multiple Parameters Example +[source,java] +---- +@TemplateExtension +class MyExtensions { + + static BigDecimal scale(BigDecimal val, int scale, RoundingMode mode) { <1> + return val.setScale(scale, mode); + } +} +---- +<1> This method matches an expression with base object of the type `BigDecimal.class`, with the `scale` virtual method name and two virtual method parameters. + +[source,html] +---- +{item.discountedPrice.scale(2,mode)} <1> +---- +<1> `item.discountedPrice` is resolved to an instance of `BigDecimal`. === @TemplateData diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index 8ae0021315c49..27989177efa35 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -98,6 +98,8 @@ public class QuteProcessor { static final DotName MAP = DotName.createSimple(Map.class.getName()); static final DotName MAP_ENTRY = DotName.createSimple(Entry.class.getName()); + private static final String MATCH_NAME = "matchName"; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.QUTE); @@ -255,25 +257,53 @@ void collectTemplateExtensionMethods(BeanArchiveIndexBuildItem beanArchiveIndex, BuildProducer extensionMethods) { IndexView index = beanArchiveIndex.getIndex(); + Map methods = new HashMap<>(); + Map classes = new HashMap<>(); for (AnnotationInstance templateExtension : index.getAnnotations(ExtensionMethodGenerator.TEMPLATE_EXTENSION)) { if (templateExtension.target().kind() == Kind.METHOD) { - MethodInfo method = templateExtension.target().asMethod(); - ExtensionMethodGenerator.validate(method); - String matchName = null; - AnnotationValue matchNameValue = templateExtension.value("matchName"); - if (matchNameValue != null) { - matchName = matchNameValue.asString(); - } - if (matchName == null) { - matchName = method.name(); + methods.put(templateExtension.target().asMethod(), templateExtension); + } else if (templateExtension.target().kind() == Kind.CLASS) { + classes.put(templateExtension.target().asClass(), templateExtension); + } + } + + for (Entry entry : methods.entrySet()) { + MethodInfo method = entry.getKey(); + ExtensionMethodGenerator.validate(method); + produceExtensionMethod(index, extensionMethods, method, entry.getValue()); + LOGGER.debugf("Found template extension method %s declared on %s", method, + method.declaringClass().name()); + } + + for (Entry entry : classes.entrySet()) { + ClassInfo clazz = entry.getKey(); + for (MethodInfo method : clazz.methods()) { + if (!Modifier.isStatic(method.flags()) || method.returnType().kind() == org.jboss.jandex.Type.Kind.VOID + || method.parameters().isEmpty() || Modifier.isPrivate(method.flags()) || methods.containsKey(method)) { + continue; } - extensionMethods.produce(new TemplateExtensionMethodBuildItem(method, matchName, - index.getClassByName(method.parameters().get(0).name()))); + produceExtensionMethod(index, extensionMethods, method, entry.getValue()); + LOGGER.debugf("Found template extension method %s declared on %s", method, + method.declaringClass().name()); } } } + private void produceExtensionMethod(IndexView index, BuildProducer extensionMethods, + MethodInfo method, AnnotationInstance extensionAnnotation) { + String matchName = null; + AnnotationValue matchNameValue = extensionAnnotation.value(MATCH_NAME); + if (matchNameValue != null) { + matchName = matchNameValue.asString(); + } + if (matchName == null) { + matchName = method.name(); + } + extensionMethods.produce(new TemplateExtensionMethodBuildItem(method, matchName, + index.getClassByName(method.parameters().get(0).name()))); + } + @BuildStep void validateBeansInjectedInTemplates(ApplicationArchivesBuildItem applicationArchivesBuildItem, TemplatesAnalysisBuildItem analysis, BeanArchiveIndexBuildItem beanArchiveIndex, @@ -411,6 +441,9 @@ public void write(String name, byte[] data) { idx = name.lastIndexOf(ValueResolverGenerator.SUFFIX); } String className = name.substring(0, idx).replace("/", "."); + if (className.contains(ValueResolverGenerator.NESTED_SEPARATOR)) { + className = className.replace(ValueResolverGenerator.NESTED_SEPARATOR, "$"); + } boolean appClass = appClassPredicate.test(className); LOGGER.debugf("Writing %s [appClass=%s]", name, appClass); generatedClass.produce(new GeneratedClassBuildItem(appClass, name, data)); @@ -451,7 +484,7 @@ public void write(String name, byte[] data) { ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(classOutput); for (TemplateExtensionMethodBuildItem templateExtension : templateExtensionMethods) { - extensionMethodGenerator.generate(templateExtension.getMethod()); + extensionMethodGenerator.generate(templateExtension.getMethod(), templateExtension.getMatchName()); } generatedTypes.addAll(extensionMethodGenerator.getGeneratedTypes()); diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java index e26a6bf8d748e..0a6c17ed063b1 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/ReflectionResolverTest.java @@ -19,8 +19,6 @@ public class ReflectionResolverTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(HelloReflect.class) - // Make sure we do not detect the template data - .addAsResource(new StringAsset("quarkus.qute.detect-template-data=false"), "application.properties") .addAsResource(new StringAsset("{age}:{ping}:{noMatch}"), "templates/reflect.txt")); @Inject diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java new file mode 100644 index 0000000000000..55e99c70c52be --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java @@ -0,0 +1,90 @@ +package io.quarkus.qute.deployment; + +import static io.quarkus.qute.TemplateExtension.ANY; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Engine; +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateExtension; +import io.quarkus.test.QuarkusUnitTest; + +public class TemplateExtensionMethodsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Foo.class, Extensions.class) + .addAsResource(new StringAsset("{foo.name.toLower} {foo.name.ignored} {foo.callMe(1)} {foo.baz}"), + "templates/foo.txt") + .addAsResource(new StringAsset("{baz.setScale(2,roundingMode)}"), + "templates/baz.txt") + .addAsResource(new StringAsset("{anyInt.foo('bing')}"), + "templates/any.txt")); + + @Inject + Template foo; + + @Inject + Engine engine; + + @Test + public void testTemplateExtensions() { + assertEquals("fantomas NOT_FOUND 11 baz", + foo.data("foo", new Foo("Fantomas", 10l)).render()); + } + + @Test + public void testMethodParameters() { + assertEquals("123.46", + engine.getTemplate("baz.txt").data("roundingMode", RoundingMode.HALF_UP).data("baz", new BigDecimal("123.4563")) + .render()); + } + + @Test + public void testMatchAnyWithParameter() { + assertEquals("10=bing", + engine.getTemplate("any.txt").data("anyInt", 10).render()); + } + + @TemplateExtension + public static class Extensions { + + String ignored(String val) { + return val.toLowerCase(); + } + + static String toLower(String val) { + return val.toLowerCase(); + } + + static Long callMe(Foo foo, Integer val) { + return foo.age + val; + } + + @TemplateExtension(matchName = "baz") + static String override(Foo foo) { + return "baz"; + } + + static BigDecimal setScale(BigDecimal val, int scale, RoundingMode mode) { + return val.setScale(scale, mode); + } + + @TemplateExtension(matchName = ANY) + static String any(Integer val, String name, String info) { + return val + "=" + info; + } + } + +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java index f1704a4ed69ec..80052ee679c3a 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java @@ -85,6 +85,7 @@ private CompletionStage resolve(EvalContextImpl evalContext, Iterator evaluate(Expression expression) { return resolutionContext.evaluate(expression); } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EvalContextImpl [tryParent=").append(tryParent).append(", base=").append(base).append(", name=") + .append(name).append(", params=").append(params).append("]"); + return builder.toString(); + } + } } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java index c32e273748ade..b025a09ba4b3b 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java @@ -30,12 +30,16 @@ public int getPriority() { @Override public boolean appliesTo(EvalContext context) { - return context.getBase() != null; + Object base = context.getBase(); + if (base == null) { + return false; + } + return memberCache.computeIfAbsent(MemberKey.newInstance(base, context.getName()), ReflectionValueResolver::findWrapper) + .isPresent(); } @Override public CompletionStage resolve(EvalContext context) { - Object base = context.getBase(); MemberKey key = MemberKey.newInstance(base, context.getName()); MemberWrapper wrapper = memberCache.computeIfAbsent(key, ReflectionValueResolver::findWrapper).orElse(null); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java index 55c3cb2eef4d1..234b5a96d8618 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateExtension.java @@ -1,16 +1,24 @@ package io.quarkus.qute; import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** - * A value resolver is automatically generated for a template extension method. + * A value resolver is automatically generated for a method annotated with this annotation. If declared on a class a value + * resolver is generated for every non-private static method declared on the class. Methods that do not meet the following + * requirements are ignored. *

- * The method must be static, must not return {@code void} and must accept at least one parameter. The class of the first - * parameter is used to match the base object. + * A template extension method: + *